Compare commits

...

129 Commits

Author SHA1 Message Date
kolaente e7ffba5322 [skip ci] Updated translations via Crowdin 2021-09-29 20:53:14 +00:00
kolaente 1b7a6cecfe
chore(ci): temporarily disable cache 2021-09-29 22:48:01 +02:00
kolaente 4a1b402e62
fix: quick add magic always disabled 2021-09-29 22:42:13 +02:00
renovate e20be7b788 chore(deps): update dependency vite to v2.6.1 (#807)
Reviewed-on: vikunja/frontend#807
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-29 20:39:08 +00:00
kolaente 416380025e
fix: more spacing for last viewed tasks headline 2021-09-29 21:29:09 +02:00
kolaente 96ef25ba01
fix: don't enable editing when the user has no rights for it
related: #804
2021-09-29 21:22:44 +02:00
Christoph Schmatzler 7cd3394da5
Allow specifying listen ports (#27) 2021-09-29 21:12:22 +02:00
kolaente 4bd2c94256
fix: don't try to create a task with an empty title when creating multiple tasks at once 2021-09-29 21:01:54 +02:00
konrad 96523f1fbf feat: task checklist improvements (#797)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#797
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-09-29 18:31:14 +00:00
konrad d47b13647e feat(natural language): make natural language prefixes configurable (#795)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#795
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-09-29 18:30:55 +00:00
renovate 3b58756285 chore(deps): update dependency jest to v27.2.4 (#806)
Reviewed-on: vikunja/frontend#806
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-29 18:12:42 +00:00
renovate 924b77d10f chore(deps): update dependency vite to v2.6.0 (#805)
Reviewed-on: vikunja/frontend#805
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-29 18:12:30 +00:00
renovate 08521d7eb9 chore(deps): update dependency esbuild to v0.13.3 (#802)
Reviewed-on: vikunja/frontend#802
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-28 18:29:30 +00:00
renovate dd74b9f957 fix(deps): update dependency vue-i18n to v8.26.2 (#803)
Reviewed-on: vikunja/frontend#803
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-28 18:29:01 +00:00
renovate 5e8fb32c71 chore(deps): update dependency jest to v27.2.3 (#801)
Reviewed-on: vikunja/frontend#801
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-28 11:50:18 +00:00
renovate 8596d501dd chore(deps): update dependency cypress to v8.5.0 (#800)
Reviewed-on: vikunja/frontend#800
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-28 07:04:29 +00:00
renovate 4c0bdbb908 chore(deps): update typescript-eslint monorepo to v4.32.0 (#799)
Reviewed-on: vikunja/frontend#799
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-27 18:13:33 +00:00
kolaente 2691a84610
feat: don't show bullet points of checklists 2021-09-26 20:53:27 +02:00
kolaente e17116dac1
feat: make checklists work with '-' instead of '*' 2021-09-26 20:51:02 +02:00
konrad c0d744cfaa [skip ci] Updated translations via Crowdin 2021-09-26 18:42:26 +00:00
konrad 442e6b12e0 feat: allow quickly creating multiple tasks at once with multiline input (#796)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#796
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-09-26 18:22:28 +00:00
kolaente 3dfa286a12
feat(quick actions): select the item when only one result is available
resolves #703
2021-09-26 20:19:58 +02:00
kolaente a955488cdf
fix: date formatting for non-english languages 2021-09-26 20:07:15 +02:00
dpschen 8cd4bbccf6 chore: some small changes in the cypress README.md (#793)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#793
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-26 17:49:27 +00:00
dpschen 166539c7e8 fix: remove font preload of quicksand 300 (#794)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#794
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-26 16:16:22 +00:00
dpschen 7579222bb0 feat: add example configuration for vscode with volor (#791)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#791
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-26 14:49:06 +00:00
kolaente 8b01dc6b71
fix: run tests with unstable api 2021-09-26 15:54:58 +02:00
kolaente 4b64e27d2b
fix(natural language parser): parts of week days in other words 2021-09-26 15:00:11 +02:00
kolaente 403d77ce14
fix(natural language parser): fix parsing short days
related: #773
2021-09-26 14:38:15 +02:00
kolaente e918b82cfa
feat: don't try to load task comments if they are disabled 2021-09-26 13:44:13 +02:00
renovate 75f1a5a97f chore(deps): update dependency autoprefixer to v10.3.6 (#792)
Reviewed-on: vikunja/frontend#792
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-26 08:37:08 +00:00
dpschen e9978548d8 feat: add types for vite (#790)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#790
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-25 18:33:30 +00:00
dpschen c551bf5836 feat: define node version in .nvmrc file (#789)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#789
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-25 18:33:00 +00:00
dpschen feb34c8cc1 fix: eslint settings (#787)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#787
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-25 15:56:06 +00:00
renovate 72627d13c6 chore(deps): update dependency jest to v27.2.2 (#788)
Reviewed-on: vikunja/frontend#788
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-25 15:55:11 +00:00
kolaente f762d8ad4d
fix: remove unused function 2021-09-24 21:46:42 +02:00
Dominik Pschenitschni 697ea12c8e
fix: breaking attribute coercion behavior
see: https://v3.vuejs.org/guide/migration/attribute-coercion.html#overview
2021-09-24 21:46:42 +02:00
Dominik Pschenitschni fe27a432c7
feat: move unique functions from taskList to List 2021-09-24 21:46:42 +02:00
Dominik Pschenitschni f51371bbe0
feat: move from life cycle to data or watcher
- remove from created / mounted
- initialize component services in data
- use immediate watcher where appropriate
- deep watch for route changes
2021-09-24 21:46:42 +02:00
Dominik Pschenitschni ebeca48be4
fix: namespace collision of global error method with draggable error method 2021-09-24 21:46:42 +02:00
Dominik Pschenitschni 0da7a46612
feat: formatting
- remove unneeded data props
- prepare for compiler warnings vue3; see https://v3.vuejs.org/guide/migration/v-bind.html
2021-09-24 21:46:42 +02:00
Dominik Pschenitschni 4454e6cf22
feat: update to new slot syntax
Prepare for vue 3
2021-09-24 21:46:42 +02:00
kolaente 77f8b27dc6
fix: lists disappearing when updating their namespace 2021-09-24 20:51:59 +02:00
dpschen 1bc6d66895 [skip ci] Updated translations via Crowdin 2021-09-24 18:27:00 +00:00
dpschen 41331c8a86 feature/remove-attachment-upload-mixin (#724)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#724
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-24 18:16:37 +00:00
dpschen 4f2378ff02 feat: add variant hint-modal to modal component (#764)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#764
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-24 18:08:48 +00:00
kolaente 97416ab2d5 [skip ci] Updated translations via Crowdin 2021-09-24 17:57:26 +00:00
kolaente dedf6cbf21
fix: quick add magic assignee prefix in explanation 2021-09-24 19:47:40 +02:00
renovate dab6bc23ed fix(deps): pin dependency ufo to 0.7.9 (#780)
Reviewed-on: vikunja/frontend#780
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-23 18:23:23 +00:00
renovate c44db22b99 chore(deps): update dependency esbuild to v0.13.2 (#782)
Reviewed-on: vikunja/frontend#782
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-23 18:22:51 +00:00
renovate 4e62ab2b79 fix(deps): update dependency vue-i18n to v8.26.1 (#784)
Reviewed-on: vikunja/frontend#784
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-23 18:22:17 +00:00
dpschen 6b1bf27bf8 feat: reduce file size by removing by removing fonts (#759)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#759
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-23 14:07:08 +00:00
dpschen 3245752a80 fix: computed in api-config (#777)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#777
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-23 05:09:47 +00:00
renovate 649bbd8c9b chore(deps): update dependency esbuild to v0.13.1 (#776)
Reviewed-on: vikunja/frontend#776
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-23 05:07:17 +00:00
renovate ab954fcb9a fix(deps): update dependency vue-i18n to v8.26.0 (#779)
Reviewed-on: vikunja/frontend#779
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-23 05:06:43 +00:00
dpschen 17d11c6ce3 fix: prevent vue-shortkey use in elements with contenteditable (#775)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#775
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-22 16:25:09 +00:00
renovate d72d69debb chore(deps): update dependency sass to v1.42.1 (#772)
Reviewed-on: vikunja/frontend#772
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-22 09:55:06 +00:00
renovate 7d59e64c61 chore(deps): update dependency autoprefixer to v10.3.5 (#771)
Reviewed-on: vikunja/frontend#771
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-22 07:13:20 +00:00
renovate 6d72ac1ef3 [skip ci] Updated translations via Crowdin 2021-09-22 06:18:44 +00:00
renovate da4bac09f8 chore(deps): update dependency esbuild to v0.12.29 (#769)
Reviewed-on: vikunja/frontend#769
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-22 06:08:12 +00:00
renovate 64f4852d76 fix(deps): update dependency browserslist to v4.17.1 (#770)
Reviewed-on: vikunja/frontend#770
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-22 06:07:40 +00:00
renovate 1ae8f84b1c [skip ci] Updated translations via Crowdin 2021-09-21 20:18:47 +00:00
renovate 2f506ea653 chore(deps): update dependency sass to v1.42.0 (#751)
Reviewed-on: vikunja/frontend#751
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 20:08:17 +00:00
renovate e5158d9438 chore(deps): update dependency cypress to v8.4.1 (#750)
Reviewed-on: vikunja/frontend#750
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 19:06:48 +00:00
renovate c758c00f54 chore(deps): update dependency vite to v2.5.10 (#746)
Reviewed-on: vikunja/frontend#746
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 19:06:11 +00:00
renovate 1a0b47a581 fix(deps): update dependency date-fns to v2.24.0 (#757)
Reviewed-on: vikunja/frontend#757
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 19:05:48 +00:00
renovate 35a9532b86 chore(deps): update dependency eslint-plugin-vue to v7.18.0 (#761)
Reviewed-on: vikunja/frontend#761
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 19:05:35 +00:00
renovate 68947b77e9 chore(deps): update dependency @types/jest to v27.0.2 (#766)
Reviewed-on: vikunja/frontend#766
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 19:04:58 +00:00
dpschen 7719ef1bef feat: move pagination to dedicated component (#760)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#760
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-21 19:03:38 +00:00
dpschen 2ff0976da6 [skip ci] Updated translations via Crowdin 2021-09-21 17:46:16 +00:00
dpschen 77352e7a8c fix spelling in cypress README (#763)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#763
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-21 16:59:35 +00:00
simon1506 d88e299358 fix: no drag delay when using mouse on touch device (#748)
Reviewed-on: vikunja/frontend#748
Reviewed-by: konrad <k@knt.li>
Co-authored-by: simon1506 <simon.sch.dev@gmail.com>
Co-committed-by: simon1506 <simon.sch.dev@gmail.com>
2021-09-21 16:47:42 +00:00
dpschen 9122a184d6 feat: move fontawesome icons import to dedicated file (#742)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#742
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-21 16:39:56 +00:00
dpschen 728dfc52e5 feat: close modals with esc key (#741)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#741
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-21 16:37:56 +00:00
dpschen 47ad115738 chore: remove obsolete css vendor prefixes (#739)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#739
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-21 16:36:38 +00:00
dpschen 3ac25c9f08 fix: import bulma utilities global (#738)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#738
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-21 16:35:46 +00:00
renovate 7fbd9c6ab0 fix(deps): update dependency dompurify to v2.3.3 (#754)
Reviewed-on: vikunja/frontend#754
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 16:23:50 +00:00
renovate 681b76b3a5 fix(deps): update dependency marked to v3.0.4 (#753)
Reviewed-on: vikunja/frontend#753
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 16:23:28 +00:00
renovate 857fdc35e4 chore(deps): update typescript-eslint monorepo to v4.31.2 (#749)
Reviewed-on: vikunja/frontend#749
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 16:23:12 +00:00
renovate b70c88b3f3 fix(deps): update dependency vue-i18n to v8.25.1 (#747)
Reviewed-on: vikunja/frontend#747
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 16:22:48 +00:00
renovate b6ad09e3d8 chore(deps): update dependency jest to v27.2.1 (#745)
Reviewed-on: vikunja/frontend#745
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 16:22:27 +00:00
renovate eddb2cbce0 chore(deps): update dependency esbuild to v0.12.28 (#744)
Reviewed-on: vikunja/frontend#744
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 16:22:01 +00:00
renovate 1cf0316b3b chore(deps): update dependency typescript to v4.4.3 (#740)
Reviewed-on: vikunja/frontend#740
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 16:21:23 +00:00
dpschen 50c1a2e4d5 feat simplify taskList mixin (#728)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#728
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-10 14:39:18 +00:00
dpschen 07a6a31f47 chore: move constants in folder (#732)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#732
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-10 14:21:33 +00:00
dpschen 0295113f50 feat: use store getters to check auth (#731)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#731
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-10 13:04:00 +00:00
dpschen b5df941e39 chore: define default filters and params at one location (#721)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#721
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-10 13:02:52 +00:00
konrad 077fe264f0 fix: use date-fns for gantt years (#734)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#734
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-09-10 12:58:23 +00:00
dpschen dae441a373 feat: simplify heading blur logic (#727)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#727
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-10 12:57:59 +00:00
dpschen 0376ef53e3 fix: remove attachment by id (#725)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#725
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-10 12:50:41 +00:00
renovate 7ab308b846 chore(deps): update workbox monorepo to v6.3.0 (#730)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#730
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-10 12:49:50 +00:00
renovate 33447c4a09 chore(deps): update dependency sass to v1.39.2 (#733)
Reviewed-on: vikunja/frontend#733
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-10 11:13:54 +00:00
dpschen d0e46e59e8 chore: make method event independent (#719)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#719
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-09 22:25:08 +00:00
dpschen 0ed3cf2553 feat: import bulma utilities global (#718)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#718
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-09 22:24:08 +00:00
renovate 33f1480284 chore(deps): update dependency esbuild to v0.12.26 (#729)
Reviewed-on: vikunja/frontend#729
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-09 16:17:12 +00:00
dpschen 432c6babf2 feat: use computed for api domain (#722)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#722
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-08 21:48:30 +00:00
dpschen 96ef926dde chore: create progress dots dynamically (#715)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#715
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-08 21:45:28 +00:00
dpschen 87c70cec0e chore: define default label background color once (#713)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#713
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-08 21:44:56 +00:00
renovate 4689de7f1f fix(deps): update dependency marked to v3.0.3 (#726)
Reviewed-on: vikunja/frontend#726
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-08 21:19:21 +00:00
renovate b0f616d784 [skip ci] Updated translations via Crowdin 2021-09-08 20:36:21 +00:00
renovate 11b5d0574d chore(deps): update dependency vite to v2.5.6 (#723)
Reviewed-on: vikunja/frontend#723
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-08 20:31:09 +00:00
kolaente 134a09c9f2
Fix download export user data title 2021-09-08 22:19:45 +02:00
kolaente c7c3ef79be
0.18.1 release preparations 2021-09-08 19:49:23 +02:00
kolaente 2bae8e95e5
Fix task attributes overridden when saving the task title with enter 2021-09-08 19:37:08 +02:00
dpschen c4095327ad feat: make it possible to fake online state via dev env (#720)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#720
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-08 16:30:14 +00:00
dpschen c9631c1e71 fix: call to /null from background image (#714)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#714
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-08 16:28:26 +00:00
dpschen 4fc8858c64 fix: kanban-card mutatation violation (#712)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#712
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-08 16:28:13 +00:00
kolaente 9cee720ac9
Fix sort order for table view 2021-09-08 18:13:02 +02:00
kolaente 6f89863c81
Fix missing translation when creating a new task on the kanban board 2021-09-08 17:49:10 +02:00
kolaente debdc83f1b
Fix data export download progress 2021-09-08 17:33:58 +02:00
renovate 033b30d6cd Update dependency @4tw/cypress-drag-drop to v2 (#711)
Reviewed-on: vikunja/frontend#711
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-08 14:27:47 +00:00
renovate c23d3c1488 Update dependency jest to v27.1.1 (#716)
Reviewed-on: vikunja/frontend#716
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-08 14:27:24 +00:00
renovate 4f305b28fd Update dependency vite to v2.5.5 (#709)
Reviewed-on: vikunja/frontend#709
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-08 05:43:13 +00:00
renovate 33926a2d11 Update dependency vite to v2.5.4 (#708)
Reviewed-on: vikunja/frontend#708
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-07 17:44:41 +00:00
kolaente 332dbc1598
Fix rearranging tasks in a kanban bucket when its limit was reached 2021-09-07 18:38:53 +02:00
renovate 28a4b1c533 Update dependency vite-plugin-vue2 to v1.8.2 (#707)
Reviewed-on: vikunja/frontend#707
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-07 16:17:41 +00:00
kolaente 78346f9ac6
Fix translation badge 2021-09-06 21:22:18 +02:00
renovate 8f916c275b Update typescript-eslint monorepo to v4.31.0 (#706)
Reviewed-on: vikunja/frontend#706
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-06 19:02:48 +00:00
renovate cb0e2a7bc1 Update dependency axios to v0.21.4 (#705)
Reviewed-on: vikunja/frontend#705
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-06 16:53:46 +00:00
kolaente 2d804c3af3
0.18.0 release preparations 2021-09-05 17:07:51 +02:00
renovate 6868fb134d Update dependency browserslist to v4.17.0 (#701)
Reviewed-on: vikunja/frontend#701
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-05 12:40:41 +00:00
kolaente b7ec975384
Change building latest docker image 2021-09-05 14:40:14 +02:00
kolaente 75174c2496
Switch the :latest docker image tag to contain the latest release instead of the latest unstable 2021-09-05 13:44:05 +02:00
kolaente e0c9332634
Add proofread languages to available languages 2021-09-05 13:31:53 +02:00
kolaente 7a4e568898 [skip ci] Updated translations via Crowdin 2021-09-05 11:10:28 +00:00
kolaente fb20afae92
Move translated files after downloading them 2021-09-05 13:06:45 +02:00
kolaente db0e023d35 [skip ci] Updated translations via Crowdin 2021-09-05 11:03:35 +00:00
kolaente 13f01cdc30
Add depends_on for push step 2021-09-05 13:03:15 +02:00
kolaente b4919a5662
Add depends_on for upload step 2021-09-05 13:02:43 +02:00
175 changed files with 5707 additions and 3820 deletions

View File

@ -12,29 +12,30 @@ trigger:
services:
- name: api
image: vikunja/api
image: vikunja/api:unstable
environment:
VIKUNJA_SERVICE_TESTINGTOKEN: averyLongSecretToSe33dtheDB
VIKUNJA_LOG_LEVEL: DEBUG
steps:
- name: restore-cache
image: meltwater/drone-cache:dev
pull: true
environment:
AWS_ACCESS_KEY_ID:
from_secret: cache_aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: cache_aws_secret_access_key
settings:
restore: true
bucket: kolaente.dev-drone-dependency-cache
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
cache_key: '{{ .Repo.Name }}_{{ checksum "yarn.lock" }}_{{ arch }}_{{ os }}'
mount:
- '.cache'
# Disabled until we figure out why it is so slow
# - name: restore-cache
# image: meltwater/drone-cache:dev
# pull: true
# environment:
# AWS_ACCESS_KEY_ID:
# from_secret: cache_aws_access_key_id
# AWS_SECRET_ACCESS_KEY:
# from_secret: cache_aws_secret_access_key
# settings:
# restore: true
# bucket: kolaente.dev-drone-dependency-cache
# endpoint: https://s3.fr-par.scw.cloud
# region: fr-par
# path_style: true
# cache_key: '{{ .Repo.Name }}_{{ checksum "yarn.lock" }}_{{ arch }}_{{ os }}'
# mount:
# - '.cache'
- name: dependencies
image: node:16
@ -44,28 +45,28 @@ steps:
CYPRESS_CACHE_FOLDER: .cache/cypress/
commands:
- yarn --frozen-lockfile --network-timeout 100000
depends_on:
- restore-cache
# depends_on:
# - restore-cache
- name: rebuild-cache
image: meltwater/drone-cache:dev
pull: true
environment:
AWS_ACCESS_KEY_ID:
from_secret: cache_aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: cache_aws_secret_access_key
settings:
rebuild: true
bucket: kolaente.dev-drone-dependency-cache
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
cache_key: '{{ .Repo.Name }}_{{ checksum "yarn.lock" }}_{{ arch }}_{{ os }}'
mount:
- '.cache'
depends_on:
- dependencies
# - name: rebuild-cache
# image: meltwater/drone-cache:dev
# pull: true
# environment:
# AWS_ACCESS_KEY_ID:
# from_secret: cache_aws_access_key_id
# AWS_SECRET_ACCESS_KEY:
# from_secret: cache_aws_secret_access_key
# settings:
# rebuild: true
# bucket: kolaente.dev-drone-dependency-cache
# endpoint: https://s3.fr-par.scw.cloud
# region: fr-par
# path_style: true
# cache_key: '{{ .Repo.Name }}_{{ checksum "yarn.lock" }}_{{ arch }}_{{ os }}'
# mount:
# - '.cache'
# depends_on:
# - dependencies
- name: lint
image: node:16
@ -166,23 +167,23 @@ steps:
commands:
- git fetch --tags
- name: restore-cache
image: meltwater/drone-cache:dev
pull: true
environment:
AWS_ACCESS_KEY_ID:
from_secret: cache_aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: cache_aws_secret_access_key
settings:
restore: true
bucket: kolaente.dev-drone-dependency-cache
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
cache_key: '{{ .Repo.Name }}_{{ checksum "yarn.lock" }}_{{ arch }}_{{ os }}'
mount:
- '.cache'
# - name: restore-cache
# image: meltwater/drone-cache:dev
# pull: true
# environment:
# AWS_ACCESS_KEY_ID:
# from_secret: cache_aws_access_key_id
# AWS_SECRET_ACCESS_KEY:
# from_secret: cache_aws_secret_access_key
# settings:
# restore: true
# bucket: kolaente.dev-drone-dependency-cache
# endpoint: https://s3.fr-par.scw.cloud
# region: fr-par
# path_style: true
# cache_key: '{{ .Repo.Name }}_{{ checksum "yarn.lock" }}_{{ arch }}_{{ os }}'
# mount:
# - '.cache'
- name: build
image: node:16
@ -196,8 +197,8 @@ steps:
- "echo '{\"VERSION\": \"'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'\"}' > src/version.json"
- yarn run build
- sed -i 's/http\:\\/\\/localhost\\:3456\\/api\\/v1/\\/api\\/v1/g' dist/index.html # Override the default api url used for developing
depends_on:
- restore-cache
# depends_on:
# - restore-cache
- name: static
image: kolaente/zip
@ -241,23 +242,23 @@ steps:
commands:
- git fetch --tags
- name: restore-cache
image: meltwater/drone-cache:dev
pull: true
environment:
AWS_ACCESS_KEY_ID:
from_secret: cache_aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: cache_aws_secret_access_key
settings:
restore: true
bucket: kolaente.dev-drone-dependency-cache
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
cache_key: '{{ .Repo.Name }}_{{ checksum "yarn.lock" }}_{{ arch }}_{{ os }}'
mount:
- '.cache'
# - name: restore-cache
# image: meltwater/drone-cache:dev
# pull: true
# environment:
# AWS_ACCESS_KEY_ID:
# from_secret: cache_aws_access_key_id
# AWS_SECRET_ACCESS_KEY:
# from_secret: cache_aws_secret_access_key
# settings:
# restore: true
# bucket: kolaente.dev-drone-dependency-cache
# endpoint: https://s3.fr-par.scw.cloud
# region: fr-par
# path_style: true
# cache_key: '{{ .Repo.Name }}_{{ checksum "yarn.lock" }}_{{ arch }}_{{ os }}'
# mount:
# - '.cache'
- name: build
image: node:16
@ -271,8 +272,8 @@ steps:
- "echo '{\"VERSION\": \"'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'\"}' > src/version.json"
- yarn run build
- sed -i 's/http\:\\/\\/localhost\\:3456\\/api\\/v1/\\/api\\/v1/g' dist/index.html # Override the default api url used for developing
depends_on:
- restore-cache
# depends_on:
# - restore-cache
- name: static
image: kolaente/zip
@ -341,7 +342,7 @@ trigger:
- "refs/tags/**"
steps:
- name: docker-latest
- name: docker-unstable
image: plugins/docker:linux-arm
pull: true
settings:
@ -350,7 +351,7 @@ steps:
password:
from_secret: docker_password
repo: vikunja/frontend
tags: latest-linux-arm
tags: unstable-linux-arm
build_args:
- USE_RELEASE=true
- RELEASE_VERSION=unstable
@ -380,7 +381,7 @@ steps:
depends_on:
- clone
- name: docker-latest-arm64
- name: docker-unstable-arm64
image: plugins/docker:linux-arm64
pull: true
settings:
@ -389,7 +390,7 @@ steps:
password:
from_secret: docker_password
repo: vikunja/frontend
tags: latest-linux-arm64
tags: unstable-linux-arm64
build_args:
- USE_RELEASE=true
- RELEASE_VERSION=unstable
@ -438,7 +439,7 @@ trigger:
- "refs/tags/**"
steps:
- name: docker-latest
- name: docker-unstable
image: plugins/docker:linux-amd64
pull: true
settings:
@ -447,7 +448,7 @@ steps:
password:
from_secret: docker_password
repo: vikunja/frontend
tags: latest-linux-amd64
tags: unstable-linux-amd64
build_args:
- USE_RELEASE=true
- RELEASE_VERSION=unstable
@ -488,12 +489,12 @@ depends_on:
- docker-arm-release
steps:
- name: manifest-latest
- name: manifest-unstable
pull: always
image: plugins/manifest
settings:
tags: latest
spec: docker-manifest-latest.tmpl
tags: unstable
spec: docker-manifest-unstable.tmpl
password:
from_secret: docker_password
username:
@ -516,6 +517,23 @@ steps:
when:
ref:
- "refs/tags/**"
- name: manifest-release-latest
pull: always
image: plugins/manifest
depends_on:
- clone
settings:
tags: latest
ignore_missing: true
spec: docker-manifest.tmpl
password:
from_secret: docker_password
username:
from_secret: docker_username
when:
ref:
- "refs/tags/**"
---
kind: pipeline
@ -555,8 +573,8 @@ kind: pipeline
type: docker
name: update-translations
#depends_on:
# - build
depends_on:
- build
trigger:
branch:
@ -577,9 +595,19 @@ steps:
CROWDIN_KEY:
from_secret: crowdin_key
- name: move-files
pull: always
image: bash
depends_on:
- download
commands:
- mv src/i18n/lang/*/*.json src/i18n/lang
- name: push
pull: always
image: appleboy/drone-git-push
depends_on:
- move-files
settings:
author_email: "frederik@vikunja.io"
author_name: Frederick [Bot]
@ -593,6 +621,8 @@ steps:
- name: upload
pull: always
image: jonasfranz/crowdin
depends_on:
- clone
settings:
files:
en.json: src/i18n/lang/en.json

View File

@ -19,4 +19,7 @@ indent_size = 2
[*.json]
indent_style = space
indent_size = 2
indent_size = 2
[.nvmrc]
insert_final_newline = false

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v16

View File

@ -0,0 +1,12 @@
{
"recommendations": [
"codezombiech.gitignore",
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig",
"johnsoncodehk.volar",
"lokalise.i18n-ally",
"mgmcdermott.vscode-language-babel",
"mikestead.dotenv",
"Syler.sass-indented"
]
}

View File

@ -0,0 +1,30 @@
{
"eslint.packageManager": "yarn",
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"eslint.format.enable": true,
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
// https://eslint.vuejs.org/user-guide/#editor-integrations
"eslint.validate": [
"javascript",
"javascriptreact",
"vue"
],
"vetur.validation.template": false,
// i18n ally
"i18n-ally.localesPaths": [
"src/i18n/lang"
],
"i18n-ally.sortKeys": true,
"i18n-ally.keepFulfilled": true,
"i18n-ally.keystyle": "nested",
}

View File

@ -2,13 +2,347 @@
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
All releases can be found on https://code.vikunja.io/frontend/releases.
The releases aim at the api versions which is why there are missing versions.
## [0.18.1] - 2021-09-08
### Added
* feat: make it possible to fake online state via dev env (#720)
### Fixed
* fix: call to /null from background image (#714)
* Fix data export download progress
* fix: kanban-card mutatation violation (#712)
* Fix missing translation when creating a new task on the kanban board
* Fix rearranging tasks in a kanban bucket when its limit was reached
* Fix sort order for table view
* Fix task attributes overridden when saving the task title with enter
* Fix translation badge
### Dependency Updates
* Update dependency @4tw/cypress-drag-drop to v2 (#711)
* Update dependency axios to v0.21.4 (#705)
* Update dependency jest to v27.1.1 (#716)
* Update dependency vite-plugin-vue2 to v1.8.2 (#707)
* Update dependency vite to v2.5.4 (#708)
* Update dependency vite to v2.5.5 (#709)
* Update typescript-eslint monorepo to v4.31.0 (#706)
## [0.18.0] - 2021-09-05
### Added
* Add a button to copy an attachment url from the attachment overview
* Add collapsing kanban buckets
* Add confirm with enter when setting a new password
* Add default list setting & creating tasks from home (#520)
* Add depends_on for push step
* Add depends_on for upload step
* Add drag delay on mobile
* Add express for serve:dev
* Add filters for quick action bar
* Add frontend tests for list history
* Add making tasks favorite from the task detail view
* Add missing position property to list and bucket models
* Add more debug logs for gantt charts
* Add more global state tests (#521)
* Add proofread languages to available languages
* Add quick action bar shortcut to shortcut overview
* Add setting for the first day of the week
* Add showing version info in GUI
* Add syncing translations to crowdin
* Add timeout to fix race condition when authenticating as a link share and renewing the token simultaneously
* Add translations (#562)
* Add typescript support for helper functions (#598)
* Add vite (#416)
* Allow failure of the weblate update step
* Always set the kanban board to full width for share links
* Another day, another js date edge-case
* Automatically update approved translations from crowdin
* Break long list titles in list overview
* Preload labels and use locally stored in vuex
* PWA improvments (#622)
* Quick Actions & global search (#528)
* Quick add magic for tasks (#570)
* Reorder tasks, lists and kanban buckets (#620)
* Show last visited list on home page
* Show recently visited lists in quick actions
* Show salutation based on the time of day
* Sort labels alphabetically on tasks
* Switch the :latest docker image tag to contain the latest release instead of the latest unstable
### Changed
* Change building latest docker image
* Change desktop downstream trigger plugin with our own debug build
* Change menu hamburger icon
* Change quick add magic characters to be more familiar with the todoist ones
* Change the docker builder image to a working one on arm
* chore: discard old font file formats (#673)
* chore: only import common languages (#671)
* Cleanup broken sw functions
* Cleanup drone pipeline
* Cleanup old vue cli config
* Configure tests retries
* Decrease page padding on task detail page
* Directly redirect to the openid auth provider if that's the only auth method
* Don't allow dragging a list when the user does not have the rights
* Don't load already loaded task attachments again when saving an edited task description
* Don't prefetch all i18n files
* Don't show archived lists/namespaces in quick actions
* feat: provide global variables in all components (#669)
* Hide favorite list edit menu
* Hide keyboard shortcuts indicator on mobile
* Improve chunk size
* Improve some translations (#581)
* Improve tests
* Indicate done tasks in quick actions
* Load list background in list card
* Make editor edit button at the bottom the default and make sure the done button stands out more
* Make saving a text edit a button
* Make sure highlight.js is always lazy-loaded
* Make sure the task popup view takes up all the space it can on mobile
* Make tests less flaky
* Make the logo smaller on link shared lists
* Make the progress bar color lighter
* Move creation of new items to the bottom of the multiselect list
* Move general settings to the top
* Move translated files after downloading them
* Move weblate ping to shell script
* Only add a drag delay if on mobile instead of setting it to 0
* Only build a bundle for modern browsers
* Refactor success and error messages
* Refactor success and error notifications to prevent html in them
* Remove logout button for link shares
* Run frontend-tests with dist in ci (#605)
* Save auth tokens from link shares only in memory, don't persist them to localStorage
* Search namespaces locally only when duplicating a list
* Show errors from openid provider
* Show labels alphabetically sorted in the overview
* Small cleanups & code improvements
* TOTP UX improvements & translation fixes
### Fixed
* Fix changing the repeat mode of a task when no value is entered yet
* Fix comment on different task after clicking on a task notification
* Fix CTA spacings
* Fix date parsing parsing words with weekdays in them (#607)
* fix(deps): update dependency marked to v3.0.1 (#677)
* fix(deps): update dependency marked to v3.0.2 (#682)
* Fix error property already defined as a function
* Fix flickering pre-loaded search results when focusing the search input
* Fix Gantt layout overflowsing on mobile
* Fix gantt months being wrong
* Fix git push remote to update crowdin translations
* Fix global mutation of has tasks state
* Fix header layout for long list titles
* Fix highlight.js in editor
* Fix home page tests
* Fix keyboard shortcuts not working on the task detail page
* Fix label changes appearing to be saved immediately when editing them
* Fix labels list in saved filter spacing
* Fix lint
* Fix list archived notification mobile layout
* Fix list settings not being available when list backgrounds are disabled
* Fix lists showing up multiple times in history
* Fix llama background url
* Fix loading a list when it was already partially saved in vuex
* Fix loading & disabled state on inputs when creating a new task
* Fix loading labels when editing a saved filter
* Fix menu styles
* Fix missing background for tasks on a shared list with a background
* Fix multiselect search padding
* Fix new lists created with quick actions not showing up in the menu
* fix: non unique ids (#672)
* Fix not reloading tasks of a saved filter after editing it
* Fix not updating list name in store when changing it
* Fix other values getting pushed away when creating a new one through multiselect
* Fix padding for kanban cards
* Fix parsing dates on the last day of the month
* Fix populating task details ater updating the description
* Fix quick actions not opening
* Fix quick actions not working when nonexisting lists where left over in history
* Fix redirecting to /login for some routes
* Fix removing a namespace from state after it was deleted
* Fix resetting date filters from upcoming after viewing a task detail page (popup)
* Fix sass division
* Fix saving showing archived setting
* Fix selecting a single value from multiselect
* Fix sending openid scopes when authenticating
* Fix sending the user back to the list view they came from when opening a task in detail view
* Fix setting a task as favorite button
* Fix setting delete button for newly created task comments
* Fix setting filters for reminders
* Fix setting secret for updating translations
* Fix setting task favorite status in test fixtures
* Fix showing an editor save button in cases where it wasn't required
* Fix showing edit buttons when the user does not have the rights to use them
* Fix showing import tasks cta when tasks are loading
* Fix some translation strings
* Fix sorting labels
* Fix spacing for task detail view in lists with a background
* Fix table headers wrapping in table view
* Fix table text alignment in task detail page
* Fix table view scrolling on mobile
* Fix test for saving a task description
* Fix tests failing on thursdays
* Fix token in storage not getting renewed
* Fix translating dates
* Fix usage of / in sass
* Fix user name and avatar alignment in navbar
* Fix users not removed from the list in settings when unshared
* Fix user test fixtures
* fix: vuex mutation violation from draggable (#674)
### Dependency Updates
* chore(deps): update dependency @4tw/cypress-drag-drop to v1.8.1 (#693)
* chore(deps): update dependency autoprefixer to v10.3.3 (#684)
* chore(deps): update dependency autoprefixer to v10.3.4 (#697)
* chore(deps): update dependency axios to v0.21.2 (#698)
* chore(deps): update dependency axios to v0.21.3 (#700)
* chore(deps): update dependency cypress to v8.3.1 (#689)
* chore(deps): update dependency esbuild to v0.12.23 (#683)
* chore(deps): update dependency esbuild to v0.12.24 (#688)
* chore(deps): update dependency esbuild to v0.12.25 (#696)
* chore(deps): update dependency eslint-plugin-vue to v7.17.0 (#686)
* chore(deps): update dependency jest to v27.1.0 (#687)
* chore(deps): update dependency sass to v1.38.1 (#679)
* chore(deps): update dependency sass to v1.38.2 (#690)
* chore(deps): update dependency sass to v1.39.0 (#695)
* chore(deps): update dependency typescript to v4.4.2 (#685)
* chore(deps): update dependency vite-plugin-pwa to v0.11.2 (#681)
* chore(deps): update dependency vite to v2.5.1 (#680)
* chore(deps): update dependency vite to v2.5.2 (#692)
* chore(deps): update dependency vite to v2.5.3 (#694)
* chore(deps): update typescript-eslint monorepo to v4.29.3 (#676)
* chore(deps): update typescript-eslint monorepo to v4.30.0 (#691)
* Update dependency autoprefixer to v10.3.2 (#670)
* Update dependency browserslist to v4.16.7 (#634)
* Update dependency browserslist to v4.16.8 (#664)
* Update dependency browserslist to v4.17.0 (#701)
* Update dependency bulma to v0.9.3 (#554)
* Update dependency cypress-file-upload to v5.0.8 (#556)
* Update dependency cypress to v7.3.0 (#507)
* Update dependency cypress to v7.4.0 (#517)
* Update dependency cypress to v7.5.0 (#541)
* Update dependency cypress to v7.6.0 (#561)
* Update dependency cypress to v7.7.0 (#577)
* Update dependency cypress to v8.1.0 (#624)
* Update dependency cypress to v8.2.0 (#637)
* Update dependency cypress to v8.3.0 (#660)
* Update dependency cypress to v8 (#601)
* Update dependency date-fns to v2.22.0 (#523)
* Update dependency date-fns to v2.22.1 (#524)
* Update dependency date-fns to v2.23.0 (#604)
* Update dependency dompurify to v2.2.9 (#529)
* Update dependency dompurify to v2.3.0 (#573)
* Update dependency dompurify to v2.3.1 (#655)
* Update dependency esbuild to v0.12.15 (#610)
* Update dependency esbuild to v0.12.16 (#614)
* Update dependency esbuild to v0.12.17 (#623)
* Update dependency esbuild to v0.12.18 (#638)
* Update dependency esbuild to v0.12.19 (#643)
* Update dependency esbuild to v0.12.20 (#654)
* Update dependency esbuild to v0.12.21 (#666)
* Update dependency esbuild to v0.12.22 (#668)
* Update dependency eslint-plugin-vue to v7.10.0 (#525)
* Update dependency eslint-plugin-vue to v7.11.0 (#547)
* Update dependency eslint-plugin-vue to v7.11.1 (#548)
* Update dependency eslint-plugin-vue to v7.12.1 (#565)
* Update dependency eslint-plugin-vue to v7.13.0 (#574)
* Update dependency eslint-plugin-vue to v7.14.0 (#597)
* Update dependency eslint-plugin-vue to v7.15.0 (#625)
* Update dependency eslint-plugin-vue to v7.15.1 (#633)
* Update dependency eslint-plugin-vue to v7.16.0 (#648)
* Update dependency eslint to v7.27.0 (#514)
* Update dependency eslint to v7.28.0 (#539)
* Update dependency eslint to v7.29.0 (#555)
* Update dependency eslint to v7.30.0 (#571)
* Update dependency eslint to v7.31.0 (#596)
* Update dependency eslint to v7.32.0 (#627)
* Update dependency highlight.js to v11.0.1 (#538)
* Update dependency highlight.js to v11.1.0 (#582)
* Update dependency highlight.js to v11.2.0 (#630)
* Update dependency highlight.js to v11 (#527)
* Update dependency jest to v27.0.3 (#526)
* Update dependency jest to v27.0.4 (#535)
* Update dependency jest to v27.0.5 (#558)
* Update dependency jest to v27.0.6 (#569)
* Update dependency jest to v27 (#519)
* Update dependency marked to v2.0.4 (#510)
* Update dependency marked to v2.0.5 (#513)
* Update dependency marked to v2.0.6 (#522)
* Update dependency marked to v2.0.7 (#532)
* Update dependency marked to v2.1.0 (#552)
* Update dependency marked to v2.1.1 (#553)
* Update dependency marked to v2.1.2 (#559)
* Update dependency marked to v2.1.3 (#567)
* Update dependency marked to v3 (#657)
* Update dependency @rollup/plugin-commonjs to v19.0.2 (#617)
* Update dependency sass to v1.33.0 (#512)
* Update dependency sass to v1.34.0 (#515)
* Update dependency sass to v1.34.1 (#534)
* Update dependency sass to v1.35.0 (#550)
* Update dependency sass to v1.35.1 (#551)
* Update dependency sass to v1.35.2 (#579)
* Update dependency sass to v1.36.0 (#606)
* Update dependency sass to v1.37.0 (#628)
* Update dependency sass to v1.37.2 (#632)
* Update dependency sass to v1.37.5 (#635)
* Update dependency sass to v1.38.0 (#661)
* Update dependency ts-jest to v27.0.4 (#602)
* Update dependency ts-jest to v27.0.5 (#662)
* Update dependency @types/jest to v27.0.1 (#653)
* Update dependency @types/jest to v27 (#650)
* Update dependency vite-plugin-pwa to v0.10.0 (#644)
* Update dependency vite-plugin-pwa to v0.11.0 (#667)
* Update dependency vite-plugin-pwa to v0.8.2 (#612)
* Update dependency vite-plugin-pwa to v0.9.3 (#629)
* Update dependency vite-plugin-vue2 to v1.7.3 (#613)
* Update dependency vite-plugin-vue2 to v1.8.0 (#646)
* Update dependency vite-plugin-vue2 to v1.8.1 (#656)
* Update dependency vite to v2.4.3 (#611)
* Update dependency vite to v2.4.4 (#619)
* Update dependency vite to v2.5.0 (#658)
* Update dependency vue-advanced-cropper to v1.6.0 (#516)
* Update dependency vue-advanced-cropper to v1.7.0 (#543)
* Update dependency vue-advanced-cropper to v1.8.0 (#641)
* Update dependency vue-advanced-cropper to v1.8.1 (#642)
* Update dependency vue-advanced-cropper to v1.8.2 (#645)
* Update dependency vue-flatpickr-component to v8.1.7 (#572)
* Update dependency vue-i18n to v8.24.5 (#564)
* Update dependency vue-i18n to v8.25.0 (#595)
* Update dependency vue-router to v3.5.2 (#557)
* Update dependency wait-on to v6 (#568)
* Update dependency workbox-cli to v6.1.5 (#609)
* Update Font Awesome (#636)
* Update Node.js (#549)
* Update Node.js to v16.4.1 (#576)
* Update Node.js to v16.4.2 (#578)
* Update typescript-eslint monorepo to v4.28.4 (#600)
* Update typescript-eslint monorepo to v4.28.5 (#618)
* Update typescript-eslint monorepo to v4.29.0 (#631)
* Update typescript-eslint monorepo to v4.29.1 (#647)
* Update typescript-eslint monorepo to v4.29.2 (#659)
* Update vue monorepo to v2.6.13 (#530)
* Update vue monorepo to v2.6.14 (#540)
* Update workbox monorepo to v6.2.0 (#639)
* Update workbox monorepo to v6.2.2 (#640)
* Update workbox monorepo to v6.2.4 (#649)
* User account deletion (#651)
* User Data Export and import (#699)
## [0.17.0 - 2021-05-14]
### Added
@ -148,7 +482,8 @@ The releases aim at the api versions which is why there are missing versions.
* Make sure all arm64 build steps run in parallel
* Make sure all empty pages have a call to action
* Make sure all popups & dropdowns are animated
* Make sure attachements are only added once to the list after uploading + Make sure the attachment list shows up every time after adding an attachment
* Make sure attachements are only added once to the list after uploading + Make sure the attachment list shows up every
time after adding an attachment
* Make sure no cta's are visible while the page is loading
* Make sure the loading spinner is always visible at the end of the page
* Make the button shadow lighter
@ -675,7 +1010,7 @@ The releases aim at the api versions which is why there are missing versions.
* Hide totp settings if it is disabled server side
* Increase network timeout when building docker image
* Make sure the version includes the tag when building docker images
* #PrideMonth
* # PrideMonth
* Only renew user token on tab focus events
* Redirect the user to login page if the token expired when the tab gets focus again
* Remove title length restrictions
@ -710,7 +1045,7 @@ The releases aim at the api versions which is why there are missing versions.
## [0.13] - 2020-05-12
#### Added
#### Added
* Add docker run script to change api url on startup
* Add github token for renovate (#89)
@ -1055,6 +1390,7 @@ The releases aim at the api versions which is why there are missing versions.
* Use email instead of username when resetting a password
### Fixed
* Fixed trying to verify an email when there was none
* Fixed loading tasks when the user was not authenticated

View File

@ -4,8 +4,8 @@
[![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.17.0-brightgreen.svg)](https://dl.vikunja.io)
[![Translation](https://hosted.weblate.org/widgets/vikunja/-/frontend/svg-badge.svg)](https://hosted.weblate.org/engage/vikunja/)
[![Download](https://img.shields.io/badge/download-v0.18.1-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

@ -9,8 +9,8 @@
## Fixtures
We're using the [test endpoint](https://vikunja.io/docs/config-options/#testingtoken) of the vikunja api to
seed the database with test data before running the tests.
We're using the [test endpoint](https://vikunja.io/docs/config-options/#testingtoken) of the vikunja api to
seed the database with test data before running the tests.
This ensures better reproducability of tests.
## Running The Tests Locally
@ -22,20 +22,20 @@ It uses the same configuration as the CI.
To use it, run
```
```shell
docker-compose up -d
```
Then, once all containers are started, run
```
docker-composer run cypress bash
```shell
docker-compose run cypress bash
```
to get a shell inside the cypress container.
In that shell you can then execute the tests with
```
```shell
yarn test:frontend
```
@ -43,6 +43,6 @@ yarn test:frontend
To open the Cypress Dashboard and run tests from there, run
```
```shell
yarn cypress:open
```

View File

@ -27,7 +27,7 @@ describe('Task', () => {
it('Should be created new', () => {
cy.visit('/lists/1/list')
cy.get('input.input[placeholder="Add a new task…"')
cy.get('.input[placeholder="Add a new task…"')
.type('New Task')
cy.get('.button')
.contains('Add')
@ -43,7 +43,7 @@ describe('Task', () => {
cy.visit('/lists/1/list')
cy.get('.list-is-empty-notice')
.should('not.exist')
cy.get('input.input[placeholder="Add a new task…"')
cy.get('.input[placeholder="Add a new task…"')
.type('New Task')
cy.get('.button')
.contains('Add')

View File

@ -1,17 +1,17 @@
image: vikunja/frontend:latest
image: vikunja/frontend:unstable
manifests:
-
image: vikunja/frontend:latest-linux-amd64
image: vikunja/frontend:unstable-linux-amd64
platform:
architecture: amd64
os: linux
-
image: vikunja/frontend:latest-linux-arm64
image: vikunja/frontend:unstable-linux-arm64
platform:
architecture: arm64
os: linux
-
image: vikunja/frontend:latest-linux-arm
image: vikunja/frontend:unstable-linux-arm
platform:
architecture: arm
os: linux

View File

@ -12,7 +12,6 @@
<link rel="apple-touch-icon" href="/images/icons/apple-touch-icon-180x180.png"/>
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-700italic.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-italic.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-300.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-500.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-700.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-regular.woff2" as="font">

View File

@ -14,74 +14,78 @@
"test:frontend": "cypress run"
},
"dependencies": {
"browserslist": "4.16.8",
"browserslist": "4.17.1",
"bulma": "0.9.3",
"camel-case": "4.1.2",
"copy-to-clipboard": "3.3.1",
"date-fns": "2.23.0",
"dompurify": "2.3.1",
"date-fns": "2.24.0",
"dompurify": "2.3.3",
"highlight.js": "11.2.0",
"is-touch-device": "1.0.1",
"lodash": "4.17.21",
"marked": "3.0.2",
"marked": "3.0.4",
"register-service-worker": "1.7.2",
"snake-case": "3.0.4",
"ufo": "0.7.9",
"verte": "0.0.12",
"vue": "2.6.14",
"vue-advanced-cropper": "1.8.2",
"vue-drag-resize": "1.5.4",
"vue-easymde": "1.4.0",
"vue-i18n": "8.25.0",
"vue-i18n": "8.26.2",
"vue-shortkey": "3.1.7",
"vuedraggable": "2.24.3",
"vuex": "3.6.2",
"workbox-precaching": "6.2.4"
"workbox-precaching": "6.3.0"
},
"devDependencies": {
"@4tw/cypress-drag-drop": "1.8.1",
"@4tw/cypress-drag-drop": "2.0.0",
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/vue-fontawesome": "2.0.2",
"@types/jest": "27.0.1",
"@typescript-eslint/eslint-plugin": "4.30.0",
"@typescript-eslint/parser": "4.30.0",
"@types/jest": "27.0.2",
"@typescript-eslint/eslint-plugin": "4.32.0",
"@typescript-eslint/parser": "4.32.0",
"@vue/babel-preset-app": "4.5.13",
"@vue/eslint-config-typescript": "7.0.0",
"autoprefixer": "10.3.4",
"axios": "0.21.3",
"@vue/runtime-dom": "latest",
"autoprefixer": "10.3.6",
"axios": "0.21.4",
"babel-eslint": "10.1.0",
"cypress": "8.3.1",
"cypress": "8.5.0",
"cypress-file-upload": "5.0.8",
"esbuild": "0.12.25",
"esbuild": "0.13.3",
"eslint": "7.32.0",
"eslint-plugin-vue": "7.17.0",
"eslint-plugin-vue": "7.18.0",
"express": "4.17.1",
"faker": "5.5.3",
"jest": "27.1.0",
"jest": "27.2.4",
"rollup-plugin-terser": "7.0.2",
"rollup-plugin-visualizer": "5.5.2",
"sass": "1.39.0",
"sass": "1.42.1",
"ts-jest": "27.0.5",
"typescript": "4.4.2",
"vite": "2.5.3",
"typescript": "4.4.3",
"vite": "2.6.1",
"vite-plugin-pwa": "0.11.2",
"vite-plugin-vue2": "1.8.1",
"vite-plugin-vue2": "1.8.2",
"vue-flatpickr-component": "8.1.7",
"vue-notification": "1.3.20",
"vue-router": "3.5.2",
"vue-template-compiler": "2.6.14",
"wait-on": "6.0.0",
"workbox-cli": "6.2.4"
"workbox-cli": "6.3.0"
},
"eslintConfig": {
"root": true,
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended",
"plugin:vue/essential",
"@vue/typescript"
],
"rules": {
@ -102,8 +106,10 @@
"never"
]
},
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser"
"parser": "@typescript-eslint/parser",
"ecmaVersion": 2021
},
"ignorePatterns": [
"*.test.*",

10
run.sh
View File

@ -2,15 +2,19 @@
# This shell script sets the api url based on an environment variable and starts nginx in foreground.
if [ -z "$VIKUNJA_API_URL" ]; then
VIKUNJA_API_URL="/api/v1"
fi
VIKUNJA_API_URL="${VIKUNJA_API_URL:-"/api/v1"}"
VIKUNJA_HTTP_PORT="${VIKUNJA_HTTP_PORT:-80}"
VIKUNJA_HTTPS_PORT="${VIKUNJA_HTTP_PORT:-443}"
# Escape the variable to prevent sed from complaining
VIKUNJA_API_URL=$(echo $VIKUNJA_API_URL |sed 's/\//\\\//g')
sed -i "s/http\:\/\/localhost\:3456\/api\/v1/$VIKUNJA_API_URL/g" /usr/share/nginx/html/index.html
sed -i "s/listen 80/listen $VIKUNJA_HTTP_PORT/g" nginx.conf
sed -i "s/listen 443/listen $VIKUNJA_HTTPS_PORT/g" nginx.conf
# Set the uid and gid of the nginx run user
usermod --non-unique --uid ${PUID} nginx
groupmod --non-unique --gid ${PGID} nginx

View File

@ -23,11 +23,9 @@
</template>
<script>
import {mapState} from 'vuex'
import {mapState, mapGetters} from 'vuex'
import isTouchDevice from 'is-touch-device'
import authTypes from './models/authTypes'
import Notification from './components/misc/notification'
import {KEYBOARD_SHORTCUTS_ACTIVE, ONLINE} from './store/mutation-types'
import KeyboardShortcuts from './components/misc/keyboard-shortcuts'
@ -74,11 +72,13 @@ export default {
return isTouchDevice()
},
...mapState({
authUser: state => state.auth.authenticated && (state.auth.info && state.auth.info.type === authTypes.USER),
authLinkShare: state => state.auth.authenticated && (state.auth.info && state.auth.info.type === authTypes.LINK_SHARE),
online: ONLINE,
keyboardShortcutsActive: KEYBOARD_SHORTCUTS_ACTIVE,
}),
...mapGetters('auth', [
'authUser',
'authLinkShare',
]),
},
methods: {
setupOnlineStatus() {
@ -105,10 +105,10 @@ export default {
const accountDeletionService = new AccountDeleteService()
accountDeletionService.confirm(this.$route.query.accountDeletionConfirm)
.then(() => {
this.success({message: this.$t('user.deletion.confirmSuccess')})
this.$message.success({message: this.$t('user.deletion.confirmSuccess')})
this.$store.dispatch('auth/refreshUserInfo')
})
.catch(e => this.error(e))
.catch(e => this.$message.error(e))
}
},
},

View File

@ -5,15 +5,13 @@
</a>
<div
:class="{'has-background': background}"
:style="{'background-image': `url(${background})`}"
:style="{'background-image': background && `url(${background})`}"
class="app-container"
>
<navigation/>
<div
:class="[
{
'is-menu-enabled': menuActive,
},
{ 'is-menu-enabled': menuActive },
$route.name,
]"
class="app-content"
@ -51,18 +49,17 @@ export default {
name: 'contentAuth',
components: {QuickActions, Navigation},
watch: {
'$route': 'doStuffAfterRoute',
'$route': {
handler: 'doStuffAfterRoute',
deep: true,
},
},
created() {
this.renewTokenOnFocus()
this.loadLabels()
},
computed: mapState({
namespaces(state) {
return state.namespaces.namespaces.filter(n => !n.isArchived)
},
currentList: CURRENT_LIST,
background: 'background', // FIXME: Return the full thing or nothing at all to prevent calls to /null
background: 'background',
menuActive: MENU_ACTIVE,
userInfo: state => state.auth.info,
authenticated: state => state.auth.authenticated,
@ -130,7 +127,7 @@ export default {
loadLabels() {
this.$store.dispatch('labels/loadAllLabels')
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
},

View File

@ -83,12 +83,12 @@
triggered by the change needs to have access to the current namespace
-->
<draggable
v-bind="dragOptions"
:value="activeLists[nk]"
@input="(lists) => updateActiveLists(n, lists)"
:group="`namespace-${n.id}-lists`"
@start="() => drag = true"
@end="e => saveListPosition(e, nk)"
v-bind="dragOptions"
handle=".handle"
:disabled="n.id < 0"
:class="{'dragging-disabled': n.id < 0}"
@ -206,7 +206,7 @@ export default {
return
}
this.$store.dispatch('lists/toggleListFavorite', list)
.catch(e => this.error(e))
.catch(e => this.$message.error(e))
},
resize() {
// Hide the menu by default on mobile
@ -252,7 +252,7 @@ export default {
position,
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
.finally(() => {
this.$set(this.listUpdating, list.id, false)

View File

@ -49,7 +49,7 @@
<div class="user">
<img :src="userAvatar" alt="" class="avatar" width="40" height="40"/>
<dropdown class="is-right" ref="usernameDropdown">
<template v-slot:trigger>
<template #trigger>
<x-button
type="secondary"
:shadow="false">
@ -97,7 +97,7 @@
<script>
import {mapState} from 'vuex'
import {CURRENT_LIST, QUICK_ACTIONS_ACTIVE} from '@/store/mutation-types'
import Rights from '@/models/rights.json'
import Rights from '@/models/constants/rights.json'
import Update from '@/components/home/update.vue'
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
import Dropdown from '@/components/misc/dropdown.vue'

View File

@ -9,9 +9,9 @@
'is-text is-inverted has-no-shadow underline-none':
type === 'tertary',
}"
:disabled="disabled"
:disabled="disabled || null"
@click="click"
:href="href !== '' ? href : false"
:href="href !== '' ? href : null"
>
<icon :icon="icon" v-if="showIconOnly"/>
<span class="icon is-small" v-else-if="icon !== ''">

View File

@ -48,16 +48,16 @@ export default {
},
},
watch: {
value(newVal) {
this.color = newVal
value: {
handler(value) {
this.color = value
},
immediate: true,
},
color() {
this.update()
},
},
mounted() {
this.color = this.value
},
computed: {
empty() {
return this.color === '#000000' || this.color === ''

View File

@ -151,15 +151,15 @@ export default {
},
},
mounted() {
this.setDateValue(this.value)
document.addEventListener('click', this.hideDatePopup)
},
beforeDestroy() {
document.removeEventListener('click', this.hideDatePopup)
},
watch: {
value(newVal) {
this.setDateValue(newVal)
value: {
handler: 'setDateValue',
immediate: true,
},
flatPickrDate(newVal) {
this.date = createDateFromString(newVal)

View File

@ -1,7 +1,7 @@
<template>
<div class="editor">
<div class="clear"></div>
<vue-easymde
:configs="config"
@change="bubble"
@ -31,7 +31,7 @@
<a @click="action.action">{{ action.title }}</a>
</li>
</ul>
<template v-else-if="showSave">
<template v-else-if="isEditEnabled && showSave">
<ul v-if="!isEditActive" class="actions">
<li>
<a @click="toggleEdit">{{ $t('input.editor.edit') }}</a>
@ -53,6 +53,7 @@ import hljs from 'highlight.js/lib/common'
import AttachmentModel from '../../models/attachment'
import AttachmentService from '../../services/attachment'
import {findCheckboxesInText} from '../../helpers/checklistFromText'
export default {
name: 'editor',
@ -303,34 +304,7 @@ export default {
return str.substr(0, index) + replacement + str.substr(index + replacement.length)
},
findNthIndex(str, n) {
const searchChecked = '* [x] '
const searchUnchecked = '* [ ] '
let inChecked, inUnchecked, startIndex = 0
// We're building an array with all checkboxes, checked or unchecked.
// I've found this to be the best way to always get the results I need.
// The difficulty without an index is that we need to get all checkboxes, checked and unchecked
// and calculate our index based off that to compare it and find the checkbox we need.
let checkboxes = []
// Searching in two different loops for each search term since that is way easier and more predicatble
// More "intelligent" solutions sometimes don't have all values or duplicates.
// Because we're sorting and removing duplicates of them, we can safely put everything in one giant array.
while ((inChecked = str.indexOf(searchChecked, startIndex)) > -1) {
checkboxes.push(inChecked)
startIndex = startIndex + searchChecked.length
}
startIndex = 0
while ((inUnchecked = str.indexOf(searchUnchecked, startIndex)) > -1) {
checkboxes.push(inUnchecked)
startIndex = startIndex + searchUnchecked.length
}
checkboxes.sort((a, b) => a - b)
checkboxes = checkboxes.filter((v, i, s) => s.indexOf(v) === i && v > -1)
const checkboxes = findCheckboxesInText(str)
return checkboxes[n]
},
renderPreview() {
@ -415,6 +389,7 @@ export default {
for (const check of textCheckbox) {
check.removeEventListener('change', this.handleCheckboxClick)
check.addEventListener('change', this.handleCheckboxClick)
check.parentElement.classList.add('has-checkbox')
}
}
})
@ -431,10 +406,12 @@ export default {
}
console.debug(index, this.text.substr(index, 9))
const listPrefix = this.text.substr(index, 1)
if (checked) {
this.text = this.replaceAt(this.text, index, '* [x] ')
this.text = this.replaceAt(this.text, index, `${listPrefix} [x] `)
} else {
this.text = this.replaceAt(this.text, index, '* [ ] ')
this.text = this.replaceAt(this.text, index, `${listPrefix} [ ] `)
}
this.bubble()
this.renderPreview()
@ -466,8 +443,15 @@ export default {
.preview.content {
margin-bottom: .5rem;
ul li input[type="checkbox"] {
margin-right: .5rem;
ul li {
input[type="checkbox"] {
margin-right: .5rem;
}
&.has-checkbox {
margin-left: -2em;
list-style: none;
}
}
}
}

View File

@ -4,7 +4,7 @@
:checked="checked"
:disabled="disabled"
:id="checkBoxId"
@change="updateData"
@change="(event) => updateData(event.target.checked)"
style="display: none;"
type="checkbox"/>
<label :for="checkBoxId" class="check">
@ -26,7 +26,7 @@ export default {
data() {
return {
checked: false,
checkBoxId: '',
checkBoxId: 'fancycheckbox' + Math.random(),
}
},
props: {
@ -40,21 +40,19 @@ export default {
},
},
watch: {
value(newVal) {
this.checked = newVal
value: {
handler(value) {
this.checked = value
},
immediate: true,
},
},
mounted() {
this.checked = this.value
},
created() {
this.checkBoxId = 'fancycheckbox' + Math.random()
},
methods: {
updateData(e) {
this.checked = e.target.checked
this.$emit('input', this.checked)
this.$emit('change', e.target.checked)
updateData(checked) {
this.checked = checked
this.$emit('input', checked)
this.$emit('change', checked)
},
},
}

View File

@ -190,14 +190,16 @@ export default {
},
mounted() {
document.addEventListener('click', this.hideSearchResultsHandler)
this.setSelectedObject(this.value)
},
beforeDestroy() {
document.removeEventListener('click', this.hideSearchResultsHandler)
},
watch: {
value(newVal) {
this.setSelectedObject(newVal)
value: {
handler(value) {
this.setSelectedObject(value)
},
immediate: true,
},
},
computed: {

View File

@ -25,15 +25,17 @@ export default {
Filters,
},
mounted() {
this.params = this.value
document.addEventListener('click', this.hidePopup)
},
beforeDestroy() {
document.removeEventListener('click', this.hidePopup)
},
watch: {
value(newVal) {
this.$set(this, 'params', newVal)
value: {
handler(value) {
this.params = value
},
immediate: true,
},
visible() {
this.visibleInternal = !this.visibleInternal

View File

@ -190,6 +190,35 @@ import ListService from '@/services/list'
import NamespaceService from '@/services/namespace'
import EditLabels from '@/components/tasks/partials/editLabels.vue'
// FIXME: merge with DEFAULT_PARAMS in taskList.js
const DEFAULT_PARAMS = {
sort_by: [],
order_by: [],
filter_by: [],
filter_value: [],
filter_comparator: [],
filter_include_nulls: true,
filter_concat: 'or',
s: '',
}
const DEFAULT_FILTERS = {
done: false,
dueDate: '',
requireAllFilters: false,
priority: 0,
usePriority: false,
startDate: '',
endDate: '',
percentDone: 0,
usePercentDone: false,
reminders: '',
assignees: '',
labels: '',
list_id: '',
namespace: '',
}
export default {
name: 'filters',
components: {
@ -202,58 +231,27 @@ export default {
},
data() {
return {
params: {
sort_by: [],
order_by: [],
filter_by: [],
filter_value: [],
filter_comparator: [],
filter_include_nulls: true,
filter_concat: 'or',
s: '',
},
filters: {
done: false,
dueDate: '',
requireAllFilters: false,
priority: 0,
usePriority: false,
startDate: '',
endDate: '',
percentDone: 0,
usePercentDone: false,
reminders: '',
assignees: '',
labels: '',
list_id: '',
namespace: '',
},
params: DEFAULT_PARAMS,
filters: DEFAULT_FILTERS,
usersService: UserService,
usersService: new UserService(),
foundusers: [],
users: [],
labelQuery: '',
labels: [],
listsService: ListService,
listsService: new ListService(),
foundlists: [],
lists: [],
namespaceService: NamespaceService,
namespaceService: new NamespaceService(),
foundnamespace: [],
namespace: [],
}
},
created() {
this.usersService = new UserService()
this.listsService = new ListService()
this.namespaceService = new NamespaceService()
},
mounted() {
this.params = this.value
this.filters.requireAllFilters = this.params.filter_concat === 'and'
this.prepareFilters()
},
props: {
value: {
@ -261,9 +259,12 @@ export default {
},
},
watch: {
value(newVal) {
this.$set(this, 'params', newVal)
this.prepareFilters()
value: {
handler(value) {
this.params = value
this.prepareFilters()
},
immediate: true,
},
},
computed: {
@ -483,7 +484,7 @@ export default {
.then(r => {
this.$set(this, kind, r)
})
.catch(e => this.error(e))
.catch(e => this.$message.error(e))
}
},
setDoneFilter() {
@ -538,7 +539,7 @@ export default {
}))
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
add(kind, filterName) {

View File

@ -50,13 +50,11 @@ export default {
},
},
watch: {
list() {
this.loadBackground()
list: {
handler: 'loadBackground',
immediate: true,
},
},
created() {
this.loadBackground()
},
methods: {
loadBackground() {
if (this.list === null || !this.list.backgroundInformation || this.backgroundLoading) {
@ -71,7 +69,7 @@ export default {
this.$set(this, 'background', b)
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
.finally(() => this.backgroundLoading = false)
},
@ -82,7 +80,7 @@ export default {
return
}
this.$store.dispatch('lists/toggleListFavorite', list)
.catch(e => this.error(e))
.catch(e => this.$message.error(e))
},
},
}

View File

@ -36,14 +36,7 @@
<div class="migration-in-progress">
<img :alt="name" :src="`/images/migration/${identifier}.png`"/>
<div class="progress-dots">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span v-for="i in progressDotsCount" :key="i" />
</div>
<img alt="Vikunja" src="/images/logo.svg">
</div>
@ -74,15 +67,19 @@
import AbstractMigrationService from '../../services/migrator/abstractMigration'
import AbstractMigrationFileService from '../../services/migrator/abstractMigrationFile'
const PROGRESS_DOTS_COUNT = 8
export default {
name: 'migration',
data() {
return {
progressDotsCount: PROGRESS_DOTS_COUNT,
authUrl: '',
isMigrating: false,
lastMigrationDate: null,
message: '',
migratorAuthCode: '',
migrationService: null,
}
},
props: {
@ -133,7 +130,7 @@ export default {
this.migrate()
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
}
},
@ -144,7 +141,7 @@ export default {
this.authUrl = r.url
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
migrate() {
@ -162,7 +159,7 @@ export default {
this.$store.dispatch('namespaces/loadNamespaces')
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
.finally(() => {
this.isMigrating = false
@ -179,7 +176,7 @@ export default {
this.$store.dispatch('namespaces/loadNamespaces')
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
.finally(() => {
this.isMigrating = false

View File

@ -24,7 +24,7 @@
</div>
<div class="api-url-info" v-else>
<i18n path="apiConfig.signInOn">
<span class="url" v-tooltip="apiUrl"> {{ apiDomain() }} </span>
<span class="url" v-tooltip="apiUrl"> {{ apiDomain }} </span>
</i18n>
<br />
<a @click="() => (configureApi = true)">{{ $t('apiConfig.change') }}</a>
@ -46,32 +46,31 @@
</template>
<script>
import { parseURL } from 'ufo'
const API_DEFAULT_PORT = 3456
export default {
name: 'apiConfig',
data() {
return {
configureApi: false,
apiUrl: '',
apiUrl: window.API_URL,
errorMsg: '',
successMsg: '',
}
},
created() {
this.apiUrl = window.API_URL
if (this.apiUrl === '') {
this.configureApi = true
}
},
methods: {
computed: {
apiDomain() {
if (window.API_URL.startsWith('/api/v1')) {
return window.location.host
}
const urlParts = window.API_URL.replace('http://', '')
.replace('https://', '')
.split(/[/?#]/)
return urlParts[0]
return parseURL(this.apiUrl).host
},
},
methods: {
setApiUrl() {
if (this.apiUrl === '') {
return
@ -131,17 +130,17 @@ export default {
return Promise.reject(e)
})
.catch((e) => {
// Check if it is reachable at port 3456 and https
if (urlToCheck.port !== 3456) {
// Check if it is reachable at port API_DEFAULT_PORT and https
if (urlToCheck.port !== API_DEFAULT_PORT) {
urlToCheck.protocol = 'https:'
urlToCheck.port = 3456
urlToCheck.port = API_DEFAULT_PORT
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch((e) => {
// Check if it is reachable at :3456 and /api/v1 and https
// Check if it is reachable at :API_DEFAULT_PORT and /api/v1 and https
urlToCheck.pathname = origUrlToCheck.pathname
if (
!urlToCheck.pathname.endsWith('/api/v1') &&
@ -154,17 +153,17 @@ export default {
return Promise.reject(e)
})
.catch((e) => {
// Check if it is reachable at port 3456 and http
if (urlToCheck.port !== 3456) {
// Check if it is reachable at port API_DEFAULT_PORT and http
if (urlToCheck.port !== API_DEFAULT_PORT) {
urlToCheck.protocol = 'http:'
urlToCheck.port = 3456
urlToCheck.port = API_DEFAULT_PORT
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch((e) => {
// Check if it is reachable at :3456 and /api/v1 and http
// Check if it is reachable at :API_DEFAULT_PORT and /api/v1 and http
urlToCheck.pathname = origUrlToCheck.pathname
if (
!urlToCheck.pathname.endsWith('/api/v1') &&
@ -179,14 +178,14 @@ export default {
.catch(() => {
// Still not found, url is still invalid
this.successMsg = ''
this.errorMsg = this.$t('apiConfig.error', {domain: this.apiDomain()})
this.errorMsg = this.$t('apiConfig.error', {domain: this.apiDomain})
window.API_URL = oldUrl
})
.then((r) => {
if (typeof r !== 'undefined') {
// Set it + save it to local storage to save us the hoops
this.errorMsg = ''
this.successMsg = this.$t('apiConfig.success', {domain: this.apiDomain()})
this.successMsg = this.$t('apiConfig.success', {domain: this.apiDomain})
localStorage.setItem('API_URL', window.API_URL)
this.configureApi = false
this.apiUrl = window.API_URL

View File

@ -1,7 +1,5 @@
<template>
<div class="modal-mask hint-modal">
<div @click.self="close()" class="modal-container">
<div class="modal-content">
<modal @close="close()">
<card class="has-background-white has-no-shadow" :title="$t('keyboardShortcuts.title')">
<div class="message is-primary">
<div class="message-body">
@ -55,9 +53,7 @@
<shortcut :keys="['r']"/>
</p>
</card>
</div>
</div>
</div>
</modal>
</template>
<script>
@ -73,4 +69,4 @@ export default {
},
},
}
</script>
</script>

View File

@ -1,33 +1,24 @@
<template>
<notifications position="bottom left" :max="2" class="global-notification">
<template slot="body" slot-scope="props">
<template #body="{ item, close }">
<div
:class="[
'vue-notification-template',
'vue-notification',
props.item.type,
item.type,
]"
@click="close(props)"
@click="close()"
>
<div
class="notification-title"
v-if="props.item.title"
>
{{ props.item.title }}
</div>
<div
class="notification-content"
>
<template v-for="(t, k) in props.item.text">
{{ t }}<br :key="k"/>
</template>
<div v-if="item.title" class="notification-title">{{ item.title }}</div>
<div class="notification-content">
<template v-for="(t, k) in item.text">{{ t }}<br :key="k"/></template>
</div>
<div
class="buttons is-right"
v-if="
props.item.data &&
props.item.data.actions &&
props.item.data.actions.length > 0
item.data &&
item.data.actions &&
item.data.actions.length > 0
"
>
<x-button
@ -36,7 +27,7 @@
:shadow="false"
class="is-small"
type="secondary"
v-for="(action, i) in props.item.data.actions"
v-for="(action, i) in item.data.actions"
>
{{ action.title }}
</x-button>
@ -49,11 +40,6 @@
<script>
export default {
name: 'notification',
methods: {
close(props) {
props.close()
},
},
}
</script>

View File

@ -0,0 +1,125 @@
<template>
<nav
aria-label="pagination"
class="pagination is-centered p-4"
role="navigation"
v-if="totalPages > 1"
>
<router-link
:disabled="currentPage === 1"
:to="getRouteForPagination(currentPage - 1)"
class="pagination-previous"
tag="button">
{{ $t('misc.previous') }}
</router-link>
<router-link
:disabled="currentPage === totalPages"
:to="getRouteForPagination(currentPage + 1)"
class="pagination-next"
tag="button">
{{ $t('misc.next') }}
</router-link>
<ul class="pagination-list">
<template v-for="(p, i) in pages">
<li :key="'page' + i" v-if="p.isEllipsis">
<span class="pagination-ellipsis">&hellip;</span>
</li>
<li :key="'page' + i" v-else>
<router-link
:aria-label="'Goto page ' + p.number"
:class="{ 'is-current': p.number === currentPage }"
:to="getRouteForPagination(p.number)"
class="pagination-link"
>
{{ p.number }}
</router-link>
</li>
</template>
</ul>
</nav>
</template>
<script>
function createPagination(totalPages, currentPage) {
const pages = []
for (let i = 0; i < totalPages; i++) {
// Show ellipsis instead of all pages
if (
i > 0 && // Always at least the first page
(i + 1) < totalPages && // And the last page
(
// And the current with current + 1 and current - 1
(i + 1) > currentPage + 1 ||
(i + 1) < currentPage - 1
)
) {
// Only add an ellipsis if the last page isn't already one
if (pages[i - 1] && !pages[i - 1].isEllipsis) {
pages.push({
number: 0,
isEllipsis: true,
})
}
continue
}
pages.push({
number: i + 1,
isEllipsis: false,
})
}
return pages
}
function getRouteForPagination(page = 1, type = 'list') {
return {
name: 'list.' + type,
params: {
type: type,
},
query: {
page: page,
},
}
}
export default {
name: 'Pagination',
props: {
totalPages: {
type: Number,
required: true,
},
currentPage: {
type: Number,
required: true,
},
},
computed: {
pages() {
return createPagination(this.totalPages, this.currentPage)
},
},
methods: {
getRouteForPagination,
},
}
</script>
<style lang="scss" scoped>
.pagination {
padding-bottom: 1rem;
.pagination-previous,
.pagination-next {
&:not(:disabled):hover {
background: $scheme-main;
cursor: pointer;
}
}
}
</style>

View File

@ -1,8 +1,8 @@
<template>
<span class="shortcuts">
<template v-for="(k, i) in keys">
<span :key="i">{{ k }}</span>
<i v-if="i < keys.length - 1" :key="`plus${i}`">+</i>
<kbd :key="i">{{ k }}</kbd>
<span v-if="i < keys.length - 1" :key="`plus${i}`">+</span>
</template>
</span>
</template>
@ -18,3 +18,22 @@ export default {
},
}
</script>
<style lang="scss" scoped>
.shortcuts {
display: flex;
align-items: center;
}
kbd {
padding: .1rem .35rem;
border: 1px solid $grey-300;
background: $grey-100;
border-radius: 3px;
font-size: .75rem;
}
span {
padding: 0 .25rem;
}
</style>

View File

@ -30,7 +30,7 @@ export default {
name: 'task-subscription',
data() {
return {
subscriptionService: SubscriptionService,
subscriptionService: new SubscriptionService(),
}
},
props: {
@ -49,9 +49,6 @@ export default {
default: true,
},
},
created() {
this.subscriptionService = new SubscriptionService()
},
computed: {
tooltipText() {
if (this.disabled) {
@ -99,10 +96,10 @@ export default {
this.subscriptionService.create(subscription)
.then(() => {
this.$emit('change', subscription)
this.success({message: this.$t('task.subscription.subscribeSuccess', {entity: this.entity})})
this.$message.success({message: this.$t('task.subscription.subscribeSuccess', {entity: this.entity})})
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
unsubscribe() {
@ -113,10 +110,10 @@ export default {
this.subscriptionService.delete(subscription)
.then(() => {
this.$emit('change', null)
this.success({message: this.$t('task.subscription.unsubscribeSuccess', {entity: this.entity})})
this.$message.success({message: this.$t('task.subscription.unsubscribeSuccess', {entity: this.entity})})
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
},

View File

@ -47,8 +47,6 @@ export default {
}
img {
-webkit-border-radius: 100%;
-moz-border-radius: 100%;
border-radius: 100%;
vertical-align: middle;

View File

@ -1,8 +1,27 @@
<template>
<transition name="modal">
<div class="modal-mask has-overflow" :class="{'has-overflow': overflow}">
<div class="modal-container" @mousedown.self.prevent.stop="$emit('close')" :class="{'has-overflow': overflow}">
<div class="modal-content" :class="{'has-overflow': overflow, 'is-wide': wide}">
<section
v-if="enabled"
class="modal-mask"
:class="[
{ 'has-overflow': overflow },
variant,
]"
>
<div
class="modal-container"
:class="{'has-overflow': overflow}"
@click.self.prevent.stop="$emit('close')"
@shortkey="$emit('close')"
v-shortkey="['esc']"
>
<div
class="modal-content"
:class="{
'has-overflow': overflow,
'is-wide': wide
}"
>
<slot>
<div class="header">
<slot name="header"></slot>
@ -29,14 +48,29 @@
</slot>
</div>
</div>
</div>
</section>
</transition>
</template>
<script>
export const TRANSITION_NAMES = {
MODAL: 'modal',
FADE: 'fade',
}
export const VARIANTS = {
DEFAULT: 'default',
HINT_MODAL: 'hint-modal',
SCROLLING: 'scrolling',
}
function validValue(values) {
return (value) => Object.values(values).includes(value)
}
export default {
name: 'modal',
mounted: function () {
mounted() {
document.addEventListener('keydown', (e) => {
// Close the model when escape is pressed
if (e.keyCode === 27) {
@ -45,6 +79,10 @@ export default {
})
},
props: {
enabled: {
type: Boolean,
default: true,
},
overflow: {
type: Boolean,
default: false,
@ -53,6 +91,126 @@ export default {
type: Boolean,
default: false,
},
transitionName: {
type: String,
default: TRANSITION_NAMES.MODAL,
validator: validValue(TRANSITION_NAMES),
},
variant: {
type: String,
default: VARIANTS.DEFAULT,
validator: validValue(VARIANTS),
},
},
}
</script>
<style lang="scss" scoped>
.modal-mask {
position: fixed;
z-index: 4000;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .8);
transition: opacity 150ms ease;
color: #fff;
}
.modal-container {
transition: all 150ms ease;
position: relative;
width: 100%;
height: 100%;
max-height: 100vh;
overflow: auto;
}
.default .modal-content,
.hint-modal .modal-content {
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
@media screen and (max-width: $tablet) {
margin: 0;
top: 25%;
transform: translate(-50%, -25%);
}
.header {
font-size: 2rem;
font-weight: 700;
}
.button {
margin: 0 0.5rem;
}
}
// scrolling-content
// used e.g. for <TaskDetailViewModal>
.scrolling .modal-content {
max-width: 1024px;
width: 100%;
margin: 4rem auto;
max-height: none; // reset bulma
overflow: visible; // reset bulma
@media screen and (min-width: $tablet) {
max-height: none; // reset bulma
margin: 4rem auto; // reset bulma
width: 100%;
}
@media screen and (max-width: $desktop) {
margin: 0;
}
}
.is-wide {
max-width: $desktop;
width: calc(100% - 2rem);
}
.hint-modal {
z-index: 4600;
::v-deep.card-content {
text-align: left;
.info {
font-style: italic;
}
p {
display: flex;
justify-content: space-between;
align-items: center;
}
.message-body {
padding: .5rem .75rem;
}
}
}
/* Transitions */
.modal-enter,
.modal-leave-active {
opacity: 0;
}
.modal-enter .modal-container,
.modal-leave-active .modal-container {
transform: scale(0.9);
}
</style>

View File

@ -50,28 +50,27 @@
<script>
import NotificationService from '@/services/notification'
import User from '@/components/misc/user.vue'
import names from '@/models/notificationNames.json'
import names from '@/models/constants/notificationNames.json'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {mapState} from 'vuex'
const LOAD_NOTIFICATIONS_INTERVAL = 10000
export default {
name: 'notifications',
components: {User},
data() {
return {
notificationService: NotificationService,
notificationService: new NotificationService(),
allNotifications: [],
showNotifications: false,
interval: null,
}
},
created() {
this.notificationService = new NotificationService()
},
mounted() {
this.loadNotifications()
document.addEventListener('click', this.hidePopup)
this.interval = setInterval(this.loadNotifications, 10000)
this.interval = setInterval(this.loadNotifications, LOAD_NOTIFICATIONS_INTERVAL)
},
beforeDestroy() {
document.removeEventListener('click', this.hidePopup)
@ -100,7 +99,7 @@ export default {
this.$set(this, 'allNotifications', r)
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
to(n, index) {
@ -138,7 +137,7 @@ export default {
.then(r => {
this.$set(this.allNotifications, index, r)
})
.catch(e => this.error(e))
.catch(e => this.$message.error(e))
}
},
},

View File

@ -90,11 +90,11 @@ export default {
foundTasks: [],
taskSearchTimeout: null,
taskService: null,
taskService: new TaskService(),
foundTeams: [],
teamSearchTimeout: null,
teamService: null,
teamService: new TeamService(),
}
},
mixins: [
@ -261,10 +261,6 @@ export default {
return this.selectedCmd !== null && this.selectedCmd.action === CMD_NEW_TASK
},
},
created() {
this.taskService = new TaskService()
this.teamService = new TeamService()
},
methods: {
search() {
this.searchTasks()
@ -358,6 +354,11 @@ export default {
}
},
doCmd() {
if (this.results.length === 1 && this.results[0].items.length === 1) {
this.doAction(this.results[0].type, this.results[0].items[0])
return
}
if (this.selectedCmd === null) {
return
}
@ -388,12 +389,12 @@ export default {
this.createNewTask(this.query, 0, this.currentList.id)
.then(r => {
this.success({message: this.$t('task.createSuccess')})
this.$message.success({message: this.$t('task.createSuccess')})
this.$router.push({name: 'task.detail', params: {id: r.id}})
this.closeQuickActions()
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
newList() {
@ -407,12 +408,12 @@ export default {
})
this.$store.dispatch('lists/createList', newList)
.then(r => {
this.success({message: this.$t('list.create.createdSuccess')})
this.$message.success({message: this.$t('list.create.createdSuccess')})
this.$router.push({name: 'list.index', params: {listId: r.id}})
this.closeQuickActions()
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
newNamespace() {
@ -420,11 +421,11 @@ export default {
this.$store.dispatch('namespaces/createNamespace', newNamespace)
.then(() => {
this.success({message: this.$t('namespace.create.success')})
this.$message.success({message: this.$t('namespace.create.success')})
this.closeQuickActions()
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
newTeam() {
@ -435,11 +436,11 @@ export default {
name: 'teams.edit',
params: {id: r.id},
})
this.success({message: this.$t('team.create.success')})
this.$message.success({message: this.$t('team.create.success')})
this.closeQuickActions()
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
select(parentIndex, index) {
@ -485,3 +486,20 @@ export default {
},
}
</script>
<style lang="scss" scoped>
.quick-actions {
// FIXME: changed position should be an option of the modal
::v-deep.modal-content {
top: 3rem;
transform: translate(-50%, 0);
}
}
// HACK:
// FIXME:
.modal-container-smaller ::v-deep.hint-modal .modal-container {
height: calc(100vh - 5rem);
}
</style>

View File

@ -163,17 +163,20 @@
@submit="remove()"
v-if="showDeleteModal"
>
<span slot="header">{{ $t('list.share.links.remove') }}</span>
<p slot="text">
{{ $t('list.share.links.removeText') }}
</p>
<template #header>
<span>{{ $t('list.share.links.remove') }}</span>
</template>
<template #text>
<p>{{ $t('list.share.links.removeText') }}</p>
</template>
</modal>
</transition>
</div>
</template>
<script>
import rights from '../../models/rights'
import rights from '../../models/constants/rights'
import LinkShareService from '../../services/linkShare'
import LinkShareModel from '../../models/linkShare'
@ -192,8 +195,7 @@ export default {
data() {
return {
linkShares: [],
linkShareService: LinkShareService,
newLinkShare: LinkShareModel,
linkShareService: new LinkShareService(),
rights: rights,
selectedRight: rights.READ,
name: '',
@ -203,17 +205,10 @@ export default {
showNewForm: false,
}
},
beforeMount() {
this.linkShareService = new LinkShareService()
},
created() {
this.linkShareService = new LinkShareService()
this.load()
},
watch: {
listId() {
// watch it
this.load()
listId: {
handler: 'load',
immediate: true,
},
},
computed: mapState({
@ -232,7 +227,7 @@ export default {
this.linkShares = r
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
add() {
@ -249,11 +244,11 @@ export default {
this.name = ''
this.password = ''
this.showNewForm = false
this.success({message: this.$t('list.share.links.createSuccess')})
this.$message.success({message: this.$t('list.share.links.createSuccess')})
this.load()
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
remove() {
@ -264,19 +259,17 @@ export default {
this.linkShareService
.delete(linkshare)
.then(() => {
this.success({message: this.$t('list.share.links.deleteSuccess')})
this.$message.success({message: this.$t('list.share.links.deleteSuccess')})
this.load()
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
.finally(() => {
this.showDeleteModal = false
})
},
copy(text) {
copy(text)
},
copy,
getShareLink(hash) {
return this.frontendUrl + 'share/' + hash + '/auth'
},

View File

@ -119,12 +119,12 @@
@submit="deleteSharable()"
v-if="showDeleteModal"
>
<span slot="header">
{{ $t('list.share.userTeam.removeHeader', {type: shareTypeName, sharable: sharableName}) }}
</span>
<p slot="text">
{{ $t('list.share.userTeam.removeText', {type: shareTypeName, sharable: sharableName}) }}
</p>
<template #header>
<span>{{ $t('list.share.userTeam.removeHeader', {type: shareTypeName, sharable: sharableName}) }}</span>
</template>
<template #text>
<p>{{ $t('list.share.userTeam.removeText', {type: shareTypeName, sharable: sharableName}) }}</p>
</template>
</modal>
</transition>
</div>
@ -145,7 +145,7 @@ import TeamListService from '../../services/teamList'
import TeamService from '../../services/team'
import TeamModel from '../../models/team'
import rights from '../../models/rights'
import rights from '../../models/constants/rights.json'
import Multiselect from '@/components/input/multiselect.vue'
import Nothing from '@/components/misc/nothing.vue'
@ -282,7 +282,7 @@ export default {
)
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
deleteSharable() {
@ -304,10 +304,10 @@ export default {
this.sharables.splice(i, 1)
}
}
this.success({message: this.$t('list.share.userTeam.removeSuccess', {type: this.shareTypeName, sharable: this.sharableName})})
this.$message.success({message: this.$t('list.share.userTeam.removeSuccess', {type: this.shareTypeName, sharable: this.sharableName})})
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
add(admin) {
@ -328,11 +328,11 @@ export default {
this.stuffService
.create(this.stuffModel)
.then(() => {
this.success({message: this.$t('list.share.userTeam.addedSuccess', {type: this.shareTypeName})})
this.$message.success({message: this.$t('list.share.userTeam.addedSuccess', {type: this.shareTypeName})})
this.load()
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
toggleType(sharable) {
@ -365,10 +365,10 @@ export default {
this.$set(this.sharables[i], 'right', r.right)
}
}
this.success({message: this.$t('list.share.userTeam.updatedSuccess', {type: this.shareTypeName})})
this.$message.success({message: this.$t('list.share.userTeam.updatedSuccess', {type: this.shareTypeName})})
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
find(query) {
@ -383,7 +383,7 @@ export default {
this.$set(this, 'found', response)
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
clearAll() {

View File

@ -2,16 +2,17 @@
<div class="task-add">
<div class="field is-grouped">
<p class="control has-icons-left is-expanded">
<input
:disabled="taskService.loading"
@keyup.enter="addTask()"
<textarea
:disabled="taskService.loading || null"
class="input"
:placeholder="$t('list.list.addPlaceholder')"
type="text"
v-focus
v-model="newTaskTitle"
ref="newTaskInput"
:style="{'height': `${textAreaHeight}px`}"
@keyup="errorMessage = ''"
@keydown.enter="handleEnter"
/>
<span class="icon is-small is-left">
<icon icon="tasks"/>
@ -19,7 +20,7 @@
</p>
<p class="control">
<x-button
:disabled="newTaskTitle === '' || taskService.loading"
:disabled="newTaskTitle === '' || taskService.loading || null"
@click="addTask()"
icon="plus"
:loading="taskService.loading"
@ -40,13 +41,20 @@ import TaskService from '../../services/task'
import createTask from '@/components/tasks/mixins/createTask'
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
const INITIAL_SCROLL_HEIGHT = 40
const cleanupTitle = title => {
return title.replace(/^((\* |\+ |- )(\[ \] )?)/g, '')
}
export default {
name: 'add-task',
data() {
return {
newTaskTitle: '',
taskService: TaskService,
taskService: new TaskService(),
errorMessage: '',
textAreaHeight: INITIAL_SCROLL_HEIGHT,
}
},
mixins: [
@ -55,15 +63,22 @@ export default {
components: {
QuickAddMagic,
},
created() {
this.taskService = new TaskService()
},
props: {
defaultPosition: {
type: Number,
required: false,
},
},
watch: {
newTaskTitle(newVal) {
let scrollHeight = this.$refs.newTaskInput.scrollHeight
if (scrollHeight < INITIAL_SCROLL_HEIGHT || newVal === '') {
scrollHeight = INITIAL_SCROLL_HEIGHT
}
this.textAreaHeight = scrollHeight
},
},
methods: {
addTask() {
if (this.newTaskTitle === '') {
@ -76,19 +91,44 @@ export default {
return
}
this.createNewTask(this.newTaskTitle, 0, this.$store.state.auth.settings.defaultListId, this.defaultPosition)
.then(task => {
const newTasks = []
this.newTaskTitle.split(/[\r\n]+/).forEach(t => {
const title = cleanupTitle(t)
if (title === '') {
return
}
newTasks.push(
this.createNewTask(title, 0, this.$store.state.auth.settings.defaultListId, this.defaultPosition)
.then(task => {
this.$emit('taskAdded', task)
return task
}),
)
})
Promise.all(newTasks)
.then(() => {
this.newTaskTitle = ''
this.$emit('taskAdded', task)
})
.catch(e => {
if (e === 'NO_LIST') {
this.errorMessage = this.$t('list.create.addListRequired')
return
}
this.error(e)
this.$message.error(e)
})
},
handleEnter(e) {
// when pressing shift + enter we want to continue as we normally would. Otherwise, we want to create
// the new task(s). The vue event modifier don't allow this, hence this method.
if (e.shiftKey) {
return
}
e.preventDefault()
this.addTask()
},
},
}
</script>
@ -101,4 +141,8 @@ export default {
height: 2.5rem;
}
}
.input, .textarea {
transition: border-color $transition;
}
</style>

View File

@ -5,7 +5,7 @@
<div class="control">
<input
:class="{ disabled: taskService.loading }"
:disabled="taskService.loading"
:disabled="taskService.loading || null"
@change="editTaskSubmit()"
class="input"
id="tasktext"
@ -72,7 +72,7 @@
import ListService from '../../services/list'
import TaskService from '../../services/task'
import TaskModel from '../../models/task'
import priorities from '../../models/priorities'
import priorities from '../../models/constants/priorities'
import EditLabels from './partials/editLabels'
import Reminders from './partials/reminders'
import ColorPicker from '../input/colorPicker'
@ -84,13 +84,13 @@ export default {
data() {
return {
listId: this.$route.params.id,
listService: ListService,
taskService: TaskService,
listService: new ListService(),
taskService: new TaskService(),
priorities: priorities,
list: {},
editorActive: false,
newTask: TaskModel,
newTask: new TaskModel(),
isTaskEdit: false,
taskEditTask: TaskModel,
}
@ -113,18 +113,14 @@ export default {
},
},
watch: {
task() {
this.taskEditTask = this.task
this.initTaskFields()
task: {
handler() {
this.taskEditTask = this.task
this.initTaskFields()
},
immediate: true,
},
},
created() {
this.listService = new ListService()
this.taskService = new TaskService()
this.newTask = new TaskModel()
this.taskEditTask = this.task
this.initTaskFields()
},
methods: {
initTaskFields() {
this.taskEditTask.dueDate =
@ -149,10 +145,10 @@ export default {
.then((r) => {
this.$set(this, 'taskEditTask', r)
this.initTaskFields()
this.success({message: this.$t('task.detail.updateSuccess')})
this.$message.success({message: this.$t('task.detail.updateSuccess')})
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
},

View File

@ -11,7 +11,7 @@
</x-button>
</div>
<filter-popup
@change="loadTasks"
@change="loadTasks()"
:visible="showTaskFilter"
v-model="params"
/>
@ -24,10 +24,7 @@
class="month"
v-for="(m, mk) in days[yk]"
>
{{
new Date(new Date(`${yk}-${parseInt(mk) + 1}-01`)).toLocaleString('en-us', {month: 'long'})
}},
{{ new Date(yk).getFullYear() }}
{{ formatYear(new Date(`${yk}-${parseInt(mk) + 1}-01`)) }}
<div class="days">
<div
:class="{ today: d.toDateString() === now.toDateString() }"
@ -191,12 +188,13 @@ import EditTask from './edit-task'
import TaskService from '../../services/task'
import TaskModel from '../../models/task'
import priorities from '../../models/priorities'
import priorities from '../../models/constants/priorities'
import PriorityLabel from './partials/priorityLabel'
import TaskCollectionService from '../../services/taskCollection'
import {mapState} from 'vuex'
import Rights from '../../models/rights.json'
import Rights from '../../models/constants/rights.json'
import FilterPopup from '@/components/list/partials/filter-popup.vue'
import {format} from 'date-fns'
export default {
name: 'GanttChart',
@ -234,17 +232,17 @@ export default {
endDate: null,
theTasks: [], // Pretty much a copy of the prop, since we cant mutate the prop directly
tasksWithoutDates: [],
taskService: TaskService,
taskService: new TaskService(),
taskDragged: null, // Saves to currently dragged task to be able to update it
fullWidth: 0,
now: null,
now: new Date(),
dayOffsetUntilToday: 0,
isTaskEdit: false,
taskToEdit: null,
newTaskTitle: '',
newTaskFieldActive: false,
priorities: {},
taskCollectionService: TaskCollectionService,
priorities: priorities,
taskCollectionService: new TaskCollectionService(),
showTaskFilter: false,
params: {
@ -262,12 +260,6 @@ export default {
dateTo: 'buildTheGanttChart',
listId: 'parseTasks',
},
created() {
this.now = new Date()
this.taskCollectionService = new TaskCollectionService()
this.taskService = new TaskService()
this.priorities = priorities
},
mounted() {
this.buildTheGanttChart()
},
@ -356,7 +348,7 @@ export default {
})
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
addGantAttributes(t) {
@ -441,7 +433,7 @@ export default {
}
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
editTask(task) {
@ -478,9 +470,12 @@ export default {
this.hideCrateNewTask()
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
formatYear(date) {
return format(date, 'MMMM, yyyy')
},
},
}
</script>

View File

@ -1,39 +0,0 @@
import AttachmentModel from '../../../models/attachment'
import AttachmentService from '../../../services/attachment'
import {generateAttachmentUrl} from '@/helpers/generateAttachmentUrl'
export default {
methods: {
attachmentUpload(file, onSuccess) {
const files = [file]
const attachmentService = new AttachmentService()
this.createAttachment(attachmentService, files, onSuccess)
},
createAttachment(attachmentService, files, onSuccess = () => {}) {
const attachmentModel = new AttachmentModel({taskId: this.taskId})
attachmentService.create(attachmentModel, files)
.then(r => {
console.debug(`Uploaded attachments for task ${this.taskId}, response was`, r)
if (r.success !== null) {
r.success.forEach(a => {
this.$store.dispatch('tasks/addTaskAttachment', {
taskId: this.taskId,
attachment: a,
})
onSuccess(generateAttachmentUrl(this.taskId, a.id))
})
}
if (r.errors !== null) {
r.errors.forEach(m => {
this.error(m)
})
}
})
.catch(e => {
this.error(e)
})
},
},
}

View File

@ -7,6 +7,7 @@ import LabelTaskService from '@/services/labelTask'
import {mapState} from 'vuex'
import UserService from '@/services/user'
import TaskService from '@/services/task'
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
export default {
data() {
@ -26,7 +27,7 @@ export default {
}),
methods: {
createNewTask(newTaskTitle, bucketId = 0, lId = 0, position = 0) {
const parsedTask = parseTaskText(newTaskTitle)
const parsedTask = parseTaskText(newTaskTitle, getQuickAddMagicMode())
const assignees = []
// Uses the following ways to get the list id of the new task:

View File

@ -1,6 +1,15 @@
import TaskCollectionService from '../../../services/taskCollection'
import TaskCollectionService from '@/services/taskCollection'
import cloneDeep from 'lodash/cloneDeep'
import {calculateItemPosition} from '../../../helpers/calculateItemPosition'
// FIXME: merge with DEFAULT_PARAMS in filters.vue
const DEFAULT_PARAMS = {
sort_by: ['position', 'id'],
order_by: ['asc', 'desc'],
filter_by: ['done'],
filter_value: ['false'],
filter_comparator: ['equals'],
filter_concat: 'and',
}
/**
* This mixin provides a base set of methods and properties to get tasks on a list.
@ -8,40 +17,27 @@ import {calculateItemPosition} from '../../../helpers/calculateItemPosition'
export default {
data() {
return {
taskCollectionService: TaskCollectionService,
taskCollectionService: new TaskCollectionService(),
tasks: [],
pages: [],
currentPage: 0,
loadedList: null,
showTaskSearch: false,
searchTerm: '',
showTaskFilter: false,
params: {
sort_by: ['position', 'id'],
order_by: ['asc', 'desc'],
filter_by: ['done'],
filter_value: ['false'],
filter_comparator: ['equals'],
filter_concat: 'and',
},
params: DEFAULT_PARAMS,
}
},
watch: {
'$route.query': 'loadTasksForPage', // Only listen for query path changes
// Only listen for query path changes
'$route.query': {
handler: 'loadTasksForPage',
immediate: true,
},
'$route.path': 'loadTasksOnSavedFilter',
},
beforeMount() {
// Triggering loading the tasks in beforeMount lets the component maintain the current page, therefore the page
// is not lost after navigating back from a task detail page for example.
this.loadTasksForPage(this.$route.query)
},
created() {
this.taskCollectionService = new TaskCollectionService()
},
methods: {
loadTasks(
page,
@ -72,56 +68,28 @@ export default {
const currentList = {
id: list.listId,
params: params,
search: search,
page: page,
params,
search,
page,
}
if (JSON.stringify(currentList) === JSON.stringify(this.loadedList) && !forceLoading) {
return
}
this.$set(this, 'tasks', [])
this.tasks = []
this.taskCollectionService.getAll(list, params, page)
.then(r => {
this.$set(this, 'tasks', r)
this.$set(this, 'pages', [])
this.tasks = r
this.currentPage = page
for (let i = 0; i < this.taskCollectionService.totalPages; i++) {
// Show ellipsis instead of all pages
if (
i > 0 && // Always at least the first page
(i + 1) < this.taskCollectionService.totalPages && // And the last page
(
// And the current with current + 1 and current - 1
(i + 1) > this.currentPage + 1 ||
(i + 1) < this.currentPage - 1
)
) {
// Only add an ellipsis if the last page isn't already one
if (this.pages[i - 1] && !this.pages[i - 1].isEllipsis) {
this.pages.push({
number: 0,
isEllipsis: true,
})
}
continue
}
this.pages.push({
number: i + 1,
isEllipsis: false,
})
}
this.loadedList = cloneDeep(currentList)
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
loadTasksForPage(e) {
// The page parameter can be undefined, in the case where the user loads a new list from the side bar menu
let page = Number(e.page)
@ -139,71 +107,5 @@ export default {
this.loadTasks(1, '', null, true)
}
},
sortTasks() {
if (this.tasks === null || this.tasks === []) {
return
}
return this.tasks.sort(function (a, b) {
if (a.done < b.done)
return -1
if (a.done > b.done)
return 1
if (a.position < b.position)
return -1
if (a.position > b.position)
return 1
return 0
})
},
searchTasks() {
// Only search if the search term changed
if (this.$route.query === this.searchTerm) {
return
}
this.$router.push({
name: 'list.list',
query: {search: this.searchTerm},
})
},
hideSearchBar() {
// This is a workaround.
// When clicking on the search button, @blur from the input is fired. If we
// would then directly hide the whole search bar directly, no click event
// from the button gets fired. To prevent this, we wait 200ms until we hide
// everything so the button has a chance of firering the search event.
setTimeout(() => {
this.showTaskSearch = false
}, 200)
},
getRouteForPagination(page = 1, type = 'list') {
return {
name: 'list.' + type,
params: {
type: type,
},
query: {
page: page,
},
}
},
saveTaskPosition(e) {
this.drag = false
const task = this.tasks[e.newIndex]
const taskBefore = this.tasks[e.newIndex - 1] ?? null
const taskAfter = this.tasks[e.newIndex + 1] ?? null
task.position = calculateItemPosition(taskBefore !== null ? taskBefore.position : null, taskAfter !== null ? taskAfter.position : null)
this.$store.dispatch('tasks/update', task)
.then(r => {
this.$set(this.tasks, e.newIndex, r)
})
.catch(e => {
this.error(e)
})
},
},
}

View File

@ -8,7 +8,7 @@
</h3>
<input
:disabled="attachmentService.loading"
:disabled="attachmentService.loading || null"
@change="uploadNewAttachment()"
id="files"
multiple
@ -110,11 +110,12 @@
v-if="showDeleteModal"
@submit="deleteAttachment()"
>
<span slot="header">{{ $t('task.attachment.delete') }}</span>
<p slot="text">
{{ $t('task.attachment.deleteText1', {filename: attachmentToDelete.file.name}) }}<br/>
<strong>{{ $t('task.attachment.deleteText2') }}</strong>
</p>
<template #header><span>{{ $t('task.attachment.delete') }}</span></template>
<template #text>
<p>{{ $t('task.attachment.deleteText1', {filename: attachmentToDelete.file.name}) }}<br/>
<strong>{{ $t('task.attachment.deleteText2') }}</strong></p>
</template>
</modal>
</transition>
@ -138,22 +139,19 @@
import AttachmentService from '../../../services/attachment'
import AttachmentModel from '../../../models/attachment'
import User from '../../misc/user'
import attachmentUpload from '@/components/tasks/mixins/attachmentUpload'
import {generateAttachmentUrl} from '@/helpers/generateAttachmentUrl'
import {mapState} from 'vuex'
import copy from 'copy-to-clipboard'
import { uploadFiles, generateAttachmentUrl } from '@/helpers/attachments'
export default {
name: 'attachments',
components: {
User,
},
mixins: [
attachmentUpload,
],
data() {
return {
attachmentService: AttachmentService,
attachmentService: new AttachmentService(),
showDropzone: false,
showDeleteModal: false,
@ -175,9 +173,6 @@ export default {
default: true,
},
},
created() {
this.attachmentService = new AttachmentService()
},
computed: mapState({
attachments: (state) => state.attachments.attachments,
}),
@ -221,7 +216,7 @@ export default {
this.uploadFiles(this.$refs.files.files)
},
uploadFiles(files) {
this.createAttachment(this.attachmentService, files)
uploadFiles(this.attachmentService, this.taskId, files)
},
deleteAttachment() {
this.attachmentService
@ -231,10 +226,10 @@ export default {
'attachments/removeById',
this.attachmentToDelete.id,
)
this.success(r)
this.$message.success(r)
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
.finally(() => {
this.showDeleteModal = false

View File

@ -0,0 +1,60 @@
<template>
<span v-if="checklist.total > 0" class="checklist-summary">
<svg width="12" height="12">
<circle stroke-width="2" fill="transparent" cx="50%" cy="50%" r="5"></circle>
<circle stroke-width="2" stroke-dasharray="31" :stroke-dashoffset="checklistCircleDone"
stroke-linecap="round" fill="transparent" cx="50%" cy="50%" r="5"></circle>
</svg>
<span>
{{ $t(checklist.total === checklist.checked ? 'task.checklistAllDone' : 'task.checklistTotal', checklist) }}
</span>
</span>
</template>
<script>
import {getChecklistStatistics} from '../../../helpers/checklistFromText'
export default {
name: 'checklist-summary',
props: {
task: {
required: true,
},
},
computed: {
checklist() {
return getChecklistStatistics(this.task.description)
},
checklistCircleDone() {
const r = 5
const c = Math.PI * (r * 2)
const progress = this.checklist.checked / this.checklist.total * 100
return ((100 - progress) / 100) * c
},
},
}
</script>
<style scoped lang="scss">
.checklist-summary {
color: $grey-500;
display: inline-flex;
align-items: center;
svg {
transform: rotate(-90deg);
transition: stroke-dashoffset 0.35s;
margin-right: .25rem;
circle {
stroke: $grey-400;
&:last-child {
stroke: $primary;
}
}
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div class="content details">
<div class="content details" v-if="enabled">
<h3 v-if="canWrite || comments.length > 0">
<span class="icon is-grey">
<icon :icon="['far', 'comments']"/>
@ -56,7 +56,7 @@
</span>
<span
class="has-text-success"
v-if="
v-else-if="
!taskCommentService.loading &&
saved === c.id
"
@ -138,11 +138,14 @@
@submit="deleteComment()"
v-if="showDeleteModal"
>
<span slot="header">{{ $t('task.comment.delete') }}</span>
<p slot="text">
{{ $t('task.comment.deleteText1') }}<br/>
<strong>{{ $t('task.comment.deleteText2') }}</strong>
</p>
<template #header><span>{{ $t('task.comment.delete') }}</span></template>
<template #text>
<p>
{{ $t('task.comment.deleteText1') }}<br/>
<strong>{{ $t('task.comment.deleteText2') }}</strong>
</p>
</template>
</modal>
</transition>
</div>
@ -151,9 +154,10 @@
<script>
import TaskCommentService from '../../../services/taskComment'
import TaskCommentModel from '../../../models/taskComment'
import attachmentUpload from '../mixins/attachmentUpload'
import LoadingComponent from '../../misc/loading'
import ErrorComponent from '../../misc/error'
import {uploadFile} from '@/helpers/attachments'
import {mapState} from 'vuex'
export default {
name: 'comments',
@ -165,7 +169,6 @@ export default {
timeout: 60000,
}),
},
mixins: [attachmentUpload],
props: {
taskId: {
type: Number,
@ -180,13 +183,13 @@ export default {
comments: [],
showDeleteModal: false,
commentToDelete: TaskCommentModel,
commentToDelete: new TaskCommentModel(),
isCommentEdit: false,
commentEdit: TaskCommentModel,
commentEdit: new TaskCommentModel(),
taskCommentService: TaskCommentService,
newComment: TaskCommentModel,
taskCommentService: new TaskCommentService(),
newComment: new TaskCommentModel(),
editorActive: true,
actions: {},
@ -195,33 +198,33 @@ export default {
creating: false,
}
},
created() {
this.taskCommentService = new TaskCommentService()
this.newComment = new TaskCommentModel({taskId: this.taskId})
this.commentEdit = new TaskCommentModel({taskId: this.taskId})
this.commentToDelete = new TaskCommentModel({taskId: this.taskId})
this.comments = []
},
mounted() {
this.loadComments()
},
watch: {
taskId() {
this.loadComments()
this.newComment.taskId = this.taskId
this.commentEdit.taskId = this.taskId
this.commentToDelete.taskId = this.taskId
taskId: {
handler(taskId) {
if (!this.enabled) {
return
}
this.loadComments()
this.newComment.taskId = taskId
this.commentEdit.taskId = taskId
this.commentToDelete.taskId = taskId
},
immediate: true,
},
canWrite() {
this.makeActions()
},
},
computed: {
userAvatar() {
return this.$store.state.auth.info.getAvatarUrl(48)
},
},
computed: mapState({
userAvatar: state => state.auth.info.getAvatarUrl(48),
enabled: state => state.config.taskCommentsEnabled,
}),
methods: {
attachmentUpload(...args) {
return uploadFile(this.taskId, ...args)
},
loadComments() {
this.taskCommentService
.getAll({taskId: this.taskId})
@ -230,7 +233,7 @@ export default {
this.makeActions()
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
addComment() {
@ -252,11 +255,11 @@ export default {
.then((r) => {
this.comments.push(r)
this.newComment.comment = ''
this.success({message: this.$t('task.comment.addedSuccess')})
this.$message.success({message: this.$t('task.comment.addedSuccess')})
this.makeActions()
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
.finally(() => {
this.creating = false
@ -292,7 +295,7 @@ export default {
}, 2000)
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
.finally(() => {
this.isCommentEdit = false
@ -310,7 +313,7 @@ export default {
}
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
.finally(() => {
this.showDeleteModal = false

View File

@ -30,7 +30,7 @@
<flat-pickr
:class="{ disabled: taskService.loading }"
:config="flatPickerConfig"
:disabled="taskService.loading"
:disabled="taskService.loading || null"
class="input"
v-model="dueDate"
/>
@ -45,7 +45,7 @@ export default {
name: 'defer-task',
data() {
return {
taskService: TaskService,
taskService: new TaskService(),
task: null,
// We're saving the due date seperately to prevent null errors in very short periods where the task is null.
dueDate: null,
@ -61,14 +61,17 @@ export default {
required: true,
},
},
created() {
this.taskService = new TaskService()
watch: {
value: {
handler(value) {
this.task = value
this.dueDate = value.dueDate
this.lastValue = value.dueDate
},
immediate: true,
},
},
mounted() {
this.task = this.value
this.dueDate = this.task.dueDate
this.lastValue = this.dueDate
// Because we don't really have other ways of handling change since if we let flatpickr
// change events trigger updates, it would trigger a flatpickr change event which would trigger
// an update which would trigger a change event and so on...
@ -86,13 +89,6 @@ export default {
}
this.updateDueDate()
},
watch: {
value(newVal) {
this.task = newVal
this.dueDate = this.task.dueDate
this.lastValue = this.dueDate
},
},
computed: {
flatPickerConfig() {
return {
@ -132,7 +128,7 @@ export default {
this.$emit('input', r)
})
.catch((e) => {
this.error(e)
this.$message.error(e)
})
},
},

View File

@ -10,7 +10,7 @@
<span class="loader is-inline-block mr-2"></span>
{{ $t('misc.saving') }}
</span>
<span class="is-small has-text-success" v-if="!loading && saved">
<span class="is-small has-text-success" v-else-if="!loading && saved">
<icon icon="check"/>
{{ $t('misc.saved') }}
</span>
@ -68,13 +68,13 @@ export default {
},
},
watch: {
value(newVal) {
this.task = newVal
value: {
handler(value) {
this.task = value
},
immediate: true,
},
},
mounted() {
this.task = this.value
},
methods: {
save() {
this.saving = true
@ -89,7 +89,7 @@ export default {
}, 2000)
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
.finally(() => {
this.saving = false

View File

@ -16,7 +16,7 @@
v-model="assignees"
ref="multiselect"
>
<template v-slot:tag="props">
<template #tag="props">
<span class="assignee">
<user :avatar-size="32" :show-username="false" :user="props.item"/>
<a @click="removeAssignee(props.item)" class="remove-assignee" v-if="!disabled">
@ -61,22 +61,19 @@ export default {
},
data() {
return {
newAssignee: UserModel,
listUserService: ListUserService,
newAssignee: new UserModel(),
listUserService: new ListUserService(),
foundUsers: [],
assignees: [],
taskAssigneeService: TaskAssigneeService,
taskAssigneeService: new TaskAssigneeService(),
}
},
created() {
this.assignees = this.value
this.listUserService = new ListUserService()
this.newAssignee = new UserModel()
this.taskAssigneeService = new TaskAssigneeService()
},
watch: {
value(newVal) {
this.assignees = newVal
value: {
handler(value) {
this.assignees = value
},
immediate: true,
},
},
methods: {
@ -84,10 +81,10 @@ export default {
this.$store.dispatch('tasks/addAssignee', {user: user, taskId: this.taskId})
.then(() => {
this.$emit('input', this.assignees)
this.success({message: this.$t('task.assignee.assignSuccess')})
this.$message.success({message: this.$t('task.assignee.assignSuccess')})
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
removeAssignee(user) {
@ -99,10 +96,10 @@ export default {
this.assignees.splice(a, 1)
}
}
this.success({message: this.$t('task.assignee.assignSuccess')})
this.$message.success({message: this.$t('task.assignee.assignSuccess')})
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
findUser(query) {
@ -119,7 +116,7 @@ export default {
}))
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
clearAllFoundUsers() {

View File

@ -13,7 +13,7 @@
v-model="labels"
:search-delay="10"
>
<template v-slot:tag="props">
<template #tag="props">
<span
:style="{'background': props.item.hexColor, 'color': props.item.textColor}"
class="tag">
@ -21,7 +21,7 @@
<a @click="removeLabel(props.item)" class="delete is-small"></a>
</span>
</template>
<template v-slot:searchResult="props">
<template #searchResult="props">
<span
v-if="typeof props.option === 'string'"
class="tag">
@ -64,7 +64,7 @@ export default {
},
data() {
return {
labelTaskService: LabelTaskService,
labelTaskService: new LabelTaskService(),
labelTimeout: null,
labels: [],
query: '',
@ -74,14 +74,13 @@ export default {
Multiselect,
},
watch: {
value(newLabels) {
this.labels = newLabels
value: {
handler(value) {
this.labels = value
},
immediate: true,
},
},
created() {
this.labelTaskService = new LabelTaskService()
this.labels = this.value
},
computed: {
foundLabels() {
const labels = (Object.values(this.$store.state.labels.labels).filter(l => {
@ -115,11 +114,11 @@ export default {
.then(() => {
bubble()
if (showNotification) {
this.success({message: this.$t('task.label.addSuccess')})
this.$message.success({message: this.$t('task.label.addSuccess')})
}
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
removeLabel(label) {
@ -141,10 +140,10 @@ export default {
this.$store.dispatch('tasks/removeLabel', {label: label, taskId: this.taskId})
.then(() => {
removeFromState()
this.success({message: this.$t('task.label.removeSuccess')})
this.$message.success({message: this.$t('task.label.removeSuccess')})
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
createAndAddLabel(title) {
@ -157,10 +156,10 @@ export default {
.then(r => {
this.addLabel(r, false)
this.labels.push(r)
this.success({message: this.$t('task.label.removeSuccess')})
this.$message.success({message: this.$t('task.label.removeSuccess')})
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},

View File

@ -1,22 +1,23 @@
<template>
<div class="heading">
<h1 class="title task-id">
{{ task.getTextIdentifier && task.getTextIdentifier() ? task.getTextIdentifier() : '' }}
</h1>
<h1 class="title task-id">{{ textIdentifier }}</h1>
<div class="is-done" v-if="task.done">Done</div>
<h1
class="title input"
:class="{'disabled': !canWrite}"
@focusout="save()"
@keydown.enter.prevent.stop="save()"
@blur="save($event.target.textContent)"
@keydown.enter.prevent.stop="$event.target.blur()"
:contenteditable="canWrite ? 'true' : 'false'"
ref="taskTitle">{{ task.title.trim() }}</h1>
spellcheck="false"
>
{{ task.title.trim() }}
</h1>
<transition name="fade">
<span class="is-inline-flex is-align-items-center" v-if="loading && saving">
<span class="loader is-inline-block mr-2"></span>
{{ $t('misc.saving') }}
</span>
<span class="has-text-success is-inline-flex is-align-content-center" v-if="!loading && saved">
<span class="has-text-success is-inline-flex is-align-content-center" v-else-if="!loading && showSavedMessage">
<icon icon="check" class="mr-2"/>
{{ $t('misc.saved') }}
</span>
@ -25,22 +26,25 @@
</template>
<script>
import {LOADING} from '@/store/mutation-types'
import {mapState} from 'vuex'
export default {
name: 'heading',
data() {
return {
task: {title: '', identifier: '', index:''},
taskTitle: '',
saved: false,
showSavedMessage: false,
saving: false, // Since loading is global state, this variable ensures we're only showing the saving icon when saving the description.
}
},
computed: mapState({
loading: LOADING,
}),
computed: {
...mapState(['loading']),
task() {
return this.value
},
textIdentifier() {
return this.task?.getTextIdentifier() || ''
},
},
props: {
value: {
required: true,
@ -50,47 +54,33 @@ export default {
default: false,
},
},
watch: {
value(newVal) {
this.task = newVal
this.taskTitle = this.task.title
},
},
mounted() {
this.task = this.value
this.taskTitle = this.task.title
},
methods: {
save() {
this.$refs.taskTitle.spellcheck = false
// Pull the task title from the contenteditable
let taskTitle = this.$refs.taskTitle.textContent
this.task.title = taskTitle
// We only want to save if the title was actually change.
// Because the contenteditable does not have a change event,
// we're building it ourselves and only calling saveTask()
save(title) {
// We only want to save if the title was actually changed.
// Because the contenteditable does not have a change event
// we're building it ourselves and only continue
// if the task title changed.
if (this.task.title !== this.taskTitle) {
this.$refs.taskTitle.blur()
this.saveTask()
this.taskTitle = taskTitle
if (title === this.task.title) {
return
}
},
saveTask() {
this.saving = true
this.$store.dispatch('tasks/update', this.task)
.then(() => {
this.$emit('input', this.task)
this.saved = true
const newTask = {
...this.task,
title,
}
this.$store.dispatch('tasks/update', newTask)
.then((task) => {
this.$emit('input', task)
this.showSavedMessage = true
setTimeout(() => {
this.saved = false
this.showSavedMessage = false
}, 2000)
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
.finally(() => {
this.saving = false

View File

@ -92,15 +92,17 @@ export default {
methods: {
markTaskAsDone(task) {
this.loadingInternal = true
task.done = !task.done
this.$store.dispatch('tasks/update', task)
this.$store.dispatch('tasks/update', {
...task,
done: !task.done,
})
.then(() => {
if (task.done) {
playPop()
}
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
.finally(() => {
this.loadingInternal = false

View File

@ -10,7 +10,7 @@
v-model="list"
:select-placeholder="$t('list.searchSelect')"
>
<template v-slot:searchResult="props">
<template #searchResult="props">
<span class="list-namespace-title search-result">{{ namespace(props.option.namespaceId) }} ></span>
{{ props.option.title }}
</template>
@ -26,8 +26,8 @@ export default {
name: 'listSearch',
data() {
return {
listSerivce: ListService,
list: ListModel,
listSerivce: new ListService(),
list: new ListModel(),
foundLists: [],
}
},
@ -39,18 +39,14 @@ export default {
components: {
Multiselect,
},
beforeMount() {
this.listSerivce = new ListService()
this.list = new ListModel()
},
watch: {
value(newVal) {
this.list = newVal
value: {
handler(value) {
this.list = value
},
immeditate: true,
},
},
mounted() {
this.list = this.value
},
methods: {
findLists(query) {
if (query === '') {
@ -63,7 +59,7 @@ export default {
this.$set(this, 'foundLists', response)
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
clearAll() {

View File

@ -1,6 +1,6 @@
<template>
<div class="select">
<select :disabled="disabled" @change="updateData" v-model.number="percentDone">
<select :disabled="disabled || null" @change="updateData" v-model.number="percentDone">
<option value="0">0%</option>
<option value="0.1">10%</option>
<option value="0.2">20%</option>

View File

@ -21,7 +21,7 @@
</template>
<script>
import priorites from '../../../models/priorities'
import priorites from '../../../models/constants/priorities'
export default {
name: 'priorityLabel',

View File

@ -1,6 +1,6 @@
<template>
<div class="select">
<select :disabled="disabled" @change="updateData" v-model="priority">
<select :disabled="disabled || null" @change="updateData" v-model="priority">
<option :value="priorities.UNSET">{{ $t('task.priority.unset') }}</option>
<option :value="priorities.LOW">{{ $t('task.priority.low') }}</option>
<option :value="priorities.MEDIUM">{{ $t('task.priority.medium') }}</option>
@ -12,7 +12,7 @@
</template>
<script>
import priorites from '../../../models/priorities'
import priorites from '../../../models/constants/priorities'
export default {
name: 'prioritySelect',
@ -33,13 +33,13 @@ export default {
},
watch: {
// Set the priority to the :value every time it changes from the outside
value(newVal) {
this.priority = newVal
value: {
handler(value) {
this.priority = value
},
immediate: true,
},
},
mounted() {
this.priority = this.value
},
methods: {
updateData() {
this.$emit('input', this.priority)

View File

@ -1,82 +1,94 @@
<template>
<div>
<div v-if="available">
<p class="help has-text-grey">
{{ $t('task.quickAddMagic.hint') }}.
<a @click="() => visible = true">{{ $t('task.quickAddMagic.what') }}</a>
</p>
<transition name="fade">
<div class="modal-mask hint-modal" v-if="visible">
<div @click.self="() => visible = false" class="modal-container">
<div class="modal-content">
<card class="has-background-white has-no-shadow" :title="$t('task.quickAddMagic.title')">
<p>{{ $t('task.quickAddMagic.intro') }}</p>
<modal
@close="() => visible = false"
:enabled="visible"
transition-name="fade"
:overflow="true"
variant="hint-modal"
>
<card class="has-background-white has-no-shadow" :title="$t('task.quickAddMagic.title')">
<p>{{ $t('task.quickAddMagic.intro') }}</p>
<h3>{{ $t('task.attributes.labels') }}</h3>
<p>
{{ $t('task.quickAddMagic.label1', {prefix: '@'}) }}
{{ $t('task.quickAddMagic.label2') }}
{{ $t('task.quickAddMagic.multiple') }}
</p>
<p>
{{ $t('task.quickAddMagic.label3') }}
{{ $t('task.quickAddMagic.label4', {prefix: '@'}) }}
</p>
<h3>{{ $t('task.attributes.labels') }}</h3>
<p>
{{ $t('task.quickAddMagic.label1', {prefix: prefixes.label}) }}
{{ $t('task.quickAddMagic.label2') }}
{{ $t('task.quickAddMagic.multiple') }}
</p>
<p>
{{ $t('task.quickAddMagic.label3') }}
{{ $t('task.quickAddMagic.label4', {prefix: prefixes.label}) }}
</p>
<h3>{{ $t('task.attributes.priority') }}</h3>
<p>
{{ $t('task.quickAddMagic.priority1', {prefix: '!'}) }}
{{ $t('task.quickAddMagic.priority2') }}
</p>
<h3>{{ $t('task.attributes.priority') }}</h3>
<p>
{{ $t('task.quickAddMagic.priority1', {prefix: prefixes.priority}) }}
{{ $t('task.quickAddMagic.priority2') }}
</p>
<h3>{{ $t('task.attributes.assignees') }}</h3>
<p>
{{ $t('task.quickAddMagic.assignees') }}
{{ $t('task.quickAddMagic.multiple') }}
</p>
<h3>{{ $t('task.attributes.assignees') }}</h3>
<p>
{{ $t('task.quickAddMagic.assignees', {prefix: prefixes.assignee}) }}
{{ $t('task.quickAddMagic.multiple') }}
</p>
<h3>{{ $t('list.list.title') }}</h3>
<p>
{{ $t('task.quickAddMagic.list1', {prefix: '#'}) }}
{{ $t('task.quickAddMagic.list2') }}
</p>
<h3>{{ $t('list.list.title') }}</h3>
<p>
{{ $t('task.quickAddMagic.list1', {prefix: prefixes.list}) }}
{{ $t('task.quickAddMagic.list2') }}
</p>
<h3>{{ $t('task.quickAddMagic.dateAndTime') }}</h3>
<p>
{{ $t('task.quickAddMagic.date') }}
</p>
<ul>
<!-- Not localized because these only work in english -->
<li>Today</li>
<li>Tomorrow</li>
<li>Next monday</li>
<li>This weekend</li>
<li>Later this week</li>
<li>Later next week</li>
<li>Next week</li>
<li>Next month</li>
<li>End of month</li>
<li>In 5 days [hours/weeks/months]</li>
<li>Tuesday ({{ $t('task.quickAddMagic.dateWeekday') }})</li>
<li>17/02/2021</li>
<li>Feb 17 ({{ $t('task.quickAddMagic.dateCurrentYear') }})</li>
<li>17th ({{ $t('task.quickAddMagic.dateNth', {day: '17'}) }})</li>
</ul>
<p>{{ $t('task.quickAddMagic.dateTime', {time: 'at 17:00', timePM: '5pm'}) }}</p>
</card>
</div>
</div>
</div>
</transition>
<h3>{{ $t('task.quickAddMagic.dateAndTime') }}</h3>
<p>
{{ $t('task.quickAddMagic.date') }}
</p>
<ul>
<!-- Not localized because these only work in english -->
<li>Today</li>
<li>Tomorrow</li>
<li>Next monday</li>
<li>This weekend</li>
<li>Later this week</li>
<li>Later next week</li>
<li>Next week</li>
<li>Next month</li>
<li>End of month</li>
<li>In 5 days [hours/weeks/months]</li>
<li>Tuesday ({{ $t('task.quickAddMagic.dateWeekday') }})</li>
<li>17/02/2021</li>
<li>Feb 17 ({{ $t('task.quickAddMagic.dateCurrentYear') }})</li>
<li>17th ({{ $t('task.quickAddMagic.dateNth', {day: '17'}) }})</li>
</ul>
<p>{{ $t('task.quickAddMagic.dateTime', {time: 'at 17:00', timePM: '5pm'}) }}</p>
</card>
</modal>
</div>
</template>
<script>
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
import {PREFIXES} from '@/modules/parseTaskText'
export default {
name: 'quick-add-magic',
data() {
return {
visible: false,
mode: getQuickAddMagicMode(),
}
},
computed: {
available() {
return this.mode !== 'disabled'
},
prefixes() {
return PREFIXES[this.mode]
},
},
}
</script>

View File

@ -19,7 +19,7 @@
<span class="loader is-inline-block mr-2"></span>
{{ $t('misc.saving') }}
</span>
<span class="has-text-success" v-if="!taskRelationService.loading && saved">
<span class="has-text-success" v-else-if="!taskRelationService.loading && saved">
{{ $t('misc.saved') }}
</span>
</transition>
@ -36,7 +36,7 @@
:create-placeholder="$t('task.relation.createPlaceholder')"
@create="createAndRelateTask"
>
<template v-slot:searchResult="props">
<template #searchResult="props">
<span v-if="typeof props.option !== 'string'" class="search-result">
<span
class="different-list"
@ -107,12 +107,14 @@
<modal
@close="showDeleteModal = false"
@submit="removeTaskRelation()"
v-if="showDeleteModal">
<span slot="header">{{ $t('task.relation.delete') }}</span>
<p slot="text">
{{ $t('task.relation.deleteText1') }}<br/>
<strong>{{ $t('task.relation.deleteText2') }}</strong>
</p>
v-if="showDeleteModal"
>
<template #header><span>{{ $t('task.relation.delete') }}</span></template>
<template #text>
<p>{{ $t('task.relation.deleteText1') }}<br/>
<strong>{{ $t('task.relation.deleteText2') }}</strong></p>
</template>
</modal>
</transition>
</div>
@ -122,7 +124,7 @@
import TaskService from '../../../services/task'
import TaskModel from '../../../models/task'
import TaskRelationService from '../../../services/taskRelation'
import relationKinds from '../../../models/relationKinds'
import relationKinds from '../../../models/constants/relationKinds'
import TaskRelationModel from '../../../models/taskRelation'
import Multiselect from '@/components/input/multiselect.vue'
@ -132,12 +134,12 @@ export default {
data() {
return {
relatedTasks: {},
taskService: TaskService,
taskService: new TaskService(),
foundTasks: [],
relationKinds: relationKinds,
newTaskRelationTask: TaskModel,
newTaskRelationTask: new TaskModel(),
newTaskRelationKind: 'related',
taskRelationService: TaskRelationService,
taskRelationService: new TaskRelationService(),
showDeleteModal: false,
relationToDelete: {},
saved: false,
@ -169,19 +171,14 @@ export default {
default: true,
},
},
created() {
this.taskService = new TaskService()
this.taskRelationService = new TaskRelationService()
this.newTaskRelationTask = new TaskModel()
},
watch: {
initialRelatedTasks(newVal) {
this.relatedTasks = newVal
initialRelatedTasks: {
handler(value) {
this.relatedTasks = value
},
immediate: true,
},
},
mounted() {
this.relatedTasks = this.initialRelatedTasks
},
computed: {
showCreate() {
return Object.keys(this.relatedTasks).length === 0 || this.showNewRelationForm
@ -194,7 +191,7 @@ export default {
this.$set(this, 'foundTasks', response)
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
addTaskRelation() {
@ -217,7 +214,7 @@ export default {
}, 2000)
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
removeTaskRelation() {
@ -241,7 +238,7 @@ export default {
}, 2000)
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
.finally(() => {
this.showDeleteModal = false
@ -255,7 +252,7 @@ export default {
this.addTaskRelation()
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
relationKindTitle(kind, length) {

View File

@ -26,7 +26,7 @@
<div class="field has-addons is-fullwidth">
<div class="control">
<input
:disabled="disabled"
:disabled="disabled || null"
@change="updateData"
class="input"
:placeholder="$t('task.repeat.specifyAmount')"
@ -36,7 +36,7 @@
</div>
<div class="control">
<div class="select">
<select :disabled="disabled" @change="updateData" v-model="repeatAfter.type">
<select :disabled="disabled || null" @change="updateData" v-model="repeatAfter.type">
<option value="hours">{{ $t('task.repeat.hours') }}</option>
<option value="days">{{ $t('task.repeat.days') }}</option>
<option value="weeks">{{ $t('task.repeat.weeks') }}</option>
@ -51,7 +51,7 @@
</template>
<script>
import repeatModes from '@/models/taskRepeatModes'
import repeatModes from '@/models/constants/taskRepeatModes'
export default {
name: 'repeatAfter',
@ -62,7 +62,7 @@ export default {
amount: 0,
type: '',
},
repeatModes: repeatModes,
repeatModes,
}
},
props: {
@ -75,19 +75,16 @@ export default {
},
},
watch: {
value(newVal) {
this.task = newVal
if (typeof newVal.repeatAfter !== 'undefined') {
this.repeatAfter = newVal.repeatAfter
}
value: {
handler(value) {
this.task = value
if (typeof value.repeatAfter !== 'undefined') {
this.repeatAfter = value.repeatAfter
}
},
immediate: true,
},
},
mounted() {
this.task = this.value
if (typeof this.value.repeatAfter !== 'undefined') {
this.repeatAfter = this.value.repeatAfter
}
},
methods: {
updateData() {
if (this.task.repeatMode !== repeatModes.REPEAT_MODE_DEFAULT && this.repeatAfter.amount === 0) {

View File

@ -59,6 +59,7 @@
<icon icon="align-left"/>
</span>
</span>
<checklist-summary :task="task"/>
</router-link>
<progress
class="progress is-small"
@ -94,17 +95,19 @@ import Fancycheckbox from '../../input/fancycheckbox'
import DeferTask from './defer-task'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {playPop} from '@/helpers/playPop'
import ChecklistSummary from './checklist-summary'
export default {
name: 'singleTaskInList',
data() {
return {
taskService: TaskService,
task: TaskModel,
taskService: new TaskService(),
task: new TaskModel(),
showDefer: false,
}
},
components: {
ChecklistSummary,
DeferTask,
Fancycheckbox,
User,
@ -146,10 +149,6 @@ export default {
this.task = this.theTask
document.addEventListener('click', this.hideDeferDueDatePopup)
},
created() {
this.task = new TaskModel()
this.taskService = new TaskService()
},
beforeDestroy() {
document.removeEventListener('click', this.hideDeferDueDatePopup)
},
@ -175,11 +174,11 @@ export default {
}
this.task = t
this.$emit('task-updated', t)
this.success({
message: this.task.done ?
this.$t('task.doneSuccess') :
this.$t('task.undoneSuccess'),
}, [{
this.$message.success({
message: this.task.done ?
this.$t('task.doneSuccess') :
this.$t('task.undoneSuccess'),
}, [{
title: 'Undo',
callback: () => {
this.task.done = !this.task.done
@ -188,7 +187,7 @@ export default {
}])
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
}
@ -207,7 +206,7 @@ export default {
this.$store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist')
})
.catch(e => {
this.error(e)
this.$message.error(e)
})
},
hideDeferDueDatePopup(e) {

View File

@ -74,14 +74,13 @@ export default {
data() {
return {
avatarProvider: '',
avatarService: AvatarService,
avatarService: new AvatarService(),
isCropAvatar: false,
avatarToCrop: null,
loading: false, // Seperate variable because some things we're doing in browser take a bit
}
},
created() {
this.avatarService = new AvatarService()
this.avatarStatus()
},
components: {
@ -93,16 +92,16 @@ export default {
.then(r => {
this.avatarProvider = r.avatarProvider
})
.catch(e => this.error(e))
.catch(e => this.$message.error(e))
},
updateAvatarStatus() {
const avatarStatus = new AvatarModel({avatarProvider: this.avatarProvider})
this.avatarService.update(avatarStatus)
.then(() => {
this.success({message: this.$t('user.settings.avatar.statusUpdateSuccess')})
this.$message.success({message: this.$t('user.settings.avatar.statusUpdateSuccess')})
this.$store.commit('auth/reloadAvatar')
})
.catch(e => this.error(e))
.catch(e => this.$message.error(e))
},
uploadAvatar() {
this.loading = true
@ -112,10 +111,10 @@ export default {
canvas.toBlob(blob => {
this.avatarService.create(blob)
.then(() => {
this.success({message: this.$t('user.settings.avatar.setSuccess')})
this.$message.success({message: this.$t('user.settings.avatar.setSuccess')})
this.$store.commit('auth/reloadAvatar')
})
.catch(e => this.error(e))
.catch(e => this.$message.error(e))
.finally(() => {
this.loading = false
this.isCropAvatar = false

View File

@ -61,10 +61,10 @@ export default {
this.dataExportService.request(this.password)
.then(() => {
this.success({message: this.$t('user.export.success')})
this.$message.success({message: this.$t('user.export.success')})
this.password = ''
})
.catch(e => this.error(e))
.catch(e => this.$message.error(e))
},
},
}

View File

@ -91,14 +91,11 @@ export default {
name: 'user-settings-deletion',
data() {
return {
accountDeleteService: AccountDeleteService,
accountDeleteService: new AccountDeleteService(),
password: '',
errPasswordRequired: false,
}
},
created() {
this.accountDeleteService = new AccountDeleteService()
},
computed: mapState({
userDeletionEnabled: state => state.config.userDeletionEnabled,
deletionScheduledAt: state => parseDateOrNull(state.auth.info.deletionScheduledAt),
@ -113,10 +110,10 @@ export default {
this.accountDeleteService.request(this.password)
.then(() => {
this.success({message: this.$t('user.deletion.requestSuccess')})
this.$message.success({message: this.$t('user.deletion.requestSuccess')})
this.password = ''
})
.catch(e => this.error(e))
.catch(e => this.$message.error(e))
},
cancelDeletion() {
if (this.password === '') {
@ -127,11 +124,11 @@ export default {
this.accountDeleteService.cancel(this.password)
.then(() => {
this.success({message: this.$t('user.deletion.scheduledCancelSuccess')})
this.$message.success({message: this.$t('user.deletion.scheduledCancelSuccess')})
this.$store.dispatch('auth/refreshUserInfo')
this.password = ''
})
.catch(e => this.error(e))
.catch(e => this.$message.error(e))
},
},
}

View File

@ -0,0 +1,36 @@
import AttachmentModel from '@/models/attachment'
import FileModel from '@/models/file'
import AttachmentService from '@/services/attachment'
import { store } from '@/store'
export function uploadFile(taskId: number, file: FileModel, onSuccess: () => Function) {
const attachmentService = new AttachmentService()
const files = [file]
return uploadFiles(attachmentService, taskId, files, onSuccess)
}
export function uploadFiles(attachmentService: AttachmentService, taskId: number, files: FileModel[], onSuccess : Function = () => {}) {
const attachmentModel = new AttachmentModel({taskId})
attachmentService.create(attachmentModel, files)
.then(r => {
console.debug(`Uploaded attachments for task ${taskId}, response was`, r)
if (r.success !== null) {
r.success.forEach((attachment: AttachmentModel) => {
store.dispatch('tasks/addTaskAttachment', {
taskId,
attachment,
})
onSuccess(generateAttachmentUrl(taskId, attachment.id))
})
}
if (r.errors !== null) {
throw Error(r.errors)
}
})
}
export function generateAttachmentUrl(taskId: number, attachmentId: number) : any {
return `${window.API_URL}/tasks/${taskId}/attachments/${attachmentId}`
}

View File

@ -0,0 +1,91 @@
import {findCheckboxesInText, getChecklistStatistics} from './checklistFromText'
describe('Find checklists in text', () => {
it('should find no checkbox', () => {
const text: string = 'Lorem Ipsum'
const checkboxes = findCheckboxesInText(text)
expect(checkboxes).toHaveLength(0)
})
it('should find multiple checkboxes', () => {
const text: string = `* [ ] Lorem Ipsum
* [ ] Dolor sit amet
Here's some text in between
* [x] Dolor sit amet
- [ ] Dolor sit amet`
const checkboxes = findCheckboxesInText(text)
expect(checkboxes).toHaveLength(4)
expect(checkboxes[0]).toBe(0)
expect(checkboxes[1]).toBe(18)
expect(checkboxes[2]).toBe(69)
})
it('should find one checkbox with *', () => {
const text: string = '* [ ] Lorem Ipsum'
const checkboxes = findCheckboxesInText(text)
expect(checkboxes).toHaveLength(1)
expect(checkboxes[0]).toBe(0)
})
it('should find one checkbox with -', () => {
const text: string = '- [ ] Lorem Ipsum'
const checkboxes = findCheckboxesInText(text)
expect(checkboxes).toHaveLength(1)
expect(checkboxes[0]).toBe(0)
})
it('should find one checked checkbox with *', () => {
const text: string = '* [x] Lorem Ipsum'
const checkboxes = findCheckboxesInText(text)
expect(checkboxes).toHaveLength(1)
expect(checkboxes[0]).toBe(0)
})
it('should find one checked checkbox with -', () => {
const text: string = '- [x] Lorem Ipsum'
const checkboxes = findCheckboxesInText(text)
expect(checkboxes).toHaveLength(1)
expect(checkboxes[0]).toBe(0)
})
})
describe('Get Checklist Statistics in a Text', () => {
it('should find no checkbox', () => {
const text: string = 'Lorem Ipsum'
const stats = getChecklistStatistics(text)
expect(stats.total).toBe(0)
})
it('should find one checkbox', () => {
const text: string = '* [ ] Lorem Ipsum'
const stats = getChecklistStatistics(text)
expect(stats.total).toBe(1)
expect(stats.checked).toBe(0)
})
it('should find one checked checkbox', () => {
const text: string = '* [x] Lorem Ipsum'
const stats = getChecklistStatistics(text)
expect(stats.total).toBe(1)
expect(stats.checked).toBe(1)
})
it('should find multiple mixed and matched', () => {
const text: string = `* [ ] Lorem Ipsum
* [ ] Dolor sit amet
* [x] Dolor sit amet
- [x] Dolor sit amet
Here's some text in between
* [x] Dolor sit amet
- [ ] Dolor sit amet`
const stats = getChecklistStatistics(text)
expect(stats.total).toBe(6)
expect(stats.checked).toBe(3)
})
})

View File

@ -0,0 +1,53 @@
const checked = '[x]'
interface CheckboxStatistics {
total: number
checked: number
}
interface MatchedCheckboxes {
checked: number[]
unchecked: number[]
}
const getCheckboxesInText = (text: string): MatchedCheckboxes => {
const regex = /[*-] \[[ x]]/g
let match
const checkboxes: MatchedCheckboxes = {
checked: [],
unchecked: [],
}
while ((match = regex.exec(text)) !== null) {
if (match[0].endsWith(checked)) {
checkboxes.checked.push(match.index)
} else {
checkboxes.unchecked.push(match.index)
}
}
return checkboxes
}
/**
* Returns the indices where checkboxes start and end in the given text.
*
* @param text
*/
export const findCheckboxesInText = (text: string): number[] => {
const checkboxes = getCheckboxesInText(text)
return [
...checkboxes.checked,
...checkboxes.unchecked,
].sort()
}
export const getChecklistStatistics = (text: string): CheckboxStatistics => {
const checkboxes = getCheckboxesInText(text)
return {
total: checkboxes.checked.length + checkboxes.unchecked.length,
checked: checkboxes.checked.length,
}
}

3
src/helpers/find.ts Normal file
View File

@ -0,0 +1,3 @@
export function findIndexById(array : [], id : string | number) {
return array.findIndex(({id: currentId}) => currentId === id)
}

View File

@ -1,3 +0,0 @@
export const generateAttachmentUrl = (taskId, attachmentId) => {
return `${window.API_URL}/tasks/${taskId}/attachments/${attachmentId}`
}

View File

@ -0,0 +1,20 @@
import {PrefixMode} from '@/modules/parseTaskText'
const key = 'quickAddMagicMode'
export const setQuickAddMagicMode = (mode: PrefixMode) => {
localStorage.setItem(key, mode)
}
export const getQuickAddMagicMode = (): PrefixMode => {
const mode = localStorage.getItem(key)
switch (mode) {
case PrefixMode.Default:
return PrefixMode.Default
case PrefixMode.Todoist:
return PrefixMode.Todoist
}
return PrefixMode.Disabled
}

View File

@ -1,8 +1,8 @@
import {createDateFromString} from '@/helpers/time/createDateFromString'
import {format, formatDistance} from 'date-fns'
import {enGB, de} from 'date-fns/locale'
import {enGB, de, fr, ru} from 'date-fns/locale'
const locales = {enGB, de}
const locales = {en: enGB, de, ch: de, fr, ru}
const dateIsValid = date => {
if (date === null) {

View File

@ -222,7 +222,7 @@ export const getDateFromTextIn = (text: string, now: Date = new Date()) => {
}
const getDateFromWeekday = (text: string): dateFoundResult => {
const matcher: RegExp = / (mon|monday|tue|tuesday|wed|wednesday|thu|thursday|fri|friday|sat|saturday|sun|sunday)/ig
const matcher: RegExp = / (next )?(monday|mon|tuesday|tue|wednesday|wed|thursday|thu|friday|fri|saturday|sat|sunday|sun)($| )/ig
const results: string[] | null = matcher.exec(text)
if (results === null) {
return {
@ -235,7 +235,7 @@ const getDateFromWeekday = (text: string): dateFoundResult => {
const currentDay: number = date.getDay()
let day: number = 0
switch (results[1]) {
switch (results[2]) {
case 'mon':
case 'monday':
day = 1
@ -274,8 +274,15 @@ const getDateFromWeekday = (text: string): dateFoundResult => {
const distance: number = (day + 7 - currentDay) % 7
date.setDate(date.getDate() + distance)
// This a space at the end of the found text to not break parsing suffix strings like "at 14:00" in cases where the
// matched string comes with a space at the end (last part of the regex).
let foundText = results[0]
if (foundText.endsWith(' ')) {
foundText = foundText.substr(0, foundText.length - 1)
}
return {
foundText: results[1],
foundText: foundText,
date: date,
}
}

View File

@ -96,6 +96,12 @@
"uploadAvatar": "Avatar hochladen",
"statusUpdateSuccess": "Avatar-Status wurde erfolgreich aktualisiert.",
"setSuccess": "Der Avatar wurde erfolgreich gesetzt!"
},
"quickAddMagic": {
"title": "Quick Add Magic Modus",
"disabled": "Deaktiviert",
"todoist": "Todoist",
"vikunja": "Vikunja"
}
},
"deletion": {
@ -113,11 +119,12 @@
"scheduledCancelSuccess": "Wir werden deinen Account nicht löschen."
},
"export": {
"title": "Exportiere die Daten deines Vikunja-Accounts",
"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.",
"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."
"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": {
@ -492,6 +499,8 @@
"doneSuccess": "Die Aufgabe wurde erfolgreich als erledigt markiert.",
"undoneSuccess": "Die Aufgabe wurde erfolgreich als nicht-erledigt markiert.",
"openDetail": "Aufgabe in der Detailansicht anzeigen",
"checklistTotal": "{checked} von {total} Aufgaben",
"checklistAllDone": "{total} Aufgaben",
"show": {
"titleCurrent": "Aktuelle Aufgaben",
"titleDates": "Aufgaben von {from} bis {to}",
@ -517,7 +526,7 @@
"updateSuccess": "Die Aufgabe wurde erfolgreich gespeichert.",
"deleteSuccess": "Die Aufgabe wurde erfolgreich gelöscht.",
"belongsToList": "Diese Aufgabe gehört zur Liste „{list}“",
"due": "Fällig am {at}",
"due": "Fällig {at}",
"delete": {
"header": "Diese Aufgabe löschen",
"text1": "Willst du diese Aufgabe wirklich löschen?",
@ -655,7 +664,7 @@
"quickAddMagic": {
"hint": "Du kannst Quick Add Magic verwenden",
"what": "Was?",
"title": "Qucik Add Magic",
"title": "Quick Add Magic",
"intro": "Beim Erstellen einer Aufgabe kannst du spezielle Schlüsselwörter verwenden, um Attribute direkt zu der neu erstellten Aufgabe hinzuzufügen. Dadurch können häufig verwendete Attribute schneller zu Aufgaben hinzugefügt werden.",
"multiple": "Du kannst das mehrmals benutzen.",
"label1": "Um ein Label hinzuzufügen, gibt einfach den Name mit einem vorangestellten {prefix} ein.",
@ -664,7 +673,7 @@
"label4": "Zum Beispiel: {prefix}\"Label mit Leerzeichen\".",
"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 @ Zeichen ein.",
"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.",
"dateAndTime": "Datum und Uhrzeit",

View File

@ -96,6 +96,12 @@
"uploadAvatar": "Profiilbild ufeladä",
"statusUpdateSuccess": "Avatar Zuestand erfolgriich aktualisiert!",
"setSuccess": "Diis Profilbild isch erfolgriich gsetzt worde!"
},
"quickAddMagic": {
"title": "Quick Add Magic Modus",
"disabled": "Deaktiviert",
"todoist": "Todoist",
"vikunja": "Vikunja"
}
},
"deletion": {
@ -113,11 +119,12 @@
"scheduledCancelSuccess": "Wir werden deinen Account nicht löschen."
},
"export": {
"title": "Exportiere die Daten deines Vikunja-Accounts",
"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.",
"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."
"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": {
@ -492,6 +499,8 @@
"doneSuccess": "Die Uufgab isch erfolgriich als \"Fertig\" markiert wordä.",
"undoneSuccess": "Die Uufgaab isch nüme als fertig markiert.",
"openDetail": "Uufgab i de Detailaahsicht öffne",
"checklistTotal": "{checked} von {total} Aufgaben",
"checklistAllDone": "{total} Aufgaben",
"show": {
"titleCurrent": "Momentani Uufgabe",
"titleDates": "Uufgabe vo {from} bis {to}",
@ -664,7 +673,7 @@
"label4": "Zum Blaistift: {prefix}\"Label mit Leerschlag\".",
"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 direkt e Uufgab emne Benutzer zuezwiise, setz es @ vor ihren Benutzername.",
"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.",
"dateAndTime": "Datum und Ziit",

View File

@ -1,309 +0,0 @@
{
"404": {
"title": "Nicht gefunden",
"text": "Die angeforderte Seite existiert nicht."
},
"filters": {
"create": {
"action": "Neuen gespeicherten Filter erstellen",
"description": "Ein gespeicherter Filter ist eine virtuelle Liste, die bei jedem Zugriff aus einem Satz von Filtern errechnet wird. Einmal erstellt, erscheint er in einem speziellen Namensraum.",
"title": "Einen gespeicherten Filter erstellen"
},
"attributes": {
"descriptionPlaceholder": "Die Beschreibung steht hier …",
"description": "Beschreibung",
"titlePlaceholder": "Der gespeicherte Filtertitel steht hier …",
"title": "Titel"
},
"delete": {
"header": "Diesen gespeicherten Filter löschen",
"success": "Der Filter wurde erfolgreich gelöscht."
},
"edit": {
"success": "Der Filter wurde erfolgreich gespeichert.",
"title": "Diesen gespeicherten Filter bearbeiten"
},
"title": "Filter"
},
"sharing": {
"authenticating": "Authentifizierung …",
"invalidPassword": "Das Passwort ist ungültig.",
"error": "Es ist ein Fehler aufgetreten."
},
"label": {
"attributes": {
"color": "Farbe",
"description": "Beschreibung",
"title": "Titel"
}
},
"misc": {
"search": "Suchen",
"copy": "In Zwischenablage kopieren",
"disable": "Deaktivieren",
"confirm": "Bestätigen",
"delete": "Löschen",
"save": "Speichern",
"loading": "Wird geladen …",
"previous": "Vorherige",
"next": "Weiter"
},
"task": {
"delete": "Diese Aufgabe löschen",
"new": "Eine neue Aufgabe erstellen",
"task": "Aufgabe",
"show": {
"titleCurrent": "Aktuelle Aufgaben",
"noTasks": "Nichts zu tun Einen schönen Tag noch!",
"today": "Heute",
"nextWeek": "Nächste Woche"
},
"detail": {
"created": "Erstellt {0} von {1}",
"undone": "Als unerledigt markieren",
"done": "Fertig!",
"move": "Aufgabe in eine andere Liste verschieben",
"delete": {
"header": "Diese Aufgabe löschen"
},
"deleteSuccess": "Die Aufgabe wurde erfolgreich gelöscht.",
"updateSuccess": "Die Aufgabe wurde erfolgreich gespeichert.",
"doneAt": "Erledigt {0}",
"updated": "Aktualisiert {0}",
"actions": {
"priority": "Priorität einstellen",
"reminders": "Erinnerungen einstellen",
"relatedTasks": "Aufgabenbeziehungen hinzufügen",
"attachments": "Anhänge hinzufügen",
"delete": "Aufgabe löschen",
"color": "Taskfarbe einstellen",
"moveList": "Aufgabe verschieben"
}
},
"attributes": {
"color": "Farbe",
"done": "Fertig",
"createdBy": "Erstellt von",
"created": "Erstellt",
"endDate": "Enddatum",
"dueDate": "Fälligkeitsdatum",
"title": "Titel",
"startDate": "Anfangsdatum",
"relatedTasks": "Verwandte Aufgaben",
"priority": "Priorität",
"percentDone": "% erledigt",
"repeat": "Wiederholen",
"reminders": "Erinnerungen",
"updated": "Aktualisiert"
}
},
"team": {
"edit": {
"delete": {
"header": "Team löschen",
"success": "Das Team wurde erfolgreich gelöscht."
},
"madeAdmin": "Das Teammitglied wurde erfolgreich zum Admin gemacht.",
"madeMember": "Das Teammitglied wurde erfolgreich zum Mitglied gemacht.",
"userAddedSuccess": "Das Teammitglied wurde erfolgreich hinzugefügt.",
"success": "Das Team wurde erfolgreich aktualisiert.",
"addUser": "Zum Team hinzufügen",
"members": "Teammitglieder",
"title": "Team „{team}“ bearbeiten",
"deleteUser": {
"header": "Benutzer aus dem Team entfernen"
}
},
"create": {
"success": "Das Team wurde erfolgreich erstellt.",
"title": "Ein neues Team erstellen"
},
"title": "Teams",
"attributes": {
"description": "Beschreibung",
"descriptionPlaceholder": "Die Beschreibung des Teams steht hier …",
"member": "Mitglied",
"admin": "Admin",
"name": "Teamname",
"namePlaceholder": "Der Name des Teams steht hier …"
}
},
"namespace": {
"create": {
"explanation": "Ein Namensraum ist eine Sammlung von Listen, die man teilen und verwenden kann, um seine Listen zu organisieren. Tatsächlich gehört jede Liste zu einem Namensraum.",
"success": "Der Namensraum wurde erfolgreich angelegt.",
"tooltip": "Was ist ein Namensraum?",
"title": "Einen neuen Namensraum erstellen"
},
"attributes": {
"isArchived": "Dieser Namensraum wird archiviert",
"archived": "Ist archiviert",
"color": "Farbe",
"descriptionPlaceholder": "Die Beschreibung des Namensraums steht hier …",
"description": "Beschreibung",
"titlePlaceholder": "Der Titel des Namensraums steht hier …",
"title": "Namensraumtitel"
},
"share": {
"title": "„{namespace}“ teilen"
},
"edit": {
"success": "Der Namensraum wurde erfolgreich aktualisiert.",
"title": "„{namespace}“ bearbeiten"
},
"delete": {
"success": "Der Namensraum wurde erfolgreich gelöscht.",
"text2": "Dies umfasst alle Listen und Aufgaben und kann NICHT rückgängig gemacht werden!",
"title": "„{namespace}“ löschen"
},
"archive": {
"description": "Wenn ein Namensraum archiviert ist, kann man keine neuen Listen erstellen oder ihn bearbeiten.",
"success": "Der Namensraum wurde erfolgreich archiviert.",
"titleUnarchive": "Archivierung von „{namespace}“ aufheben",
"titleArchive": "„{namespace}“ archivieren"
},
"noLists": "Dieser Namensraum enthält keine Listen.",
"title": "Namensräume & Listen",
"unarchive": "Archivierung aufheben",
"archived": "Archiviert",
"showArchived": "Archivierte anzeigen"
},
"list": {
"kanban": {
"bucketTitleSavedSuccess": "Der Eimertitel wurde erfolgreich gespeichert.",
"deleteBucketSuccess": "Der Eimer wurde erfolgreich gelöscht.",
"deleteBucketText2": "Dies löscht keine Aufgaben, sondern verschiebt sie in den Standard-Eimer.",
"deleteHeaderBucket": "Den Eimer löschen",
"addBucket": "Einen neuen Eimer erstellen",
"addAnotherTask": "Weitere Aufgabe hinzufügen",
"addTask": "Eine Aufgabe hinzufügen",
"doneBucket": "Erledigte-Dinge-Eimer",
"noLimit": "Nicht eingestellt"
},
"table": {
"columns": "Spalten",
"title": "Tabelle"
},
"gantt": {
"to": "An",
"from": "Von",
"day": "Tag",
"month": "Monat",
"default": "Standard",
"size": "Größe",
"showTasksWithoutDates": "Aufgaben anzeigen, für die keine Termine festgelegt sind",
"title": "Gantt"
},
"list": {
"empty": "Diese Liste ist derzeit leer.",
"addPlaceholder": "Eine neue Aufgabe hinzufügen …",
"add": "Hinzufügen",
"title": "Liste"
},
"share": {
"title": "„{Liste}“ teilen",
"header": "Diese Liste teilen"
},
"edit": {
"success": "Die Liste wurde erfolgreich aktualisiert.",
"color": "Farbe",
"descriptionPlaceholder": "Die Listenbeschreibung geht hier …",
"description": "Beschreibung",
"identifierPlaceholder": "Der Listenbezeichner geht hier …",
"identifier": "Listebezeichner",
"identifierTooltip": "Der Listenbezeichner kann zur eindeutigen Identifizierung einer Aufgabe über Listen hinweg verwendet werden. Man kann ihn auf leer setzen, um ihn zu deaktivieren.",
"titlePlaceholder": "Der Titel der Liste steht hier …",
"title": "„{list}“ bearbeiten",
"header": "Diese Liste bearbeiten"
},
"duplicate": {
"success": "Die Liste wurde erfolgreich dupliziert.",
"label": "Duplizieren",
"title": "Diese Liste duplizieren"
},
"delete": {
"success": "Die Liste wurde erfolgreich gelöscht.",
"text2": "Dies umfasst alle Aufgaben und kann NICHT rückgängig gemacht werden!",
"header": "Diese Liste löschen",
"title": "„{list}“ löschen"
},
"background": {
"removeSuccess": "Der Hintergrund ist erfolgreich entfernt worden!",
"success": "Der Hintergrund ist erfolgreich eingestellt worden!",
"loadMore": "Mehr Fotos laden",
"poweredByUnsplash": "Angetrieben von Unsplash",
"searchPlaceholder": "Nach einem Hintergrund suchen …",
"remove": "Hintergrund entfernen",
"title": "Listenhintergrund festlegen"
},
"archive": {
"success": "Die Liste wurde erfolgreich archiviert.",
"unarchive": "Archivierung dieser Liste aufheben",
"archive": "Diese Liste archivieren",
"title": "„{Liste}“ archivieren"
},
"create": {
"createdSuccess": "Die Liste wurde erfolgreich erstellt.",
"titlePlaceholder": "Der Titel der Liste steht hier …",
"header": "Eine neue Liste erstellen"
},
"color": "Farbe"
},
"user": {
"settings": {
"caldav": {
"title": "Caldav"
},
"totp": {
"disableSuccess": "Die Zwei-Faktor-Authentifizierung wurde erfolgreich deaktiviert.",
"passcode": "Passcode",
"enroll": "Einschreiben",
"title": "Zwei-Faktor-Authentifizierung"
},
"general": {
"weekStartMonday": "Montag",
"weekStartSunday": "Sonntag",
"weekStart": "Woche beginnt am",
"playSoundWhenDone": "Einen Ton abspielen, wenn Aufgaben als erledigt markiert werden",
"discoverableByEmail": "Andere Benutzer mich finden lassen, wenn sie nach meiner vollständigen E-Mail suchen",
"discoverableByName": "Andere Benutzer mich finden lassen, wenn sie nach meinem Namen suchen",
"overdueReminders": "Mir jeden Morgen Erinnerungen für überfällige unerledigte Aufgaben per E-Mail senden",
"emailReminders": "Mir Erinnerungen für Aufgaben per E-Mail senden",
"savedSuccess": "Die Einstellungen wurden erfolgreich aktualisiert.",
"newName": "Der neue Name",
"name": "Name",
"title": "Allgemeine Einstellungen"
},
"updateEmailNew": "Neue E-Mail-Adresse",
"passwordUpdateSuccess": "Das Passwort wurde erfolgreich aktualisiert.",
"passwordsDontMatch": "Das neue Passwort und seine Bestätigung stimmen nicht überein.",
"currentPassword": "Aktuelles Passwort",
"newPasswordConfirm": "Neue Passwortbestätigung",
"newPassword": "Neues Passwort",
"title": "Einstellungen"
},
"auth": {
"openIdStateError": "Zustand stimmt nicht überein, weigert sich fortzufahren!",
"authenticating": "Authentifizierung …",
"loginWith": "Mit {provider} anmelden",
"register": "Registrieren",
"login": "Anmelden",
"totpPlaceholder": "z.B. 123456",
"totpTitle": "Zwei-Faktor-Authentifizierungscode",
"passwordsDontMatch": "Passwörter stimmen nicht überein",
"passwordPlaceholder": "z.B. •••••••••••",
"password": "Passwort",
"emailPlaceholder": "z.B. frederic@vikunja.io",
"email": "E-Mail-Adresse",
"usernamePlaceholder": "z.B. frederick",
"usernameEmail": "Benutzername oder E-Mail-Adresse",
"username": "Benutzername"
}
},
"home": {
"list": {
"new": "Eine neue Liste erstellen"
},
"welcome": "Hallo {username}"
}
}

View File

@ -96,6 +96,12 @@
"uploadAvatar": "Upload Avatar",
"statusUpdateSuccess": "Avatar status was updated successfully!",
"setSuccess": "The avatar has been set successfully!"
},
"quickAddMagic": {
"title": "Quick Add Magic Mode",
"disabled": "Disabled",
"todoist": "Todoist",
"vikunja": "Vikunja"
}
},
"deletion": {
@ -113,11 +119,12 @@
"scheduledCancelSuccess": "We will not delete your account."
},
"export": {
"title": "Export your Vikunja Data",
"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.",
"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."
"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": {
@ -492,6 +499,8 @@
"doneSuccess": "The task was successfully marked as done.",
"undoneSuccess": "The task was successfully un-marked as done.",
"openDetail": "Open task detail view",
"checklistTotal": "{checked} of {total} tasks",
"checklistAllDone": "{total} tasks",
"show": {
"titleCurrent": "Current Tasks",
"titleDates": "Tasks from {from} until {to}",
@ -664,7 +673,7 @@
"label4": "For example: {prefix}\"Label with spaces\".",
"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 @ to the task.",
"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.",
"dateAndTime": "Date and time",

View File

@ -96,6 +96,12 @@
"uploadAvatar": "Upload Avatar",
"statusUpdateSuccess": "Avatar status was updated successfully!",
"setSuccess": "The avatar has been set successfully!"
},
"quickAddMagic": {
"title": "Quick Add Magic Mode",
"disabled": "Disabled",
"todoist": "Todoist",
"vikunja": "Vikunja"
}
},
"deletion": {
@ -113,11 +119,12 @@
"scheduledCancelSuccess": "We will not delete your account."
},
"export": {
"title": "Export your Vikunja Data",
"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.",
"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."
"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": {
@ -492,6 +499,8 @@
"doneSuccess": "The task was successfully marked as done.",
"undoneSuccess": "The task was successfully un-marked as done.",
"openDetail": "Open task detail view",
"checklistTotal": "{checked} of {total} tasks",
"checklistAllDone": "{total} tasks",
"show": {
"titleCurrent": "Current Tasks",
"titleDates": "Tasks from {from} until {to}",
@ -664,7 +673,7 @@
"label4": "For example: {prefix}\"Label with spaces\".",
"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 @ to the task.",
"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.",
"dateAndTime": "Date and time",

View File

@ -1 +0,0 @@
{}

View File

@ -28,9 +28,9 @@
"passwordPlaceholder": "p. ex. •••••••••••",
"resetPassword": "Réinitialiser ton mot de passe",
"resetPasswordAction": "Menvoyer un lien de réinitialisation du mot de passe",
"resetPasswordSuccess": "Check your inbox! You should have an e-mail with instructions on how to reset your password.",
"resetPasswordSuccess": "Vérifie ta boîte de réception ! Tu devrais avoir un courriel contenant les instructions sur la manière de réinitialiser ton mot de passe.",
"passwordsDontMatch": "Les mots de passe ne correspondent pas",
"confirmEmailSuccess": "Tu peux maintenant te connecter en utilisant votre adresse courriel.",
"confirmEmailSuccess": "Tu peux maintenant te connecter en utilisant ton adresse courriel.",
"totpTitle": "Code dauthentification à deux facteurs",
"totpPlaceholder": "p. ex. 123456",
"login": "Se connecter",
@ -38,7 +38,7 @@
"loginWith": "Se connecter avec {provider}",
"authenticating": "Authentification…",
"openIdStateError": "Létat ne correspond pas, impossible de continuer !",
"openIdGeneralError": "An error occured while authenticating against the third party.",
"openIdGeneralError": "Une erreur s'est produite lors de l'authentification contre un tiers.",
"logout": "Se déconnecter"
},
"settings": {
@ -96,28 +96,35 @@
"uploadAvatar": "Téléverser lavatar",
"statusUpdateSuccess": "Statut de lavatar mis à jour.",
"setSuccess": "Avatar défini."
},
"quickAddMagic": {
"title": "Quick Add Magic Mode",
"disabled": "Disabled",
"todoist": "Todoist",
"vikunja": "Vikunja"
}
},
"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.",
"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.",
"passwordRequired": "Please enter your password.",
"confirmSuccess": "You've successfully confirmed the deletion of your account. We will delete your account in three days.",
"scheduled": "We will delete your Vikunja account at {date} ({dateSince}).",
"scheduledCancel": "To cancel the deletion of your account, click here.",
"scheduledCancelText": "To cancel the deletion of your account, please enter your password below:",
"scheduledCancelConfirm": "Cancel the deletion of my account",
"scheduledCancelSuccess": "We will not delete your account."
"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.",
"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.",
"passwordRequired": "Entre ton mot de passe.",
"confirmSuccess": "Tu as confirmé avec succès la suppression de ton compte. Nous allons supprimer ton compte dans trois jours.",
"scheduled": "Nous allons supprimer ton compte Vikunja le {date} ({dateSince}).",
"scheduledCancel": "Pour annuler la suppression de ton compte, clique ici.",
"scheduledCancelText": "Pour annuler la suppression de ton compte, entre ton mot de passe ci-dessous :",
"scheduledCancelConfirm": "Annuler la suppression de mon compte",
"scheduledCancelSuccess": "Nous ne supprimerons pas ton compte."
},
"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.",
"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."
"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.",
"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": {
@ -379,8 +386,8 @@
"alreadyMigrated1": "Il semble que tu aies déjà importé tes affaires de {name} le {date}.",
"alreadyMigrated2": "Importer à nouveau est possible mais peut créer des doublons. Es-tu sûr·e ?",
"confirm": "Je suis sûr·e, commencer à migrer maintenant !",
"importUpload": "To import data from {name} into Vikunja, click the button below to select a file.",
"upload": "Upload file"
"importUpload": "Pour importer les données de {name} dans Vikunja, clique sur le bouton ci-dessous pour sélectionner un fichier.",
"upload": "Téléverser le fichier"
},
"label": {
"title": "Étiquettes",
@ -442,7 +449,7 @@
"saved": "Enregistré !",
"default": "Par défaut",
"close": "Fermer",
"download": "Download"
"download": "Télécharger"
},
"input": {
"resetColor": "Réinitialiser la couleur",
@ -492,6 +499,8 @@
"doneSuccess": "Tâche marquée comme terminée.",
"undoneSuccess": "Tâche marquée comme non terminée.",
"openDetail": "Ouvrir la vue détaillée de la tâche",
"checklistTotal": "{checked} of {total} tasks",
"checklistAllDone": "{total} tasks",
"show": {
"titleCurrent": "Tâches actuelles",
"titleDates": "Tâches du {from} au {to}",
@ -538,8 +547,8 @@
"moveList": "Déplacer la tâche",
"color": "Définir la couleur de la tâche",
"delete": "Supprimer la tâche",
"favorite": "Save as favorite",
"unfavorite": "Remove from favorites"
"favorite": "Enregistrer comme favori",
"unfavorite": "Retirer des favoris"
}
},
"attributes": {
@ -664,7 +673,7 @@
"label4": "Par exemple : {prefix}\"Étiquette avec espaces\".",
"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 attribuer directement la tâche à une personne, ajoutez son nom dutilisateur préfixé par @ à la tâche.",
"assignees": "To directly assign the task to a user, add their username prefixed with {prefix} to the task.",
"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.",
"dateAndTime": "Date et heure",

View File

@ -1 +0,0 @@
{}

View File

@ -96,6 +96,12 @@
"uploadAvatar": "Upload Avatar",
"statusUpdateSuccess": "Avatar status was updated successfully!",
"setSuccess": "The avatar has been set successfully!"
},
"quickAddMagic": {
"title": "Quick Add Magic Mode",
"disabled": "Disabled",
"todoist": "Todoist",
"vikunja": "Vikunja"
}
},
"deletion": {
@ -113,11 +119,12 @@
"scheduledCancelSuccess": "We will not delete your account."
},
"export": {
"title": "Export your Vikunja Data",
"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.",
"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."
"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": {
@ -492,6 +499,8 @@
"doneSuccess": "The task was successfully marked as done.",
"undoneSuccess": "The task was successfully un-marked as done.",
"openDetail": "Open task detail view",
"checklistTotal": "{checked} of {total} tasks",
"checklistAllDone": "{total} tasks",
"show": {
"titleCurrent": "Current Tasks",
"titleDates": "Tasks from {from} until {to}",
@ -664,7 +673,7 @@
"label4": "For example: {prefix}\"Label with spaces\".",
"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 @ to the task.",
"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.",
"dateAndTime": "Date and time",

View File

@ -1,34 +1,34 @@
{
"home": {
"welcomeNight": "Boa noite {username}",
"welcomeMorning": "Bom dia {username}",
"welcomeDay": "Olá {username}",
"welcomeEvening": "Boa tarde {username}",
"lastViewed": "Recentes",
"welcomeNight": "Good Night {username}",
"welcomeMorning": "Good Morning {username}",
"welcomeDay": "Hi {username}",
"welcomeEvening": "Good Evening {username}",
"lastViewed": "Last viewed",
"list": {
"newText": "Você pode criar uma nova lista para suas novas tarefas:",
"new": "Criar uma nova lista",
"importText": "Ou importe suas listas e tarefas de outros serviços no Vikunja:",
"import": "Importe seus dados para o Vikunja"
"newText": "You can create a new list for your new tasks:",
"new": "Create a new list",
"importText": "Or import your lists and tasks from other services into Vikunja:",
"import": "Import your data into Vikunja"
}
},
"404": {
"title": "Não encontrado",
"text": "A página solicitada não existe."
"title": "Not found",
"text": "The page you requested does not exist."
},
"user": {
"auth": {
"username": "Usuário",
"usernameEmail": "Usuário ou Email",
"usernamePlaceholder": "ex: lucas",
"email": "Endereço de e-mail",
"emailPlaceholder": "ex: lucas@vikunja.io",
"password": "Senha",
"passwordRepeat": "Digite novamente sua senha",
"passwordPlaceholder": "ex.: •••••••••••••",
"resetPassword": "Redefinir sua senha",
"resetPasswordAction": "Envie-me um link para redefinição de senha",
"resetPasswordSuccess": "Verifique sua caixa de entrada! Você deve ter um e-mail com instruções sobre como redefinir sua senha.",
"username": "Username",
"usernameEmail": "Username Or Email Address",
"usernamePlaceholder": "e.g. frederick",
"email": "E-mail address",
"emailPlaceholder": "e.g. frederic@vikunja.io",
"password": "Password",
"passwordRepeat": "Retype your password",
"passwordPlaceholder": "e.g. •••••••••••",
"resetPassword": "Reset your password",
"resetPasswordAction": "Send me a password reset link",
"resetPasswordSuccess": "Check your inbox! You should have an e-mail with instructions on how to reset your password.",
"passwordsDontMatch": "Passwords don't match",
"confirmEmailSuccess": "You successfully confirmed your email! You can log in now.",
"totpTitle": "Two Factor Authentication Code",
@ -96,6 +96,12 @@
"uploadAvatar": "Upload Avatar",
"statusUpdateSuccess": "Avatar status was updated successfully!",
"setSuccess": "The avatar has been set successfully!"
},
"quickAddMagic": {
"title": "Quick Add Magic Mode",
"disabled": "Disabled",
"todoist": "Todoist",
"vikunja": "Vikunja"
}
},
"deletion": {
@ -113,11 +119,12 @@
"scheduledCancelSuccess": "We will not delete your account."
},
"export": {
"title": "Export your Vikunja Data",
"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.",
"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."
"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": {
@ -492,6 +499,8 @@
"doneSuccess": "The task was successfully marked as done.",
"undoneSuccess": "The task was successfully un-marked as done.",
"openDetail": "Open task detail view",
"checklistTotal": "{checked} of {total} tasks",
"checklistAllDone": "{total} tasks",
"show": {
"titleCurrent": "Current Tasks",
"titleDates": "Tasks from {from} until {to}",
@ -664,7 +673,7 @@
"label4": "For example: {prefix}\"Label with spaces\".",
"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 @ to the task.",
"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.",
"dateAndTime": "Date and time",

View File

@ -1,101 +1,107 @@
{
"home": {
"welcomeNight": "Buenas noches {username}",
"welcomeMorning": "Buenos días {username}",
"welcomeDay": "Hola {username}",
"welcomeEvening": "Buenas tardes {username}",
"lastViewed": "Visto por última vez",
"welcomeNight": "Good Night {username}",
"welcomeMorning": "Good Morning {username}",
"welcomeDay": "Hi {username}",
"welcomeEvening": "Good Evening {username}",
"lastViewed": "Last viewed",
"list": {
"newText": "Puedes crear una nueva lista para las tareas nuevas:",
"new": "Crear una lista nueva",
"importText": "O importa tus listas y tareas de otros servicios a Vikunja:",
"import": "Importa tus datos a Vikunja"
"newText": "You can create a new list for your new tasks:",
"new": "Create a new list",
"importText": "Or import your lists and tasks from other services into Vikunja:",
"import": "Import your data into Vikunja"
}
},
"404": {
"title": "No encontrado",
"text": "La página solicitada no existe."
"title": "Not found",
"text": "The page you requested does not exist."
},
"user": {
"auth": {
"username": "Nombre de usuario",
"usernameEmail": "Nombre de usuario o dirección de correo electrónico",
"usernamePlaceholder": "p/ej. Federico",
"email": "Correo electrónico",
"emailPlaceholder": "p/ej. frederic@vikunja.io",
"password": "Contraseña",
"passwordRepeat": "Reescribe tu contraseña",
"passwordPlaceholder": "p/ej. •••••••••••",
"resetPassword": "Restablecer tu contraseña",
"resetPasswordAction": "Envíame un enlace para restablecer la contraseña",
"resetPasswordSuccess": "¡Revisa tu bandeja de entrada! Debes tener un correo electrónico con instrucciones para restablecer tu contraseña.",
"passwordsDontMatch": "Las contraseñas no coinciden",
"confirmEmailSuccess": "Has confirmado correctamente tu correo electrónico. Ya puedes conectarte.",
"totpTitle": "Código de autenticación de dos factores",
"totpPlaceholder": "p/ej. 123456",
"login": "Ingresar",
"register": "Registrarse",
"loginWith": "Inicie sesión con {provider}",
"authenticating": "Autenticando…",
"openIdStateError": "¡El estado no coincide, negándome a continuar!",
"username": "Username",
"usernameEmail": "Username Or Email Address",
"usernamePlaceholder": "e.g. frederick",
"email": "E-mail address",
"emailPlaceholder": "e.g. frederic@vikunja.io",
"password": "Password",
"passwordRepeat": "Retype your password",
"passwordPlaceholder": "e.g. •••••••••••",
"resetPassword": "Reset your password",
"resetPasswordAction": "Send me a password reset link",
"resetPasswordSuccess": "Check your inbox! You should have an e-mail with instructions on how to reset your password.",
"passwordsDontMatch": "Passwords don't match",
"confirmEmailSuccess": "You successfully confirmed your email! You can log in now.",
"totpTitle": "Two Factor Authentication Code",
"totpPlaceholder": "e.g. 123456",
"login": "Login",
"register": "Register",
"loginWith": "Log in with {provider}",
"authenticating": "Authenticating…",
"openIdStateError": "State does not match, refusing to continue!",
"openIdGeneralError": "An error occured while authenticating against the third party.",
"logout": "Cerrar sesión"
"logout": "Logout"
},
"settings": {
"title": "Opciones",
"newPasswordTitle": "Actualiza tu contraseña",
"newPassword": "Nueva contraseña",
"newPasswordConfirm": "Confirmar contraseña",
"currentPassword": "Contraseña actual",
"currentPasswordPlaceholder": "Tu contraseña actual",
"passwordsDontMatch": "La contraseña nueva y su confirmación no emparejan.",
"passwordUpdateSuccess": "La contraseña se actualizó correctamente.",
"updateEmailTitle": "Actualiza tu dirección de correo electrónico",
"updateEmailNew": "Nueva dirección de correo electrónico",
"updateEmailSuccess": "Dirección de correo electrónico actualizada. Haga clic en el enlace del correo electrónico que se te ha enviado para confirmarlo.",
"title": "Settings",
"newPasswordTitle": "Update Your Password",
"newPassword": "New Password",
"newPasswordConfirm": "New Password Confirmation",
"currentPassword": "Current Password",
"currentPasswordPlaceholder": "Your current password",
"passwordsDontMatch": "The new password and its confirmation don't match.",
"passwordUpdateSuccess": "The password was successfully updated.",
"updateEmailTitle": "Update Your E-Mail Address",
"updateEmailNew": "New Email Address",
"updateEmailSuccess": "Your email address was successfully updated. We've sent you a link to confirm it.",
"general": {
"title": "Configuración General",
"name": "Nombre",
"newName": "El nombre nuevo",
"savedSuccess": "Configuración actualizada.",
"emailReminders": "Enviarme recordatorios para tareas por correo electrónico",
"overdueReminders": "Enviarme recordatorios de tareas pendientes atrasadas por correo cada mañana",
"discoverableByName": "Permitir que otros usuarios me encuentren cuando busquen mi nombre",
"discoverableByEmail": "Permitir que otros usuarios me encuentren cuando busquen mi correo electrónico completo",
"playSoundWhenDone": "Reproducir un sonido cuando marcas tareas como hechas",
"weekStart": "La semana empieza en",
"weekStartSunday": "domingo",
"weekStartMonday": "lunes",
"language": "Idioma",
"defaultList": "Lista predeterminada"
"title": "General Settings",
"name": "Name",
"newName": "The new Name",
"savedSuccess": "The settings were successfully updated.",
"emailReminders": "Send me reminders for tasks via Email",
"overdueReminders": "Send me reminders for overdue undone tasks via email each morning",
"discoverableByName": "Let other users find me when they search for my name",
"discoverableByEmail": "Let other users find me when they search for my full email",
"playSoundWhenDone": "Play a sound when marking tasks as done",
"weekStart": "Week starts on",
"weekStartSunday": "Sunday",
"weekStartMonday": "Monday",
"language": "Language",
"defaultList": "Default List"
},
"totp": {
"title": "Autenticación de dos factores",
"enroll": "Inscribirse",
"finishSetupPart1": "Para finalizar tu configuración, utiliza este secreto en tu aplicación totp (Google Authenticator o similar):",
"finishSetupPart2": "Después, introduce un código de tu aplicación abajo.",
"scanQR": "Alternativamente, escanea este código QR:",
"passcode": "Código de acceso",
"passcodePlaceholder": "Un código generado por tu aplicación totp",
"setupSuccess": "¡Has configurado con éxito la autenticación de dos factores!",
"enterPassword": "Por favor, introduce tu contraseña",
"disable": "Desactivar la autenticación en dos pasos",
"confirmSuccess": "¡Has confirmado con éxito tu configuración totp y puedes usarla a partir de ahora!",
"disableSuccess": "La autenticación de dos factores se desactivó correctamente."
"title": "Two Factor Authentication",
"enroll": "Enroll",
"finishSetupPart1": "To finish your setup, use this secret in your totp app (Google Authenticator or similar):",
"finishSetupPart2": "After that, enter a code from your app below.",
"scanQR": "Alternatively you can scan this QR code:",
"passcode": "Passcode",
"passcodePlaceholder": "A code generated by your totp application",
"setupSuccess": "You've sucessfully set up two factor authentication!",
"enterPassword": "Please Enter Your Password",
"disable": "Disable two factor authentication",
"confirmSuccess": "You've successfully confirmed your totp setup and can use it from now on!",
"disableSuccess": "Two factor authentication was sucessfully disabled."
},
"caldav": {
"title": "Caldav",
"howTo": "Puedes conectar Vikunja a los clientes caldav para ver y gestionar todas las tareas desde diferentes clientes. Introduce esta url en tu cliente:",
"more": "Más información sobre caldav en Vikunja"
"howTo": "You can connect Vikunja to caldav clients to view and manage all tasks from different clients. Enter this url into your client:",
"more": "More information about caldav in Vikunja"
},
"avatar": {
"title": "Avatar",
"initials": "Iniciales",
"initials": "Initials",
"gravatar": "Gravatar",
"upload": "Subir",
"uploadAvatar": "Subir Avatar",
"statusUpdateSuccess": "¡El estado del avatar se ha actualizado correctamente!",
"setSuccess": "¡El avatar se ha establecido correctamente!"
"upload": "Upload",
"uploadAvatar": "Upload Avatar",
"statusUpdateSuccess": "Avatar status was updated successfully!",
"setSuccess": "The avatar has been set successfully!"
},
"quickAddMagic": {
"title": "Quick Add Magic Mode",
"disabled": "Disabled",
"todoist": "Todoist",
"vikunja": "Vikunja"
}
},
"deletion": {
@ -113,152 +119,153 @@
"scheduledCancelSuccess": "We will not delete your account."
},
"export": {
"title": "Export your Vikunja Data",
"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.",
"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."
"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": "Esta lista está archivada. No es posible crear nuevas o editar tareas o ello.",
"title": "Título de Lista",
"archived": "This list is archived. It is not possible to create new or edit tasks or it.",
"title": "List Title",
"color": "Color",
"lists": "Listas",
"search": "Escribe para buscar una lista…",
"searchSelect": "Haga clic o presione enter para seleccionar esta lista",
"shared": "Listas compartidas",
"lists": "Lists",
"search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists",
"create": {
"header": "Crear una 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, especifique una lista o establezca una lista por defecto en la configuración."
"header": "Create a new list",
"titlePlaceholder": "The list'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."
},
"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 des-archives.",
"success": "La lista fue archivada exitosamente."
"title": "Archive \"{list}\"",
"archive": "Archive this list",
"unarchive": "Un-Archive this list",
"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."
},
"background": {
"title": "Establecer el fondo de la lista",
"remove": "Eliminar fondo",
"upload": "Elige un fondo de tu pc",
"searchPlaceholder": "Buscar un fondo…",
"poweredByUnsplash": "Con tecnología de Unsplash",
"loadMore": "Cargar más fotos",
"success": "¡El fondo se ha establecido correctamente!",
"removeSuccess": "¡El fondo se ha eliminado exitosamente!"
"title": "Set list 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 PUEDE SER DESHACIDO!",
"success": "La lista se ha eliminado correctamente."
"title": "Delete \"{list}\"",
"header": "Delete this list",
"text1": "Are you sure you want to delete this list and all of its contents?",
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The list was successfully deleted."
},
"duplicate": {
"title": "Duplicar esta lista",
"label": "Duplicar",
"title": "Duplicate this list",
"label": "Duplicate",
"text": "Select a namespace which should hold the duplicated list:",
"success": "La lista se ha duplicado exitosamente."
"success": "The list 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 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…",
"description": "Description",
"descriptionPlaceholder": "The lists description goes here…",
"color": "Color",
"success": "La lista se ha actualizado correctamente."
"success": "The list was successfully updated."
},
"share": {
"header": "Compartir esta lista",
"title": "Compartir \"{list}\"",
"share": "Compartir",
"header": "Share this list",
"title": "Share \"{list}\"",
"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)",
"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.",
"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": "Contraseña (opcional)",
"password": "Password (optional)",
"passwordExplanation": "When authenticating, the user will be required to enter this password.",
"noName": "Sin nombre establecido",
"remove": "Eliminar un enlace compartido",
"removeText": "¿Está seguro de que desea 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"
"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!",
"createSuccess": "The link share was successfully created.",
"deleteSuccess": "The link share was successfully deleted"
},
"userTeam": {
"typeUser": "usuario | usuarios",
"typeTeam": "equipo | equipos",
"shared": "Compartido con estos {type}",
"you": "",
"notShared": "Aún no se ha compartido con {type}.",
"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}.",
"addedSuccess": "El {type} se ha añadido correctamente.",
"updatedSuccess": "El {type} fue añadido correctamente."
"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": "Correcto",
"read": "Solo lectura",
"readWrite": "Lectura y escritura",
"title": "Right",
"read": "Read only",
"readWrite": "Read & write",
"admin": "Admin"
},
"attributes": {
"link": "Enlace",
"name": "Nombre",
"sharedBy": "Compartido por",
"right": "Correcto",
"delete": "Eliminar"
"link": "Link",
"name": "Name",
"sharedBy": "Shared by",
"right": "Right",
"delete": "Delete"
}
},
"list": {
"title": "Lista",
"add": "Añadir",
"addPlaceholder": "Añadir una nueva tarea…",
"empty": "Esta lista está vacía actualmente.",
"newTaskCta": "Crear una nueva tarea.",
"editTask": "Editar Tarea"
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list 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",
"from": "Desde",
"to": "Hasta",
"noDates": "Esta tarea no tiene fechas establecidas."
"showTasksWithoutDates": "Show tasks which don't have dates set",
"size": "Size",
"default": "Default",
"month": "Month",
"day": "Day",
"from": "From",
"to": "To",
"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",
"limit": "Limit: {limit}",
"noLimit": "Not Set",
"doneBucket": "Done bucket",
"doneBucketHint": "All tasks moved into this bucket will automatically marked as done.",
"doneBucketHintExtended": "Todas las tareas que se trasladen al depósito de finalizadas se marcarán como realizadas automáticamente. Todas las tareas marcadas como realizadas desde otro lugar también se moverán.",
"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": "Introduce el nuevo título de la tarea…",
"addTask": "Añadir una tarea",
"addAnotherTask": "Añadir otra tarea",
"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",
@ -271,82 +278,82 @@
},
"pseudo": {
"favorites": {
"title": "Favoritos"
"title": "Favorites"
}
}
},
"namespace": {
"title": "Namespaces & Lists",
"namespace": "Namespace",
"showArchived": "Mostrar archivados",
"showArchived": "Show Archived",
"noneAvailable": "You don't have any namespaces right now.",
"unarchive": "Des-archivar",
"archived": "Archivado",
"unarchive": "Un-Archive",
"archived": "Archived",
"noLists": "This namespace does not contain any lists.",
"createList": "Create a new list in this namespace.",
"namespaces": "Namespaces",
"search": "Type to search for a namespace…",
"create": {
"title": "Create a new namespace",
"titleRequired": "Por favor, especifica un título.",
"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 namepace.",
"tooltip": "What's a namespace?",
"success": "The namespace was successfully created."
},
"archive": {
"titleArchive": "Archivar \"{namespace}\"",
"titleUnarchive": "Des-archivar \"{namespace}\"",
"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.",
"success": "The namespace was successfully archived.",
"description": "If a namespace is archived, you cannot create new lists or edit it."
},
"delete": {
"title": "Eliminar \"{namespace}\"",
"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!",
"success": "The namespace was successfully deleted."
},
"edit": {
"title": "Editar \"{namespace}\"",
"title": "Edit \"{namespace}\"",
"success": "The namespace was successfully updated."
},
"share": {
"title": "Compartir \"{namespace}\""
"title": "Share \"{namespace}\""
},
"attributes": {
"title": "Namespace Title",
"titlePlaceholder": "The namespace title goes here…",
"description": "Descripción",
"description": "Description",
"descriptionPlaceholder": "The namespaces description goes here…",
"color": "Color",
"archived": "Está archivado",
"archived": "Is Archived",
"isArchived": "This namespace is archived"
},
"pseudo": {
"sharedLists": {
"title": "Listas compartidas"
"title": "Shared Lists"
},
"favorites": {
"title": "Favoritos"
"title": "Favorites"
},
"savedFilters": {
"title": "Filtros"
"title": "Filters"
}
}
},
"filters": {
"title": "Filtros",
"title": "Filters",
"attributes": {
"title": "Título",
"titlePlaceholder": "El título del filtro guardado va acá…",
"description": "Descripción",
"descriptionPlaceholder": "La descripción va aquí…",
"title": "Title",
"titlePlaceholder": "The saved filter title goes here…",
"description": "Description",
"descriptionPlaceholder": "The description goes here…",
"includeNulls": "Include Tasks which don't have a value set",
"requireAll": "Require all filters to be true for a task to show up",
"showDoneTasks": "Mostrar tareas completadas",
"enablePriority": "Activar filtro por prioridad",
"enablePercentDone": "Activar filtro por porcentaje completado",
"showDoneTasks": "Show Done Tasks",
"enablePriority": "Enable Filter By Priority",
"enablePercentDone": "Enable Filter By Percent Done",
"dueDateRange": "Due Date Range",
"startDateRange": "Start Date Range",
"endDateRange": "End Date Range",
@ -354,11 +361,11 @@
},
"create": {
"title": "Create A Saved Filter",
"description": "Un filtro guardado es una lista virtual que se calcula a partir de un conjunto de filtros cada vez que se accede a él. Una vez creado, aparecerá en un espacio de nombres especial.",
"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"
},
"delete": {
"header": "Eliminar este filtro guardado",
"header": "Delete this saved filter",
"text": "Are you sure you want to delete this saved filter?",
"success": "The filter was deleted successfully."
},
@ -374,37 +381,37 @@
"description": "Click on the logo of one of the third-party services below to get started.",
"descriptionDo": "Vikunja will import all lists, tasks, notes, reminders and files you have access to.",
"authorize": "To authorize Vikunja to access your {name} Account, click the button below.",
"getStarted": "Empecemos",
"inProgress": "Importando…",
"getStarted": "Get Started",
"inProgress": "Importing in progress…",
"alreadyMigrated1": "It looks like you've already imported your stuff from {name} at {date}.",
"alreadyMigrated2": "Importing again is possible, but might create duplicates. Are you sure?",
"confirm": "¡Estoy seguro de que empiece a migrar ahora!",
"confirm": "I am sure, please start migrating now!",
"importUpload": "To import data from {name} into Vikunja, click the button below to select a file.",
"upload": "Upload file"
},
"label": {
"title": "Etiquetas",
"manage": "Administrar etiquetas",
"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.",
"newCTA": "You currently do not have any labels.",
"search": "Escribe para buscar una etiqueta…",
"search": "Type to search for a label…",
"create": {
"header": "Nueva etiqueta",
"title": "Crear una nueva etiqueta",
"titleRequired": "Por favor, especifica un título.",
"success": "La etiqueta se ha creado correctamente."
"header": "New label",
"title": "Create a new label",
"titleRequired": "Please specify a title.",
"success": "The label was successfully created."
},
"edit": {
"header": "Editar etiqueta",
"header": "Edit Label",
"forbidden": "You are not allowed to edit this label because you dont own it.",
"success": "La etiqueta se ha actualizado correctamente."
"success": "The label was successfully updated."
},
"deleteSuccess": "La etiqueta se ha eliminado correctamente.",
"deleteSuccess": "The label was successfully deleted.",
"attributes": {
"title": "Título",
"titlePlaceholder": "El título de la etiqueta va aquí…",
"description": "Descripción",
"descriptionPlaceholder": "Descripción de la etiqueta",
"title": "Title",
"titlePlaceholder": "The label title goes here…",
"description": "Description",
"descriptionPlaceholder": "Label description",
"color": "Color"
}
},
@ -417,116 +424,118 @@
"navigation": {
"overview": "Overview",
"upcoming": "Upcoming",
"settings": "Opciones",
"settings": "Settings",
"imprint": "Imprint",
"privacy": "Política de privacidad"
"privacy": "Privacy Policy"
},
"misc": {
"loading": "Cargando…",
"save": "Guardar",
"delete": "Eliminar",
"confirm": "Confirmar",
"cancel": "Cancelar",
"refresh": "Recargar",
"disable": "Desactivar",
"copy": "Copiar al portapapeles",
"search": "Buscar",
"loading": "Loading…",
"save": "Save",
"delete": "Delete",
"confirm": "Confirm",
"cancel": "Cancel",
"refresh": "Refresh",
"disable": "Disable",
"copy": "Copy to clipboard",
"search": "Search",
"searchPlaceholder": "Type to search…",
"previous": "Anterior",
"next": "Siguiente",
"previous": "Previous",
"next": "Next",
"poweredBy": "Powered by Vikunja",
"info": "Información",
"create": "Crear",
"doit": "¡Hazlo!",
"saving": "Guardando…",
"saved": "¡Guardado!",
"default": "Predeterminado",
"close": "Cerrar",
"info": "Info",
"create": "Create",
"doit": "Do it!",
"saving": "Saving…",
"saved": "Saved!",
"default": "Default",
"close": "Close",
"download": "Download"
},
"input": {
"resetColor": "Restablecer color",
"resetColor": "Reset Color",
"datepicker": {
"today": "Hoy",
"tomorrow": "Mañana",
"nextMonday": "El próximo lunes",
"thisWeekend": "Este fin de semana",
"laterThisWeek": "Más tarde esta semana",
"nextWeek": "La próxima semana",
"chooseDate": "Elige una fecha"
"today": "Today",
"tomorrow": "Tomorrow",
"nextMonday": "Next Monday",
"thisWeekend": "This Weekend",
"laterThisWeek": "Later This Week",
"nextWeek": "Next Week",
"chooseDate": "Choose a date"
},
"editor": {
"edit": "Editar",
"done": "Hecho",
"edit": "Edit",
"done": "Done",
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"headingSmaller": "Heading Smaller",
"headingBigger": "Heading Bigger",
"bold": "Negrita",
"italic": "Cursiva",
"strikethrough": "Tachado",
"code": "Código",
"quote": "Cita",
"bold": "Bold",
"italic": "Italic",
"strikethrough": "Strikethrough",
"code": "Code",
"quote": "Quote",
"unorderedList": "Unordered List",
"orderedList": "Ordered List",
"cleanBlock": "Clean Block",
"link": "Enlace",
"image": "Imagen",
"table": "Tabla",
"link": "Link",
"image": "Image",
"table": "Table",
"horizontalRule": "Horizontal Rule",
"sideBySide": "Side By Side",
"guide": "Guide"
},
"multiselect": {
"createPlaceholder": "Crear nuevo",
"createPlaceholder": "Create new",
"selectPlaceholder": "Click or press enter to select"
}
},
"task": {
"task": "Tarea",
"new": "Crear una nueva tarea",
"delete": "Eliminar esta tarea",
"task": "Task",
"new": "Create a new task",
"delete": "Delete this task",
"createSuccess": "The task was successfully created.",
"addReminder": "Añadir un nuevo recordatorio…",
"addReminder": "Add a new reminder…",
"doneSuccess": "The task was successfully marked as done.",
"undoneSuccess": "The task was successfully un-marked as done.",
"openDetail": "Open task detail view",
"checklistTotal": "{checked} of {total} tasks",
"checklistAllDone": "{total} tasks",
"show": {
"titleCurrent": "Current Tasks",
"titleDates": "Tasks from {from} until {to}",
"noDates": "Show tasks without dates",
"current": "Current tasks",
"from": "Tasks from",
"until": "hasta",
"today": "Hoy",
"nextWeek": "La próxima semana",
"nextMonth": "El próximo mes",
"noTasks": "Nada que hacer - ¡Que tengas un buen día!"
"until": "until",
"today": "Today",
"nextWeek": "Next Week",
"nextMonth": "Next Month",
"noTasks": "Nothing to do - Have a nice day!"
},
"detail": {
"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",
"done": "¡Hecho!",
"done": "Done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
"updated": "Actualizado {0}",
"doneAt": "Hecho {0}",
"updated": "Updated {0}",
"doneAt": "Done {0}",
"updateSuccess": "The task was saved successfully.",
"deleteSuccess": "The task has been deleted successfully.",
"belongsToList": "This task belongs to list '{list}'",
"due": "Due {at}",
"delete": {
"header": "Eliminar esta tarea",
"header": "Delete this task",
"text1": "Are you sure you want to remove this task?",
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
},
"actions": {
"assign": "Assign this task to a user",
"label": "Añadir etiquetas",
"priority": "Establecer prioridad",
"label": "Add labels",
"priority": "Set Priority",
"dueDate": "Set Due Date",
"startDate": "Set a Start Date",
"endDate": "Set an End Date",
@ -535,7 +544,7 @@
"percentDone": "Set Percent Done",
"attachments": "Add attachments",
"relatedTasks": "Add task relations",
"moveList": "Mover tarea",
"moveList": "Move task",
"color": "Set task color",
"delete": "Delete task",
"favorite": "Save as favorite",
@ -664,7 +673,7 @@
"label4": "For example: {prefix}\"Label with spaces\".",
"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 @ to the task.",
"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.",
"dateAndTime": "Date and time",

886
src/i18n/lang/pt-PT.json Normal file
View File

@ -0,0 +1,886 @@
{
"home": {
"welcomeNight": "Good Night {username}",
"welcomeMorning": "Good Morning {username}",
"welcomeDay": "Hi {username}",
"welcomeEvening": "Good Evening {username}",
"lastViewed": "Last viewed",
"list": {
"newText": "You can create a new list for your new tasks:",
"new": "Create a new list",
"importText": "Or import your lists and tasks from other services into Vikunja:",
"import": "Import your data into Vikunja"
}
},
"404": {
"title": "Not found",
"text": "The page you requested does not exist."
},
"user": {
"auth": {
"username": "Username",
"usernameEmail": "Username Or Email Address",
"usernamePlaceholder": "e.g. frederick",
"email": "E-mail address",
"emailPlaceholder": "e.g. frederic@vikunja.io",
"password": "Password",
"passwordRepeat": "Retype your password",
"passwordPlaceholder": "e.g. •••••••••••",
"resetPassword": "Reset your password",
"resetPasswordAction": "Send me a password reset link",
"resetPasswordSuccess": "Check your inbox! You should have an e-mail with instructions on how to reset your password.",
"passwordsDontMatch": "Passwords don't match",
"confirmEmailSuccess": "You successfully confirmed your email! You can log in now.",
"totpTitle": "Two Factor Authentication Code",
"totpPlaceholder": "e.g. 123456",
"login": "Login",
"register": "Register",
"loginWith": "Log in with {provider}",
"authenticating": "Authenticating…",
"openIdStateError": "State does not match, refusing to continue!",
"openIdGeneralError": "An error occured while authenticating against the third party.",
"logout": "Logout"
},
"settings": {
"title": "Settings",
"newPasswordTitle": "Update Your Password",
"newPassword": "New Password",
"newPasswordConfirm": "New Password Confirmation",
"currentPassword": "Current Password",
"currentPasswordPlaceholder": "Your current password",
"passwordsDontMatch": "The new password and its confirmation don't match.",
"passwordUpdateSuccess": "The password was successfully updated.",
"updateEmailTitle": "Update Your E-Mail Address",
"updateEmailNew": "New Email Address",
"updateEmailSuccess": "Your email address was successfully updated. We've sent you a link to confirm it.",
"general": {
"title": "General Settings",
"name": "Name",
"newName": "The new Name",
"savedSuccess": "The settings were successfully updated.",
"emailReminders": "Send me reminders for tasks via Email",
"overdueReminders": "Send me reminders for overdue undone tasks via email each morning",
"discoverableByName": "Let other users find me when they search for my name",
"discoverableByEmail": "Let other users find me when they search for my full email",
"playSoundWhenDone": "Play a sound when marking tasks as done",
"weekStart": "Week starts on",
"weekStartSunday": "Sunday",
"weekStartMonday": "Monday",
"language": "Language",
"defaultList": "Default List"
},
"totp": {
"title": "Two Factor Authentication",
"enroll": "Enroll",
"finishSetupPart1": "To finish your setup, use this secret in your totp app (Google Authenticator or similar):",
"finishSetupPart2": "After that, enter a code from your app below.",
"scanQR": "Alternatively you can scan this QR code:",
"passcode": "Passcode",
"passcodePlaceholder": "A code generated by your totp application",
"setupSuccess": "You've sucessfully set up two factor authentication!",
"enterPassword": "Please Enter Your Password",
"disable": "Disable two factor authentication",
"confirmSuccess": "You've successfully confirmed your totp setup and can use it from now on!",
"disableSuccess": "Two factor authentication was sucessfully disabled."
},
"caldav": {
"title": "Caldav",
"howTo": "You can connect Vikunja to caldav clients to view and manage all tasks from different clients. Enter this url into your client:",
"more": "More information about caldav in Vikunja"
},
"avatar": {
"title": "Avatar",
"initials": "Initials",
"gravatar": "Gravatar",
"upload": "Upload",
"uploadAvatar": "Upload Avatar",
"statusUpdateSuccess": "Avatar status was updated successfully!",
"setSuccess": "The avatar has been set successfully!"
},
"quickAddMagic": {
"title": "Quick Add Magic Mode",
"disabled": "Disabled",
"todoist": "Todoist",
"vikunja": "Vikunja"
}
},
"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.",
"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.",
"passwordRequired": "Please enter your password.",
"confirmSuccess": "You've successfully confirmed the deletion of your account. We will delete your account in three days.",
"scheduled": "We will delete your Vikunja account at {date} ({dateSince}).",
"scheduledCancel": "To cancel the deletion of your account, click here.",
"scheduledCancelText": "To cancel the deletion of your account, please enter your password below:",
"scheduledCancelConfirm": "Cancel the deletion of my account",
"scheduledCancelSuccess": "We will not delete your account."
},
"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.",
"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 or it.",
"title": "List Title",
"color": "Color",
"lists": "Lists",
"search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists",
"create": {
"header": "Create a new list",
"titlePlaceholder": "The list'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."
},
"archive": {
"title": "Archive \"{list}\"",
"archive": "Archive this list",
"unarchive": "Un-Archive this list",
"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."
},
"background": {
"title": "Set list 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": "Delete \"{list}\"",
"header": "Delete this list",
"text1": "Are you sure you want to delete this list and all of its contents?",
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The list was successfully deleted."
},
"duplicate": {
"title": "Duplicate this list",
"label": "Duplicate",
"text": "Select a namespace which should hold the duplicated list:",
"success": "The list 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…",
"description": "Description",
"descriptionPlaceholder": "The lists description goes here…",
"color": "Color",
"success": "The list was successfully updated."
},
"share": {
"header": "Share this list",
"title": "Share \"{list}\"",
"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.",
"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 list with this link share. This cannot be undone!",
"createSuccess": "The link share was successfully created.",
"deleteSuccess": "The link share was successfully deleted"
},
"userTeam": {
"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": "Right",
"read": "Read only",
"readWrite": "Read & write",
"admin": "Admin"
},
"attributes": {
"link": "Link",
"name": "Name",
"sharedBy": "Shared by",
"right": "Right",
"delete": "Delete"
}
},
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": {
"title": "Gantt",
"showTasksWithoutDates": "Show tasks which don't have dates set",
"size": "Size",
"default": "Default",
"month": "Month",
"day": "Day",
"from": "From",
"to": "To",
"noDates": "This task has no dates set."
},
"table": {
"title": "Table",
"columns": "Columns"
},
"kanban": {
"title": "Kanban",
"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": "Favorites"
}
}
},
"namespace": {
"title": "Namespaces & Lists",
"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.",
"namespaces": "Namespaces",
"search": "Type to search for a namespace…",
"create": {
"title": "Create a 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 namepace.",
"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.",
"success": "The namespace was successfully archived.",
"description": "If a namespace is archived, you cannot create new lists 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!",
"success": "The namespace was successfully deleted."
},
"edit": {
"title": "Edit \"{namespace}\"",
"success": "The namespace was successfully updated."
},
"share": {
"title": "Share \"{namespace}\""
},
"attributes": {
"title": "Namespace Title",
"titlePlaceholder": "The namespace title goes here…",
"description": "Description",
"descriptionPlaceholder": "The namespaces description goes here…",
"color": "Color",
"archived": "Is Archived",
"isArchived": "This namespace is archived"
},
"pseudo": {
"sharedLists": {
"title": "Shared Lists"
},
"favorites": {
"title": "Favorites"
},
"savedFilters": {
"title": "Filters"
}
}
},
"filters": {
"title": "Filters",
"attributes": {
"title": "Title",
"titlePlaceholder": "The saved filter title goes here…",
"description": "Description",
"descriptionPlaceholder": "The description goes here…",
"includeNulls": "Include Tasks which don't have a value set",
"requireAll": "Require all filters to be true for a task to show up",
"showDoneTasks": "Show Done Tasks",
"enablePriority": "Enable Filter By Priority",
"enablePercentDone": "Enable Filter By Percent Done",
"dueDateRange": "Due Date Range",
"startDateRange": "Start Date Range",
"endDateRange": "End Date Range",
"reminderRange": "Reminder Date Range"
},
"create": {
"title": "Create A 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"
},
"delete": {
"header": "Delete this saved filter",
"text": "Are you sure you want to delete this saved filter?",
"success": "The filter was deleted successfully."
},
"edit": {
"title": "Edit This Saved Filter",
"success": "The filter was saved successfully."
}
},
"migrate": {
"title": "Migrate from other services to Vikunja",
"titleService": "Import your data from {name} into Vikunja",
"import": "Import your data into Vikunja",
"description": "Click on the logo of one of the third-party services below to get started.",
"descriptionDo": "Vikunja will import all lists, tasks, notes, reminders and files you have access to.",
"authorize": "To authorize Vikunja to access your {name} Account, click the button below.",
"getStarted": "Get Started",
"inProgress": "Importing in progress…",
"alreadyMigrated1": "It looks like you've already imported your stuff from {name} at {date}.",
"alreadyMigrated2": "Importing again is possible, but might create duplicates. Are you sure?",
"confirm": "I am sure, please start migrating now!",
"importUpload": "To import data from {name} into Vikunja, click the button below to select a file.",
"upload": "Upload file"
},
"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.",
"newCTA": "You currently do not have any labels.",
"search": "Type to search for a label…",
"create": {
"header": "New label",
"title": "Create a new label",
"titleRequired": "Please specify a title.",
"success": "The label was successfully created."
},
"edit": {
"header": "Edit Label",
"forbidden": "You are not allowed to edit this label because you dont own it.",
"success": "The label was successfully updated."
},
"deleteSuccess": "The label was successfully deleted.",
"attributes": {
"title": "Title",
"titlePlaceholder": "The label title goes here…",
"description": "Description",
"descriptionPlaceholder": "Label description",
"color": "Color"
}
},
"sharing": {
"authenticating": "Authenticating…",
"passwordRequired": "This shared list requires a password. Please enter it below:",
"error": "An error occured.",
"invalidPassword": "The password is invalid."
},
"navigation": {
"overview": "Overview",
"upcoming": "Upcoming",
"settings": "Settings",
"imprint": "Imprint",
"privacy": "Privacy Policy"
},
"misc": {
"loading": "Loading…",
"save": "Save",
"delete": "Delete",
"confirm": "Confirm",
"cancel": "Cancel",
"refresh": "Refresh",
"disable": "Disable",
"copy": "Copy to clipboard",
"search": "Search",
"searchPlaceholder": "Type to search…",
"previous": "Previous",
"next": "Next",
"poweredBy": "Powered by Vikunja",
"info": "Info",
"create": "Create",
"doit": "Do it!",
"saving": "Saving…",
"saved": "Saved!",
"default": "Default",
"close": "Close",
"download": "Download"
},
"input": {
"resetColor": "Reset Color",
"datepicker": {
"today": "Today",
"tomorrow": "Tomorrow",
"nextMonday": "Next Monday",
"thisWeekend": "This Weekend",
"laterThisWeek": "Later This Week",
"nextWeek": "Next Week",
"chooseDate": "Choose a date"
},
"editor": {
"edit": "Edit",
"done": "Done",
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"headingSmaller": "Heading Smaller",
"headingBigger": "Heading Bigger",
"bold": "Bold",
"italic": "Italic",
"strikethrough": "Strikethrough",
"code": "Code",
"quote": "Quote",
"unorderedList": "Unordered List",
"orderedList": "Ordered List",
"cleanBlock": "Clean Block",
"link": "Link",
"image": "Image",
"table": "Table",
"horizontalRule": "Horizontal Rule",
"sideBySide": "Side By Side",
"guide": "Guide"
},
"multiselect": {
"createPlaceholder": "Create new",
"selectPlaceholder": "Click or press enter to select"
}
},
"task": {
"task": "Task",
"new": "Create a new task",
"delete": "Delete this task",
"createSuccess": "The task was successfully created.",
"addReminder": "Add a new reminder…",
"doneSuccess": "The task was successfully marked as done.",
"undoneSuccess": "The task was successfully un-marked as done.",
"openDetail": "Open task detail view",
"checklistTotal": "{checked} of {total} tasks",
"checklistAllDone": "{total} tasks",
"show": {
"titleCurrent": "Current Tasks",
"titleDates": "Tasks from {from} until {to}",
"noDates": "Show tasks without dates",
"current": "Current tasks",
"from": "Tasks from",
"until": "until",
"today": "Today",
"nextWeek": "Next Week",
"nextMonth": "Next Month",
"noTasks": "Nothing to do - Have a nice day!"
},
"detail": {
"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",
"done": "Done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
"updated": "Updated {0}",
"doneAt": "Done {0}",
"updateSuccess": "The task was saved successfully.",
"deleteSuccess": "The task has been deleted successfully.",
"belongsToList": "This task belongs to list '{list}'",
"due": "Due {at}",
"delete": {
"header": "Delete this task",
"text1": "Are you sure you want to remove this task?",
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
},
"actions": {
"assign": "Assign this task to a user",
"label": "Add labels",
"priority": "Set Priority",
"dueDate": "Set Due Date",
"startDate": "Set a Start Date",
"endDate": "Set an End Date",
"reminders": "Set Reminders",
"repeatAfter": "Set a repeating interval",
"percentDone": "Set Percent Done",
"attachments": "Add attachments",
"relatedTasks": "Add task relations",
"moveList": "Move task",
"color": "Set task color",
"delete": "Delete task",
"favorite": "Save as favorite",
"unfavorite": "Remove from favorites"
}
},
"attributes": {
"assignees": "Assignees",
"color": "Color",
"created": "Created",
"createdBy": "Created By",
"description": "Description",
"done": "Done",
"dueDate": "Due Date",
"endDate": "End Date",
"labels": "Labels",
"percentDone": "% Done",
"priority": "Priority",
"relatedTasks": "Related Tasks",
"reminders": "Reminders",
"repeat": "Repeat",
"startDate": "Start Date",
"title": "Title",
"updated": "Updated"
},
"subscription": {
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
"subscribe": "Subscribe",
"unsubscribe": "Unsubscribe",
"subscribeSuccess": "You are now subscribed to this {entity}",
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
},
"attachment": {
"title": "Attachments",
"createdBy": "created {0} by {1}",
"downloadTooltip": "Download this attachment",
"upload": "Upload attachment",
"drop": "Drop files here to upload",
"delete": "Delete attachment",
"deleteTooltip": "Delete this attachment",
"deleteText1": "Are you sure you want to delete the attachment {filename}?",
"deleteText2": "This cannot be undone!",
"copyUrl": "Copy URL",
"copyUrlTooltip": "Copy the url of this attachment for usage in text"
},
"comment": {
"title": "Comments",
"loading": "Loading comments…",
"edited": "edited {date}",
"creating": "Creating comment…",
"placeholder": "Add your comment…",
"comment": "Comment",
"delete": "Delete this comment",
"deleteText1": "Are you sure you want to delete this comment?",
"deleteText2": "This cannot be undone!",
"addedSuccess": "The comment was added successfully."
},
"deferDueDate": {
"title": "Defer due date",
"1day": "1 day",
"3days": "3 days",
"1week": "1 week"
},
"description": {
"placeholder": "Click here to enter a description…",
"empty": "No description available yet."
},
"assignee": {
"placeholder": "Type to assign a user…",
"selectPlaceholder": "Assign this user",
"assignSuccess": "The user has been assigned successfully.",
"unassignSuccess": "The user has been unassigned successfully."
},
"label": {
"placeholder": "Type to add a new label…",
"createPlaceholder": "Add this as new label",
"addSuccess": "The label has been added successfully.",
"createSuccess": "The label has been created successfully.",
"removeSuccess": "The label has been removed successfully."
},
"priority": {
"unset": "Unset",
"low": "Low",
"medium": "Medium",
"high": "high",
"urgent": "Urgent",
"doNow": "DO NOW"
},
"relation": {
"add": "Add a New Task Relation",
"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.",
"noneYet": "No task relations yet.",
"delete": "Delete Task Relation",
"deleteText1": "Are you sure you want to delete this task relation?",
"deleteText2": "This cannot be undone!"
},
"repeat": {
"everyDay": "Every Day",
"everyWeek": "Every Week",
"everyMonth": "Every Month",
"mode": "Repeat mode",
"monthly": "Monthly",
"fromCurrentDate": "From Current Date",
"each": "Each",
"specifyAmount": "Specify an amount…",
"hours": "Hours",
"days": "Days",
"weeks": "Weeks",
"months": "Months",
"years": "Years"
},
"quickAddMagic": {
"hint": "You can use Quick Add Magic",
"what": "What?",
"title": "Quick Add Magic",
"intro": "When creating a task, you can use special keywords to directly add attributes to the newly created task. This allows to add commonly used attributes to tasks much faster.",
"multiple": "You can use this multiple times.",
"label1": "To add a label, simply prefix the name of the label with {prefix}.",
"label2": "Vikunja will first check if the label already exist and create it if not.",
"label3": "To use spaces, simply add a \" around the label name.",
"label4": "For example: {prefix}\"Label with spaces\".",
"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.",
"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",
"dateCurrentYear": "will use the current year",
"dateNth": "will use the {day}th of the current month",
"dateTime": "Combine any of the date formats with \"{time}\" (or {timePM}) to set a time."
}
},
"team": {
"title": "Teams",
"noTeams": "You are currently not part of any teams.",
"create": {
"title": "Create a new team",
"success": "The team was successfully created."
},
"edit": {
"title": "Edit Team \"{team}\"",
"members": "Team Members",
"search": "Type to search a user…",
"addUser": "Add to team",
"makeMember": "Make Member",
"makeAdmin": "Make Admin",
"success": "The team was successfully updated.",
"userAddedSuccess": "The team member was successfully added.",
"madeMember": "The team member was successfully made member.",
"madeAdmin": "The team member was successfully made admin.",
"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 loose access to lists 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 loose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team."
}
},
"attributes": {
"name": "Team Name",
"namePlaceholder": "The team's name goes here…",
"nameRequired": "Please specify a name.",
"description": "Description",
"descriptionPlaceholder": "The teams description goes here…",
"admin": "Admin",
"member": "Member"
}
},
"keyboardShortcuts": {
"title": "Keyboard Shortcuts",
"allPages": "These shortcuts work on all pages.",
"currentPageOnly": "These shortcuts work only on the current page.",
"toggleMenu": "Toggle The Menu",
"quickSearch": "Open the search/quick action bar",
"task": {
"title": "Task Page",
"done": "Mark a task as done",
"assign": "Assign this task to a user",
"labels": "Add labels to this task",
"dueDate": "Change the due date of this task",
"attachment": "Add an attachment to this task",
"related": "Modify related tasks of this task"
}
},
"update": {
"available": "There is an update for Vikunja available!",
"do": "Update Now"
},
"menu": {
"edit": "Edit",
"archive": "Archive",
"duplicate": "Duplicate",
"delete": "Delete",
"unarchive": "Un-Archive",
"setBackground": "Set background",
"share": "Share",
"newList": "New list"
},
"apiConfig": {
"url": "Vikunja URL",
"urlPlaceholder": "eg. https://localhost:3456",
"change": "change",
"signInOn": "Sign in to your Vikunja account on {0}",
"error": "Could not find or use Vikunja installation at \"{domain}\".",
"success": "Using Vikunja installation at \"{domain}\"."
},
"loadingError": {
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
"tryAgain": "try again",
"contact": "contact us"
},
"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."
},
"quickActions": {
"commands": "Commands",
"placeholder": "Type a command or search…",
"hint": "You can use # to only seach for tasks, * to only search for lists and @ to only search for teams.",
"tasks": "Tasks",
"lists": "Lists",
"teams": "Teams",
"newList": "Enter the title of the new list…",
"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})",
"cmds": {
"newTask": "New task",
"newList": "New list",
"newNamespace": "New namespace",
"newTeam": "New team"
}
},
"date": {
"locale": "en",
"in": "in {date}",
"ago": "{date} ago",
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
"error": {
"error": "Error",
"success": "Success",
"0001": "You're not allowed to do that.",
"1001": "A user with this username already exists.",
"1002": "A user with this email address already exists.",
"1004": "No username and password specified.",
"1005": "The user does not exist.",
"1006": "Could not get the user id.",
"1008": "No password reset token provided.",
"1009": "Invalid password reset token.",
"1010": "Invalid email confirm token.",
"1011": "Wrong username or password.",
"1012": "Email address of the user not confirmed.",
"1013": "New password is empty.",
"1014": "Old password is empty.",
"1015": "Totp is already enabled for this user.",
"1016": "Totp is not enabled for this user.",
"1017": "The totp passcode is invalid.",
"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.",
"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.",
"4007": "You can't create a task relation with an invalid kind of relation.",
"4008": "You can't create a task relation which already exists.",
"4009": "The task relation does not exist.",
"4010": "Cannot relate a task with itself.",
"4011": "The task attachment does not exist.",
"4012": "The task attachment is too large.",
"4013": "The task sort param is invalid.",
"4014": "The task sort order is invalid.",
"4015": "The task comment does not exist.",
"4016": "Invalid task field.",
"4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.",
"4019": "Invalid task filter value.",
"5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.",
"5006": "The namespace name cannot be empty.",
"5009": "You need to have namespace read access to perform that action.",
"5010": "This team does not have access to that namespace.",
"5011": "This user has already access to that namespace.",
"5012": "The namespace is archived and can therefore only be accessed read only.",
"6001": "The team name cannot be emtpy.",
"6002": "The team does not exist.",
"6004": "The team already has access to that namespace or list.",
"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.",
"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.",
"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.",
"11001": "The saved filter does not exist.",
"11002": "Saved filters are not available for link shares.",
"12001": "The subscription entity type is invalid.",
"12002": "You are already subscribed to the entity itself or a parent entity.",
"13001": "This link share requires a password for authentication, but none was provided.",
"13002": "The provided link share password was invalid."
},
"about": {
"title": "About",
"frontendVersion": "Frontend Version: {version}",
"apiVersion": "API Version: {version}"
}
}

886
src/i18n/lang/ro-RO.json Normal file
View File

@ -0,0 +1,886 @@
{
"home": {
"welcomeNight": "Good Night {username}",
"welcomeMorning": "Good Morning {username}",
"welcomeDay": "Hi {username}",
"welcomeEvening": "Good Evening {username}",
"lastViewed": "Last viewed",
"list": {
"newText": "You can create a new list for your new tasks:",
"new": "Create a new list",
"importText": "Or import your lists and tasks from other services into Vikunja:",
"import": "Import your data into Vikunja"
}
},
"404": {
"title": "Not found",
"text": "The page you requested does not exist."
},
"user": {
"auth": {
"username": "Username",
"usernameEmail": "Username Or Email Address",
"usernamePlaceholder": "e.g. frederick",
"email": "E-mail address",
"emailPlaceholder": "e.g. frederic@vikunja.io",
"password": "Password",
"passwordRepeat": "Retype your password",
"passwordPlaceholder": "e.g. •••••••••••",
"resetPassword": "Reset your password",
"resetPasswordAction": "Send me a password reset link",
"resetPasswordSuccess": "Check your inbox! You should have an e-mail with instructions on how to reset your password.",
"passwordsDontMatch": "Passwords don't match",
"confirmEmailSuccess": "You successfully confirmed your email! You can log in now.",
"totpTitle": "Two Factor Authentication Code",
"totpPlaceholder": "e.g. 123456",
"login": "Login",
"register": "Register",
"loginWith": "Log in with {provider}",
"authenticating": "Authenticating…",
"openIdStateError": "State does not match, refusing to continue!",
"openIdGeneralError": "An error occured while authenticating against the third party.",
"logout": "Logout"
},
"settings": {
"title": "Settings",
"newPasswordTitle": "Update Your Password",
"newPassword": "New Password",
"newPasswordConfirm": "New Password Confirmation",
"currentPassword": "Current Password",
"currentPasswordPlaceholder": "Your current password",
"passwordsDontMatch": "The new password and its confirmation don't match.",
"passwordUpdateSuccess": "The password was successfully updated.",
"updateEmailTitle": "Update Your E-Mail Address",
"updateEmailNew": "New Email Address",
"updateEmailSuccess": "Your email address was successfully updated. We've sent you a link to confirm it.",
"general": {
"title": "General Settings",
"name": "Name",
"newName": "The new Name",
"savedSuccess": "The settings were successfully updated.",
"emailReminders": "Send me reminders for tasks via Email",
"overdueReminders": "Send me reminders for overdue undone tasks via email each morning",
"discoverableByName": "Let other users find me when they search for my name",
"discoverableByEmail": "Let other users find me when they search for my full email",
"playSoundWhenDone": "Play a sound when marking tasks as done",
"weekStart": "Week starts on",
"weekStartSunday": "Sunday",
"weekStartMonday": "Monday",
"language": "Language",
"defaultList": "Default List"
},
"totp": {
"title": "Two Factor Authentication",
"enroll": "Enroll",
"finishSetupPart1": "To finish your setup, use this secret in your totp app (Google Authenticator or similar):",
"finishSetupPart2": "After that, enter a code from your app below.",
"scanQR": "Alternatively you can scan this QR code:",
"passcode": "Passcode",
"passcodePlaceholder": "A code generated by your totp application",
"setupSuccess": "You've sucessfully set up two factor authentication!",
"enterPassword": "Please Enter Your Password",
"disable": "Disable two factor authentication",
"confirmSuccess": "You've successfully confirmed your totp setup and can use it from now on!",
"disableSuccess": "Two factor authentication was sucessfully disabled."
},
"caldav": {
"title": "Caldav",
"howTo": "You can connect Vikunja to caldav clients to view and manage all tasks from different clients. Enter this url into your client:",
"more": "More information about caldav in Vikunja"
},
"avatar": {
"title": "Avatar",
"initials": "Initials",
"gravatar": "Gravatar",
"upload": "Upload",
"uploadAvatar": "Upload Avatar",
"statusUpdateSuccess": "Avatar status was updated successfully!",
"setSuccess": "The avatar has been set successfully!"
},
"quickAddMagic": {
"title": "Quick Add Magic Mode",
"disabled": "Disabled",
"todoist": "Todoist",
"vikunja": "Vikunja"
}
},
"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.",
"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.",
"passwordRequired": "Please enter your password.",
"confirmSuccess": "You've successfully confirmed the deletion of your account. We will delete your account in three days.",
"scheduled": "We will delete your Vikunja account at {date} ({dateSince}).",
"scheduledCancel": "To cancel the deletion of your account, click here.",
"scheduledCancelText": "To cancel the deletion of your account, please enter your password below:",
"scheduledCancelConfirm": "Cancel the deletion of my account",
"scheduledCancelSuccess": "We will not delete your account."
},
"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.",
"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 or it.",
"title": "List Title",
"color": "Color",
"lists": "Lists",
"search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists",
"create": {
"header": "Create a new list",
"titlePlaceholder": "The list'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."
},
"archive": {
"title": "Archive \"{list}\"",
"archive": "Archive this list",
"unarchive": "Un-Archive this list",
"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."
},
"background": {
"title": "Set list 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": "Delete \"{list}\"",
"header": "Delete this list",
"text1": "Are you sure you want to delete this list and all of its contents?",
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The list was successfully deleted."
},
"duplicate": {
"title": "Duplicate this list",
"label": "Duplicate",
"text": "Select a namespace which should hold the duplicated list:",
"success": "The list 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…",
"description": "Description",
"descriptionPlaceholder": "The lists description goes here…",
"color": "Color",
"success": "The list was successfully updated."
},
"share": {
"header": "Share this list",
"title": "Share \"{list}\"",
"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.",
"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 list with this link share. This cannot be undone!",
"createSuccess": "The link share was successfully created.",
"deleteSuccess": "The link share was successfully deleted"
},
"userTeam": {
"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": "Right",
"read": "Read only",
"readWrite": "Read & write",
"admin": "Admin"
},
"attributes": {
"link": "Link",
"name": "Name",
"sharedBy": "Shared by",
"right": "Right",
"delete": "Delete"
}
},
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": {
"title": "Gantt",
"showTasksWithoutDates": "Show tasks which don't have dates set",
"size": "Size",
"default": "Default",
"month": "Month",
"day": "Day",
"from": "From",
"to": "To",
"noDates": "This task has no dates set."
},
"table": {
"title": "Table",
"columns": "Columns"
},
"kanban": {
"title": "Kanban",
"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": "Favorites"
}
}
},
"namespace": {
"title": "Namespaces & Lists",
"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.",
"namespaces": "Namespaces",
"search": "Type to search for a namespace…",
"create": {
"title": "Create a 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 namepace.",
"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.",
"success": "The namespace was successfully archived.",
"description": "If a namespace is archived, you cannot create new lists 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!",
"success": "The namespace was successfully deleted."
},
"edit": {
"title": "Edit \"{namespace}\"",
"success": "The namespace was successfully updated."
},
"share": {
"title": "Share \"{namespace}\""
},
"attributes": {
"title": "Namespace Title",
"titlePlaceholder": "The namespace title goes here…",
"description": "Description",
"descriptionPlaceholder": "The namespaces description goes here…",
"color": "Color",
"archived": "Is Archived",
"isArchived": "This namespace is archived"
},
"pseudo": {
"sharedLists": {
"title": "Shared Lists"
},
"favorites": {
"title": "Favorites"
},
"savedFilters": {
"title": "Filters"
}
}
},
"filters": {
"title": "Filters",
"attributes": {
"title": "Title",
"titlePlaceholder": "The saved filter title goes here…",
"description": "Description",
"descriptionPlaceholder": "The description goes here…",
"includeNulls": "Include Tasks which don't have a value set",
"requireAll": "Require all filters to be true for a task to show up",
"showDoneTasks": "Show Done Tasks",
"enablePriority": "Enable Filter By Priority",
"enablePercentDone": "Enable Filter By Percent Done",
"dueDateRange": "Due Date Range",
"startDateRange": "Start Date Range",
"endDateRange": "End Date Range",
"reminderRange": "Reminder Date Range"
},
"create": {
"title": "Create A 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"
},
"delete": {
"header": "Delete this saved filter",
"text": "Are you sure you want to delete this saved filter?",
"success": "The filter was deleted successfully."
},
"edit": {
"title": "Edit This Saved Filter",
"success": "The filter was saved successfully."
}
},
"migrate": {
"title": "Migrate from other services to Vikunja",
"titleService": "Import your data from {name} into Vikunja",
"import": "Import your data into Vikunja",
"description": "Click on the logo of one of the third-party services below to get started.",
"descriptionDo": "Vikunja will import all lists, tasks, notes, reminders and files you have access to.",
"authorize": "To authorize Vikunja to access your {name} Account, click the button below.",
"getStarted": "Get Started",
"inProgress": "Importing in progress…",
"alreadyMigrated1": "It looks like you've already imported your stuff from {name} at {date}.",
"alreadyMigrated2": "Importing again is possible, but might create duplicates. Are you sure?",
"confirm": "I am sure, please start migrating now!",
"importUpload": "To import data from {name} into Vikunja, click the button below to select a file.",
"upload": "Upload file"
},
"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.",
"newCTA": "You currently do not have any labels.",
"search": "Type to search for a label…",
"create": {
"header": "New label",
"title": "Create a new label",
"titleRequired": "Please specify a title.",
"success": "The label was successfully created."
},
"edit": {
"header": "Edit Label",
"forbidden": "You are not allowed to edit this label because you dont own it.",
"success": "The label was successfully updated."
},
"deleteSuccess": "The label was successfully deleted.",
"attributes": {
"title": "Title",
"titlePlaceholder": "The label title goes here…",
"description": "Description",
"descriptionPlaceholder": "Label description",
"color": "Color"
}
},
"sharing": {
"authenticating": "Authenticating…",
"passwordRequired": "This shared list requires a password. Please enter it below:",
"error": "An error occured.",
"invalidPassword": "The password is invalid."
},
"navigation": {
"overview": "Overview",
"upcoming": "Upcoming",
"settings": "Settings",
"imprint": "Imprint",
"privacy": "Privacy Policy"
},
"misc": {
"loading": "Loading…",
"save": "Save",
"delete": "Delete",
"confirm": "Confirm",
"cancel": "Cancel",
"refresh": "Refresh",
"disable": "Disable",
"copy": "Copy to clipboard",
"search": "Search",
"searchPlaceholder": "Type to search…",
"previous": "Previous",
"next": "Next",
"poweredBy": "Powered by Vikunja",
"info": "Info",
"create": "Create",
"doit": "Do it!",
"saving": "Saving…",
"saved": "Saved!",
"default": "Default",
"close": "Close",
"download": "Download"
},
"input": {
"resetColor": "Reset Color",
"datepicker": {
"today": "Today",
"tomorrow": "Tomorrow",
"nextMonday": "Next Monday",
"thisWeekend": "This Weekend",
"laterThisWeek": "Later This Week",
"nextWeek": "Next Week",
"chooseDate": "Choose a date"
},
"editor": {
"edit": "Edit",
"done": "Done",
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"headingSmaller": "Heading Smaller",
"headingBigger": "Heading Bigger",
"bold": "Bold",
"italic": "Italic",
"strikethrough": "Strikethrough",
"code": "Code",
"quote": "Quote",
"unorderedList": "Unordered List",
"orderedList": "Ordered List",
"cleanBlock": "Clean Block",
"link": "Link",
"image": "Image",
"table": "Table",
"horizontalRule": "Horizontal Rule",
"sideBySide": "Side By Side",
"guide": "Guide"
},
"multiselect": {
"createPlaceholder": "Create new",
"selectPlaceholder": "Click or press enter to select"
}
},
"task": {
"task": "Task",
"new": "Create a new task",
"delete": "Delete this task",
"createSuccess": "The task was successfully created.",
"addReminder": "Add a new reminder…",
"doneSuccess": "The task was successfully marked as done.",
"undoneSuccess": "The task was successfully un-marked as done.",
"openDetail": "Open task detail view",
"checklistTotal": "{checked} of {total} tasks",
"checklistAllDone": "{total} tasks",
"show": {
"titleCurrent": "Current Tasks",
"titleDates": "Tasks from {from} until {to}",
"noDates": "Show tasks without dates",
"current": "Current tasks",
"from": "Tasks from",
"until": "until",
"today": "Today",
"nextWeek": "Next Week",
"nextMonth": "Next Month",
"noTasks": "Nothing to do - Have a nice day!"
},
"detail": {
"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",
"done": "Done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
"updated": "Updated {0}",
"doneAt": "Done {0}",
"updateSuccess": "The task was saved successfully.",
"deleteSuccess": "The task has been deleted successfully.",
"belongsToList": "This task belongs to list '{list}'",
"due": "Due {at}",
"delete": {
"header": "Delete this task",
"text1": "Are you sure you want to remove this task?",
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
},
"actions": {
"assign": "Assign this task to a user",
"label": "Add labels",
"priority": "Set Priority",
"dueDate": "Set Due Date",
"startDate": "Set a Start Date",
"endDate": "Set an End Date",
"reminders": "Set Reminders",
"repeatAfter": "Set a repeating interval",
"percentDone": "Set Percent Done",
"attachments": "Add attachments",
"relatedTasks": "Add task relations",
"moveList": "Move task",
"color": "Set task color",
"delete": "Delete task",
"favorite": "Save as favorite",
"unfavorite": "Remove from favorites"
}
},
"attributes": {
"assignees": "Assignees",
"color": "Color",
"created": "Created",
"createdBy": "Created By",
"description": "Description",
"done": "Done",
"dueDate": "Due Date",
"endDate": "End Date",
"labels": "Labels",
"percentDone": "% Done",
"priority": "Priority",
"relatedTasks": "Related Tasks",
"reminders": "Reminders",
"repeat": "Repeat",
"startDate": "Start Date",
"title": "Title",
"updated": "Updated"
},
"subscription": {
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
"subscribe": "Subscribe",
"unsubscribe": "Unsubscribe",
"subscribeSuccess": "You are now subscribed to this {entity}",
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
},
"attachment": {
"title": "Attachments",
"createdBy": "created {0} by {1}",
"downloadTooltip": "Download this attachment",
"upload": "Upload attachment",
"drop": "Drop files here to upload",
"delete": "Delete attachment",
"deleteTooltip": "Delete this attachment",
"deleteText1": "Are you sure you want to delete the attachment {filename}?",
"deleteText2": "This cannot be undone!",
"copyUrl": "Copy URL",
"copyUrlTooltip": "Copy the url of this attachment for usage in text"
},
"comment": {
"title": "Comments",
"loading": "Loading comments…",
"edited": "edited {date}",
"creating": "Creating comment…",
"placeholder": "Add your comment…",
"comment": "Comment",
"delete": "Delete this comment",
"deleteText1": "Are you sure you want to delete this comment?",
"deleteText2": "This cannot be undone!",
"addedSuccess": "The comment was added successfully."
},
"deferDueDate": {
"title": "Defer due date",
"1day": "1 day",
"3days": "3 days",
"1week": "1 week"
},
"description": {
"placeholder": "Click here to enter a description…",
"empty": "No description available yet."
},
"assignee": {
"placeholder": "Type to assign a user…",
"selectPlaceholder": "Assign this user",
"assignSuccess": "The user has been assigned successfully.",
"unassignSuccess": "The user has been unassigned successfully."
},
"label": {
"placeholder": "Type to add a new label…",
"createPlaceholder": "Add this as new label",
"addSuccess": "The label has been added successfully.",
"createSuccess": "The label has been created successfully.",
"removeSuccess": "The label has been removed successfully."
},
"priority": {
"unset": "Unset",
"low": "Low",
"medium": "Medium",
"high": "high",
"urgent": "Urgent",
"doNow": "DO NOW"
},
"relation": {
"add": "Add a New Task Relation",
"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.",
"noneYet": "No task relations yet.",
"delete": "Delete Task Relation",
"deleteText1": "Are you sure you want to delete this task relation?",
"deleteText2": "This cannot be undone!"
},
"repeat": {
"everyDay": "Every Day",
"everyWeek": "Every Week",
"everyMonth": "Every Month",
"mode": "Repeat mode",
"monthly": "Monthly",
"fromCurrentDate": "From Current Date",
"each": "Each",
"specifyAmount": "Specify an amount…",
"hours": "Hours",
"days": "Days",
"weeks": "Weeks",
"months": "Months",
"years": "Years"
},
"quickAddMagic": {
"hint": "You can use Quick Add Magic",
"what": "What?",
"title": "Quick Add Magic",
"intro": "When creating a task, you can use special keywords to directly add attributes to the newly created task. This allows to add commonly used attributes to tasks much faster.",
"multiple": "You can use this multiple times.",
"label1": "To add a label, simply prefix the name of the label with {prefix}.",
"label2": "Vikunja will first check if the label already exist and create it if not.",
"label3": "To use spaces, simply add a \" around the label name.",
"label4": "For example: {prefix}\"Label with spaces\".",
"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.",
"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",
"dateCurrentYear": "will use the current year",
"dateNth": "will use the {day}th of the current month",
"dateTime": "Combine any of the date formats with \"{time}\" (or {timePM}) to set a time."
}
},
"team": {
"title": "Teams",
"noTeams": "You are currently not part of any teams.",
"create": {
"title": "Create a new team",
"success": "The team was successfully created."
},
"edit": {
"title": "Edit Team \"{team}\"",
"members": "Team Members",
"search": "Type to search a user…",
"addUser": "Add to team",
"makeMember": "Make Member",
"makeAdmin": "Make Admin",
"success": "The team was successfully updated.",
"userAddedSuccess": "The team member was successfully added.",
"madeMember": "The team member was successfully made member.",
"madeAdmin": "The team member was successfully made admin.",
"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 loose access to lists 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 loose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team."
}
},
"attributes": {
"name": "Team Name",
"namePlaceholder": "The team's name goes here…",
"nameRequired": "Please specify a name.",
"description": "Description",
"descriptionPlaceholder": "The teams description goes here…",
"admin": "Admin",
"member": "Member"
}
},
"keyboardShortcuts": {
"title": "Keyboard Shortcuts",
"allPages": "These shortcuts work on all pages.",
"currentPageOnly": "These shortcuts work only on the current page.",
"toggleMenu": "Toggle The Menu",
"quickSearch": "Open the search/quick action bar",
"task": {
"title": "Task Page",
"done": "Mark a task as done",
"assign": "Assign this task to a user",
"labels": "Add labels to this task",
"dueDate": "Change the due date of this task",
"attachment": "Add an attachment to this task",
"related": "Modify related tasks of this task"
}
},
"update": {
"available": "There is an update for Vikunja available!",
"do": "Update Now"
},
"menu": {
"edit": "Edit",
"archive": "Archive",
"duplicate": "Duplicate",
"delete": "Delete",
"unarchive": "Un-Archive",
"setBackground": "Set background",
"share": "Share",
"newList": "New list"
},
"apiConfig": {
"url": "Vikunja URL",
"urlPlaceholder": "eg. https://localhost:3456",
"change": "change",
"signInOn": "Sign in to your Vikunja account on {0}",
"error": "Could not find or use Vikunja installation at \"{domain}\".",
"success": "Using Vikunja installation at \"{domain}\"."
},
"loadingError": {
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
"tryAgain": "try again",
"contact": "contact us"
},
"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."
},
"quickActions": {
"commands": "Commands",
"placeholder": "Type a command or search…",
"hint": "You can use # to only seach for tasks, * to only search for lists and @ to only search for teams.",
"tasks": "Tasks",
"lists": "Lists",
"teams": "Teams",
"newList": "Enter the title of the new list…",
"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})",
"cmds": {
"newTask": "New task",
"newList": "New list",
"newNamespace": "New namespace",
"newTeam": "New team"
}
},
"date": {
"locale": "en",
"in": "in {date}",
"ago": "{date} ago",
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
"error": {
"error": "Error",
"success": "Success",
"0001": "You're not allowed to do that.",
"1001": "A user with this username already exists.",
"1002": "A user with this email address already exists.",
"1004": "No username and password specified.",
"1005": "The user does not exist.",
"1006": "Could not get the user id.",
"1008": "No password reset token provided.",
"1009": "Invalid password reset token.",
"1010": "Invalid email confirm token.",
"1011": "Wrong username or password.",
"1012": "Email address of the user not confirmed.",
"1013": "New password is empty.",
"1014": "Old password is empty.",
"1015": "Totp is already enabled for this user.",
"1016": "Totp is not enabled for this user.",
"1017": "The totp passcode is invalid.",
"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.",
"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.",
"4007": "You can't create a task relation with an invalid kind of relation.",
"4008": "You can't create a task relation which already exists.",
"4009": "The task relation does not exist.",
"4010": "Cannot relate a task with itself.",
"4011": "The task attachment does not exist.",
"4012": "The task attachment is too large.",
"4013": "The task sort param is invalid.",
"4014": "The task sort order is invalid.",
"4015": "The task comment does not exist.",
"4016": "Invalid task field.",
"4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.",
"4019": "Invalid task filter value.",
"5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.",
"5006": "The namespace name cannot be empty.",
"5009": "You need to have namespace read access to perform that action.",
"5010": "This team does not have access to that namespace.",
"5011": "This user has already access to that namespace.",
"5012": "The namespace is archived and can therefore only be accessed read only.",
"6001": "The team name cannot be emtpy.",
"6002": "The team does not exist.",
"6004": "The team already has access to that namespace or list.",
"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.",
"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.",
"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.",
"11001": "The saved filter does not exist.",
"11002": "Saved filters are not available for link shares.",
"12001": "The subscription entity type is invalid.",
"12002": "You are already subscribed to the entity itself or a parent entity.",
"13001": "This link share requires a password for authentication, but none was provided.",
"13002": "The provided link share password was invalid."
},
"about": {
"title": "About",
"frontendVersion": "Frontend Version: {version}",
"apiVersion": "API Version: {version}"
}
}

View File

@ -1 +0,0 @@
{}

View File

@ -96,6 +96,12 @@
"uploadAvatar": "Загрузить аватар",
"statusUpdateSuccess": "Статус аватара обновлён.",
"setSuccess": "Аватар установлен."
},
"quickAddMagic": {
"title": "Quick Add Magic Mode",
"disabled": "Disabled",
"todoist": "Todoist",
"vikunja": "Vikunja"
}
},
"deletion": {
@ -113,11 +119,12 @@
"scheduledCancelSuccess": "Мы не будем удалять твой аккаунт."
},
"export": {
"title": "Экспорт данных Vikunja",
"title": "Export your Vikunja data",
"description": "Ты можешь запросить копию всех своих данных Vikunja. Это включает в себя пространства имён, списки, задачи и всё связанное с ними. Эти данные можно будет импортировать на любом экземпляре Vikunja через функцию миграции.",
"descriptionPasswordRequired": "Для продолжения введи свой пароль:",
"request": "Запросить копию моих данных Vikunja",
"success": "Данные Vikunja успешно запрошены! Мы отправим тебе письмо, когда они будут готовы для скачивания."
"success": "Данные Vikunja успешно запрошены! Мы отправим тебе письмо, когда они будут готовы для скачивания.",
"downloadTitle": "Download your exported Vikunja data"
}
},
"list": {
@ -492,6 +499,8 @@
"doneSuccess": "Задача отмечена как завершённая.",
"undoneSuccess": "Задача отмечена как незавершённая.",
"openDetail": "Открыть подробный просмотр задачи",
"checklistTotal": "{checked} of {total} tasks",
"checklistAllDone": "{total} tasks",
"show": {
"titleCurrent": "Текущие задачи",
"titleDates": "Задачи с {from} по {to}",
@ -664,7 +673,7 @@
"label4": "Например: {prefix}\"Метка с пробелами\".",
"priority1": "Чтобы установить задаче приоритет, добавь число 1-5 с префиксом {prefix}.",
"priority2": "Чем больше число, тем выше приоритет.",
"assignees": "Чтобы назначить задачу пользователю, добавь к задаче его имя с префиксом {prefix}.",
"assignees": "To directly assign the task to a user, add their username prefixed with {prefix} to the task.",
"list1": "Чтобы выбрать список, в который будет помещена создаваемая задача, введи его имя с префиксом {prefix}.",
"list2": "Если списка не существует, будет возвращена ошибка.",
"dateAndTime": "Дата и время",

View File

@ -1 +0,0 @@
{}

View File

@ -14,7 +14,10 @@ export const i18n = new VueI18n({
export const availableLanguages = {
en: 'English',
de: 'Deutsch',
'de-DE': 'Deutsch',
'de-swiss': 'Schwizertütsch',
'ru-RU': 'Русский',
'fr-FR': 'Français',
}
const loadedLanguages = ['en'] // our default language that is preloaded
@ -48,18 +51,16 @@ export const loadLanguageAsync = lang => {
export const getCurrentLanguage = () => {
const savedLanguage = localStorage.getItem('language')
if(savedLanguage !== null) {
if (savedLanguage !== null) {
return savedLanguage
}
let browserLanguage = navigator.language || navigator.userLanguage
if (browserLanguage.startsWith('en-')) {
browserLanguage = 'en'
}
if (typeof availableLanguages[browserLanguage] !== 'undefined') {
return browserLanguage
for (let k in availableLanguages) {
if (browserLanguage[k] === browserLanguage || k.startsWith(browserLanguage + '-')) {
return k
}
}
return 'en'

127
src/icons.js Normal file
View File

@ -0,0 +1,127 @@
import {library} from '@fortawesome/fontawesome-svg-core'
import {
faAlignLeft,
faAngleRight,
faArchive,
faBars,
faBell,
faCalendar,
faCalendarWeek,
faCheck,
faCheckDouble,
faChessKnight,
faChevronDown,
faCloudDownloadAlt,
faCloudUploadAlt,
faCocktail,
faCoffee,
faCog,
faEllipsisH,
faEllipsisV,
faExclamation,
faFillDrip,
faFilter,
faForward,
faGripLines,
faHistory,
faImage,
faKeyboard,
faLayerGroup,
faList,
faListOl,
faLock,
faPaperclip,
faPaste,
faPen,
faPencilAlt,
faPercent,
faPlus,
faPowerOff,
faSearch,
faShareAlt,
faSignOutAlt,
faSort,
faSortUp,
faStar as faStarSolid,
faTachometerAlt,
faTags,
faTasks,
faTh,
faTimes,
faTrashAlt,
faUser,
faUsers,
} from '@fortawesome/free-solid-svg-icons'
import {
faBellSlash,
faCalendarAlt,
faClock,
faComments,
faSave,
faStar,
faSun,
faTimesCircle,
} from '@fortawesome/free-regular-svg-icons'
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
library.add(faAlignLeft)
library.add(faAngleRight)
library.add(faArchive)
library.add(faBars)
library.add(faBell)
library.add(faBellSlash)
library.add(faCalendar)
library.add(faCalendarAlt)
library.add(faCalendarWeek)
library.add(faCheck)
library.add(faCheckDouble)
library.add(faChessKnight)
library.add(faChevronDown)
library.add(faClock)
library.add(faCloudDownloadAlt)
library.add(faCloudUploadAlt)
library.add(faCocktail)
library.add(faCoffee)
library.add(faCog)
library.add(faComments)
library.add(faEllipsisH)
library.add(faEllipsisV)
library.add(faExclamation)
library.add(faFillDrip)
library.add(faFilter)
library.add(faForward)
library.add(faGripLines)
library.add(faHistory)
library.add(faImage)
library.add(faKeyboard)
library.add(faLayerGroup)
library.add(faList)
library.add(faListOl)
library.add(faLock)
library.add(faPaperclip)
library.add(faPaste)
library.add(faPen)
library.add(faPencilAlt)
library.add(faPercent)
library.add(faPlus)
library.add(faPowerOff)
library.add(faSave)
library.add(faSearch)
library.add(faShareAlt)
library.add(faSignOutAlt)
library.add(faSort)
library.add(faSortUp)
library.add(faStar)
library.add(faStarSolid)
library.add(faSun)
library.add(faTachometerAlt)
library.add(faTags)
library.add(faTasks)
library.add(faTh)
library.add(faTimes)
library.add(faTimesCircle)
library.add(faTrashAlt)
library.add(faUser)
library.add(faUsers)
export default FontAwesomeIcon

View File

@ -2,6 +2,8 @@ import Vue from 'vue'
import App from './App.vue'
import router from './router'
import {error, success} from './message'
declare global {
interface Window {
API_URL: string;
@ -19,72 +21,6 @@ import Modal from './components/modal/modal'
import './styles/vikunja.scss'
// Notifications
import Notifications from 'vue-notification'
// Icons
import {library} from '@fortawesome/fontawesome-svg-core'
import {
faAlignLeft,
faAngleRight,
faBars,
faCalendar,
faCalendarWeek,
faCheck,
faCheckDouble,
faChevronDown,
faCloudDownloadAlt,
faCloudUploadAlt,
faCog,
faEllipsisV,
faExclamation,
faFillDrip,
faFilter,
faHistory,
faKeyboard,
faLayerGroup,
faList,
faListOl,
faLock,
faPaperclip,
faPaste,
faPen,
faPencilAlt,
faPercent,
faPlus,
faPowerOff,
faSearch,
faSignOutAlt,
faSort,
faSortUp,
faStar as faStarSolid,
faTachometerAlt,
faTags,
faTasks,
faTh,
faTimes,
faTrashAlt,
faUser,
faUsers,
faForward,
faChessKnight,
faCoffee,
faCocktail,
faEllipsisH,
faArchive,
faShareAlt,
faImage,
faBell,
faGripLines,
} from '@fortawesome/free-solid-svg-icons'
import {
faCalendarAlt,
faClock,
faComments,
faSave,
faStar,
faTimesCircle,
faSun,
faBellSlash,
} from '@fortawesome/free-regular-svg-icons'
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
// PWA
import './registerServiceWorker'
@ -92,7 +28,6 @@ import './registerServiceWorker'
// @ts-ignore - no types available
import vueShortkey from 'vue-shortkey'
// Mixins
import message from './message'
import {colorIsDark} from './helpers/color/colorIsDark'
import {setTitle} from './helpers/setTitle'
import {getNamespaceTitle} from './helpers/getNamespaceTitle'
@ -122,69 +57,33 @@ Vue.config.productionTip = false
Vue.use(Notifications)
library.add(faSignOutAlt)
library.add(faPlus)
library.add(faListOl)
library.add(faTasks)
library.add(faCog)
library.add(faAngleRight)
library.add(faLayerGroup)
library.add(faTrashAlt)
library.add(faUsers)
library.add(faUser)
library.add(faLock)
library.add(faPen)
library.add(faTimes)
library.add(faTachometerAlt)
library.add(faCalendar)
library.add(faTimesCircle)
library.add(faBars)
library.add(faPowerOff)
library.add(faCalendarWeek)
library.add(faCalendarAlt)
library.add(faExclamation)
library.add(faTags)
library.add(faChevronDown)
library.add(faCheck)
library.add(faPaste)
library.add(faPencilAlt)
library.add(faCloudDownloadAlt)
library.add(faCloudUploadAlt)
library.add(faPercent)
library.add(faStar)
library.add(faAlignLeft)
library.add(faPaperclip)
library.add(faClock)
library.add(faHistory)
library.add(faSearch)
library.add(faCheckDouble)
library.add(faComments)
library.add(faTh)
library.add(faSort)
library.add(faSortUp)
library.add(faList)
library.add(faEllipsisV)
library.add(faFilter)
library.add(faFillDrip)
library.add(faKeyboard)
library.add(faSave)
library.add(faStarSolid)
library.add(faForward)
library.add(faSun)
library.add(faChessKnight)
library.add(faCoffee)
library.add(faCocktail)
library.add(faEllipsisH)
library.add(faArchive)
library.add(faShareAlt)
library.add(faImage)
library.add(faBell)
library.add(faBellSlash)
library.add(faGripLines)
import FontAwesomeIcon from './icons'
Vue.component('icon', FontAwesomeIcon)
Vue.use(vueShortkey, {prevent: ['input', 'textarea', '.input']})
Vue.use(vueShortkey, {prevent: ['input', 'textarea', '.input', '[contenteditable]']})
// define as global property
const Message = {
install(Vue) {
if (this.installed) {
return
}
this.installed = true
const message = {
error(e, actions = []) {
return error(e, Vue.prototype, actions)
},
success(s, actions = []) {
return success(s, Vue.prototype, actions)
},
}
Vue.prototype['$message'] = message
},
}
Vue.use(Message)
import focus from './directives/focus'
Vue.directive('focus', focus)
@ -219,12 +118,6 @@ Vue.mixin({
getListTitle(l) {
return getListTitle(l, (p: VueI18n.Path) => this.$t(p))
},
error(e, actions = []) {
return message.error(e, this, (p: VueI18n.Path) => this.$t(p), actions)
},
success(s, actions = []) {
return message.success(s, this, (p: VueI18n.Path) => this.$t(p), actions)
},
colorIsDark: colorIsDark,
setTitle: setTitle,
},

View File

@ -1,9 +1,11 @@
export const getErrorText = (r, $t) => {
import {i18n} from '@/i18n/setup'
export const getErrorText = (r) => {
if (r.response && r.response.data) {
if(r.response.data.code) {
const path = `error.${r.response.data.code}`
const message = $t(path)
const message = i18n.t(path)
// If message and path are equal no translation exists for that error code
if (path !== message) {
@ -25,23 +27,23 @@ export const getErrorText = (r, $t) => {
return [r.message]
}
export default {
error(e, context, $t, actions = []) {
context.$notify({
type: 'error',
title: $t('error.error'),
text: getErrorText(e, $t),
export function error(e, context, actions = []) {
context.$notify({
type: 'error',
title: i18n.t('error.error'),
text: getErrorText(e),
actions: actions,
})
console.error(e, actions)
}
export function success(e, context, actions = []) {
context.$notify({
type: 'success',
title: i18n.t('error.success'),
text: getErrorText(e),
data: {
actions: actions,
})
},
success(e, context, $t, actions = []) {
context.$notify({
type: 'success',
title: $t('error.success'),
text: getErrorText(e, $t),
data: {
actions: actions,
},
})
},
},
})
}

View File

@ -1,5 +0,0 @@
{
"UNKNOWN": 0,
"USER": 1,
"LINK_SHARE": 2
}

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