Compare commits

..
204 Commits
Author SHA1 Message Date
kolaente 08821ea8a8 chore: 0.24.2 release preperations 2024-08-12 20:22:07 +02:00
kolaente e9a466fa31 fix(kanban): always make cover image full width
(cherry picked from commit f22420d502)
2024-08-12 20:17:10 +02:00
kolaente de9f686480 fix(migration): ensure project background gets exported and imported
(cherry picked from commit ebfd5f54d2)
2024-08-12 20:17:10 +02:00
kolaente d08e9650ba fix(migration): make sure tasks are associated to the correct view and bucket for data imported from Vikunja dump
This change fixes a bug where imported projects would contain the default views additionally to the ones included in the export. This also caused the tasks to not show up in the views and buckets where they should show up, the newly imported ones.

Resolves https://community.vikunja.io/t/migration-from-vikunja-export-duplicated-boards-local-to-oidc/2690

(cherry picked from commit 28b4eaee31)
2024-08-12 20:17:09 +02:00
kolaente 0434a96c3a fix(views): do not create task bucket and task position entries when duplicating a project
Resolves https://github.com/go-vikunja/vikunja/issues/297
Resolves https://community.vikunja.io/t/duplicating-a-board-puts-the-buckets-in-the-original-board/2579

(cherry picked from commit b83448b7a6)
2024-08-12 20:17:07 +02:00
kolaente 2b9b77bef2 fix(task): move task into new kanban bucket when moving between projects
Resolves https://github.com/go-vikunja/vikunja/issues/290
Resolves https://community.vikunja.io/t/tasks-moved-from-one-project-to-another-do-not-show-up-in-kanban/2689

(cherry picked from commit eed783e42f)
2024-08-12 20:17:05 +02:00
kolaente f5040ad2f4 chore: use nixpkgs unstable for more recent packages
(cherry picked from commit 1d624612ee)
2024-08-12 20:17:04 +02:00
kolaente 7bc77ae8c0 fix(task): set current project after moving a task
(cherry picked from commit 9576f72d7c)
2024-08-12 20:17:02 +02:00
kolaente 715269a5d0 fix(task): do not allow moving a task to the project the task already belongs to
(cherry picked from commit 7efc4d1bc8)
2024-08-12 20:17:01 +02:00
kolaente 53605c24f0 chore(deps): update dependency node to v20.16.0
(cherry picked from commit 670e605275)
2024-08-12 20:17:00 +02:00
kolaente da0b741b69 chore(deps): update go toolchain to 1.22.5
(cherry picked from commit 871b3ce071)
2024-08-12 20:16:59 +02:00
kolaente 73f923bc47 feat: switch from nix flakes to devenv
(cherry picked from commit ad3c5fcee5)
2024-08-12 20:16:56 +02:00
kolaente 25a8c7ea80 feat(editor): support custom protocol for links
Resolves https://github.com/go-vikunja/vikunja/issues/306

(cherry picked from commit e2a87036e0)
2024-08-12 20:16:55 +02:00
kolaente 950de7c954 fix(mail): do not fail testmail command when the connection could not be closed.
Resolves https://github.com/go-vikunja/vikunja/issues/300

(cherry picked from commit 40bb86bee5)
2024-08-12 20:16:54 +02:00
Frederick [Bot]andkolaente 42ba9240b0 chore(i18n): update translations via Crowdin
(cherry picked from commit 4a4ed2cf89)
2024-08-12 20:16:52 +02:00
kolaente 3da6299021 chore(deps): update flake
(cherry picked from commit 7751e124a7)
2024-08-12 20:16:50 +02:00
Frederick [Bot]andkolaente 1ac0f412b9 chore(i18n): update translations via Crowdin
(cherry picked from commit 6137d07138)
2024-08-12 20:16:48 +02:00
kolaente e7fbdc2727 fix(i18n): change casing of Ukrainian language in selector
(cherry picked from commit 66d316f0b5)
2024-08-12 20:16:46 +02:00
kolaente 7117303d57 docs: clarify Todoist redirect url
Resolves https://github.com/go-vikunja/vikunja/issues/302

(cherry picked from commit bf2d56c9d4)
2024-08-12 20:16:45 +02:00
Dominik Pschenitschniandkolaente 0ca43dc147 fix: remove console log
(cherry picked from commit 413798b321)
2024-08-12 20:16:43 +02:00
Frederick [Bot]andkolaente c47ea2716c chore(i18n): update translations via Crowdin
(cherry picked from commit bf1c8c6119)
2024-08-12 20:16:42 +02:00
Frederick [Bot]andkolaente cbdcc61414 chore(i18n): update translations via Crowdin
(cherry picked from commit 49fa0a8468)
2024-08-12 20:16:41 +02:00
Frederick [Bot]andkolaente 194a7d4cf8 chore(i18n): update translations via Crowdin
(cherry picked from commit 784cd42a7a)
2024-08-12 20:16:37 +02:00
Frederick [Bot]andkolaente 9695785a0c chore(i18n): update translations via Crowdin
(cherry picked from commit 4f74be0247)
2024-08-12 20:16:35 +02:00
Dominik Pschenitschniandkolaente d9f555554e fix: textarea autosize for LanguageTool
(cherry picked from commit 7ef1e0a3e5)
2024-08-12 20:16:32 +02:00
Dominik Pschenitschniandkolaente 6e38bcf349 chore: improve error message
(cherry picked from commit bc5fd380e5)
2024-08-12 20:16:30 +02:00
Dominik Pschenitschniandkolaente fc780a90ae chore: remove lodash.debounce
(cherry picked from commit bcd306b84d)
2024-08-12 20:16:28 +02:00
Dominik Pschenitschniandkolaente d586f691b7 feat: add missing peer dependency
(cherry picked from commit ad2870335d)
2024-08-12 20:16:26 +02:00
Frederick [Bot]andkolaente 9e8e43bd12 chore(i18n): update translations via Crowdin
(cherry picked from commit 917748777f)
2024-08-12 20:16:24 +02:00
renovateandkolaente c554a96b4b fix(deps): update module github.com/coreos/go-oidc/v3 to v3.11.0
(cherry picked from commit 324c966dda)
2024-08-12 20:16:23 +02:00
Frederick [Bot]andkolaente 3ecc81094f [skip ci] Updated swagger docs
(cherry picked from commit 0984821b41)
2024-08-12 20:16:21 +02:00
Dominik Pschenitschniandkolaente 811ccc1baa feat: improve ProjectSettingsViews
(cherry picked from commit e8be657d97)
2024-08-12 20:16:17 +02:00
Dominik Pschenitschniandkolaente d7c5451729 feat: add tailwind with prefix (#2513)
Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/2513
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Co-committed-by: Dominik Pschenitschni <mail@celement.de>
(cherry picked from commit bbfd5270db)
2024-08-12 20:16:16 +02:00
Dominik Pschenitschniandkolaente 96884372b4 fix(projects): trigger only single mutation
Previously these store methods created multiple edits on deep objects in the store or edited a deep nested object directly.

This is bad because:
- multiple edits lead to multiple triggers of all the watchers on the project.
- in theory we should listen deep to a project if we use some deep reactive value of a project, if we want deep updates. Because this is easy to forget, it's better to update the project directly. For this the method `setProject` already existed in the store. This is no real overhead because Vue is smart enough to only trigger listeners that use data of the modified state.

By modifying only a copy of the view and submitting the modified result __once__ we can save us a lot of headache.

PS: I'm not sure if there were any visible problems, because Vue is really fast and the reactivity system works quite well. Regardless of this we should try not to modify state unnecessarily.
(cherry picked from commit 1e632397d2)
2024-08-12 20:16:15 +02:00
Dominik Pschenitschniandkolaente dddba4d64a feat: improve priority visibility
(cherry picked from commit d35454c099)
2024-08-12 20:16:13 +02:00
Dominik Pschenitschniandkolaente a0e3efe2d1 feat: improve label store
(cherry picked from commit 2a6ba7e7f0)
2024-08-12 20:16:12 +02:00
Dominik Pschenitschniandkolaente d707e1576a feat: improve projects store
(cherry picked from commit 1e523a1a39)
2024-08-12 20:16:11 +02:00
Dominik Pschenitschniandkolaente 3aaf363413 fix: emit for DatepickerWithValues
(cherry picked from commit fb91e73a3c)
2024-08-12 20:16:08 +02:00
Frederick [Bot]andkolaente 28fff10fec chore(i18n): update translations via Crowdin
(cherry picked from commit ebd6139c1f)
2024-08-12 20:16:07 +02:00
Dominik Pschenitschniandkolaente 8729c24e1d feat: use withDefaults for Reminders
(cherry picked from commit 6990be705c)
2024-08-12 20:16:01 +02:00
kolaente eee895105a chore: 0.24.1 release preparations 2024-07-18 16:56:04 +02:00
kolaente ef0bf3156e fix(project): do not use project id of nil project in error 2024-07-18 16:39:30 +02:00
kolaente a25834b089 fix(filters): immediately emit filter query when editing saved filter
Resolves https://community.vikunja.io/t/filtering-unexpected-character-relative-dates/2544/12
2024-07-18 16:32:09 +02:00
kolaente 526bd1f170 fix(filters): reload tasks silently when marking one done in the list
This fixes a UI issue where if a user had a filter set and marked the task done, it would not disappear, even though the filter does not match the done task anymore.
2024-07-18 15:51:03 +02:00
kolaente bea81f83f7 fix(filters): only insert task buckets and positions when there are any 2024-07-18 15:38:29 +02:00
kolaente 03381f47bf fix(filters): show actual error message from api when the filter query is invalid 2024-07-18 15:35:33 +02:00
renovateandkonrad ec8f962615 fix(deps): update dependency dayjs to v1.11.12 2024-07-18 13:32:45 +00:00
kolaente dd8be14fdb fix(filters): make sure filters are replaced case-insensitively before filtering on the server
Resolves https://community.vikunja.io/t/filtering-startdate-now-is-invalid/2613
2024-07-18 15:21:37 +02:00
kolaente 12474e5be6 fix(kanban): make sure tasks which changed their done status are moved around in buckets
This fixes a bug where tasks which had their done status changed were not moved in the correct bucket. This affected both frontend and api. The move of the task between buckets is now correctly done in the api and frontend - with a bit of duplicated logic between the two. This could be optimized further in the future.

Resolves https://kolaente.dev/vikunja/vikunja/issues/2610
2024-07-18 12:39:29 +02:00
renovateandkolaente 34691b8edf fix(deps): update module golang.org/x/crypto to v0.25.0 2024-07-18 10:46:03 +02:00
Frederick [Bot]anddrone f98a336920 chore(i18n): update translations via Crowdin 2024-07-18 00:03:00 +00:00
renovate 918a05cfda fix(deps): update tiptap to v2.5.4 2024-07-17 22:15:14 +00:00
renovateandkonrad 84e8478e94 chore(deps): update pnpm to v9.5.0 2024-07-17 22:10:27 +00:00
Dominik Pschenitschni fd6692ea1a feat: remove props destructuring from FilterInput 2024-07-17 23:00:47 +02:00
renovateandkonrad ddc8b416c0 fix(deps): update module github.com/microcosm-cc/bluemonday to v1.0.27 2024-07-17 15:06:04 +00:00
renovate a4e98a33c5 fix(deps): update font awesome to v6.6.0 2024-07-17 14:11:53 +00:00
renovateandkolaente ee99ee4d02 fix(deps): update module golang.org/x/term to v0.22.0 2024-07-17 14:49:48 +02:00
kolaente acf9f836a7 fix(settings): overflow of select on mobile 2024-07-17 14:46:17 +02:00
kolaente 7c54d1accf fix(api tokens): show error message when the user tries to create an api token without at least one permission selected 2024-07-17 14:42:47 +02:00
renovateandkonrad 891db0216b fix(deps): update dependency vue to v3.4.32 2024-07-17 12:19:59 +00:00
kolaente f1d9e4f25c fix(quick add magic): create the task even when it only contains quick add magic keywords
Resolves https://github.com/go-vikunja/vikunja/issues/282
2024-07-17 14:16:21 +02:00
kolaente 82ee9f18eb fix(kanban): do not allow to create new tasks in saved filter 2024-07-17 14:00:47 +02:00
kolaente ed5feee33a fix(kanban): move task to done bucket when it was marked done from the task detail view 2024-07-17 13:56:43 +02:00
kolaente 3566b889be fix(task): use backdropView prop 2024-07-17 13:45:31 +02:00
kolaente db81701d38 fix: scss deprecation warning 2024-07-17 13:25:18 +02:00
Dominik Pschenitschniandkonrad cb706416c6 feat: use withDefaults for DatepickerWithValues 2024-07-17 11:06:57 +00:00
kolaente f8fb9d7407 fix(filters): add tasks to filter buckets when updating the filter 2024-07-17 12:57:52 +02:00
kolaente 5cc420b289 fix(filters): add task to buckets of saved filters when creating the task 2024-07-17 12:57:51 +02:00
renovateandkonrad 327905bb90 fix(deps): update module github.com/arran4/golang-ical to v0.3.1 (#2606)
Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/2606
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-07-17 10:51:53 +00:00
Dominik Pschenitschniandkonrad f1481d702c feat: withDefaults for EditAssignees 2024-07-17 10:33:28 +00:00
renovateandkonrad c97dbd3cff chore(deps): update goreleaser/nfpm docker tag to v2.38.0 2024-07-17 10:30:30 +00:00
kolaente d2602a7629 chore: update golangci lint config 2024-07-17 11:43:38 +02:00
kolaente 744b40e7f7 fix: missing error handling 2024-07-17 11:43:38 +02:00
kolaente 1e42e54db1 fix(projects): do not create buckets in the original project when duplicating a project 2024-07-17 10:24:47 +02:00
kolaente 8a6689f4fa fix(projects): do not create backlog bucket when duplicating views 2024-07-17 10:24:03 +02:00
kolaente 092a65b025 feat(project): add " - duplicate" suffix to duplicated projects title 2024-07-17 10:23:33 +02:00
kolaente 5e04abaa06 chore(project): rename receiver 2024-07-17 10:23:01 +02:00
kolaente fe8e0ef78b fix(kanban): use correct text color for deletion button 2024-07-17 10:09:36 +02:00
kolaente ecdb03aca8 fix(migration): correctly set bucket for related tasks
When creating a related task during the import, migrating would fail because the migration would try to add the task to a bucket before the task was created. This fix changes the order in which that happens to prevent the error.
2024-07-17 10:05:59 +02:00
kolaente 58e492872a fix(kanban): put task into correct bucket when creating via kanban board 2024-07-17 09:36:33 +02:00
renovateandkonrad ee20e6175a fix(deps): update dependency ufo to v1.5.4 2024-07-17 07:29:33 +00:00
kolaente 24ae38713b chore(deps): update flake 2024-07-17 09:24:49 +02:00
Dominik Pschenitschniandkonrad c8585d1a69 feat: use withDefaults for RepeatAfter 2024-07-17 06:46:47 +00:00
renovate 3be685ad08 chore(deps): update dev-dependencies 2024-07-17 06:15:23 +00:00
Dominik Pschenitschniandkonrad aaa2abced4 feat: add withDefaults and emit types to PrioritySelect 2024-07-17 02:33:35 +00:00
Dominik Pschenitschniandkonrad ddc18aa177 feat: remove props destructuring from KanbanCard 2024-07-16 23:28:15 +00:00
Dominik Pschenitschniandkonrad 07130bdc3a feat: remove props destructuring from Attachments 2024-07-16 23:10:57 +00:00
Dominik Pschenitschniandkonrad 2f92e407cc feat: remove props destructuring from ViewEditForm 2024-07-16 22:59:25 +00:00
Dominik Pschenitschniandkonrad 6e72606d74 feat: use withDefaults for EditLabels 2024-07-16 22:09:37 +00:00
Dominik Pschenitschniandkonrad 577f5ae69a feat: use withDefaults for Password 2024-07-16 22:09:30 +00:00
Dominik Pschenitschniandkonrad 8ad7e7c905 feat: remove PropTypes helper from ProjectInfo 2024-07-16 22:07:27 +00:00
Dominik Pschenitschniandkonrad b4e9d9437e feat: use withDefaults for FancyCheckbox 2024-07-16 22:07:18 +00:00
Dominik Pschenitschniandkonrad 0c70aedeb1 feat: remove props destructuring from ProjectList 2024-07-16 22:06:48 +00:00
Dominik Pschenitschniandkonrad 99e90c0b02 feat: remove props destructuring from ProjectTable 2024-07-16 22:06:40 +00:00
Dominik Pschenitschniandkonrad 38744dfd5d feat: remove props destructuring for ProjectWrapper 2024-07-16 22:04:43 +00:00
Dominik Pschenitschniandkonrad eb07be1a62 feat: remove props destructuring from ColorPicker 2024-07-16 22:04:26 +00:00
Dominik Pschenitschniandkonrad 825d1add49 feat: remove props destructuring from FilterDelete 2024-07-16 22:04:21 +00:00
Dominik Pschenitschniandkonrad a15831eb33 feat: remove props destructuring from SingleTaskInProject 2024-07-16 22:04:17 +00:00
Dominik Pschenitschniandkonrad 871e0acd8a feat: use withDefaults for PriorityLabel 2024-07-16 21:58:07 +00:00
Dominik Pschenitschniandkonrad 5af908b2e5 feat: remove props destructuring from ProjectKanban 2024-07-16 21:57:57 +00:00
Dominik Pschenitschniandkonrad 449e9a911c feat: remove props destructuring from FilterEdit 2024-07-16 21:53:56 +00:00
Dominik Pschenitschniandkonrad 098c99fd2e feat: type defineEmits for CreateEdit 2024-07-16 21:53:28 +00:00
Dominik Pschenitschniandkonrad c9591fe464 feat: remove props destructuring from NoAuthWrapper 2024-07-16 21:53:21 +00:00
Dominik Pschenitschniandkonrad df6a9b67fd feat: use withDefaults for DatepickerInline 2024-07-16 21:53:15 +00:00
Dominik Pschenitschniandkonrad d02c349231 feat: remove props destructuring from ShowTasks 2024-07-16 21:50:40 +00:00
Dominik Pschenitschniandkonrad fd3f1de861 feat: type defineEmits for Heading 2024-07-16 21:50:32 +00:00
Dominik Pschenitschniandkonrad f38a5c9220 feat: type defineEmits for ProjectSearch 2024-07-16 21:50:27 +00:00
Dominik Pschenitschniandkonrad 9d2e79f725 feat: define prop and emit types for FilterPopup 2024-07-16 21:50:23 +00:00
Dominik Pschenitschniandkonrad 85889fff56 feat: remove PropTypes helper from ProjectSettingsEdit 2024-07-16 21:49:15 +00:00
Dominik Pschenitschniandkonrad 8ac0eb4aa4 feat: defineModel and withDefaults for PercentDoneSelect 2024-07-16 21:49:02 +00:00
Dominik Pschenitschniandkonrad 1966cc3c0e feat: type defineEmits for ApiConfig 2024-07-16 21:46:15 +00:00
Dominik Pschenitschniandkonrad 20bdb01187 feat: remove props destructuring ProjectSettingsViews 2024-07-16 21:45:29 +00:00
Dominik Pschenitschniandkonrad 3ff56d7987 feat: remove props destructuring for Filters 2024-07-16 21:44:24 +00:00
Dominik Pschenitschniandkonrad 46aa2fff0f feat: remove props destructuring for DeferTask 2024-07-16 21:43:36 +00:00
Dominik Pschenitschniandkonrad a10f9ca225 feat: remove props destructuring from TaskDetailView 2024-07-16 21:42:21 +00:00
Dominik Pschenitschniandkonrad 0bc1832050 feat: remove PropTypes helper from FilePreview 2024-07-16 21:39:15 +00:00
Dominik Pschenitschniandkonrad 78811d916a feat: use withDefaults for Datepicker 2024-07-16 21:38:07 +00:00
Dominik Pschenitschniandkonrad f19f19bb75 feat: use withDefaults for Comments 2024-07-16 21:37:52 +00:00
Dominik Pschenitschniandkonrad f2fdbad7d4 feat: use withDefaults for UserTeam 2024-07-16 21:37:28 +00:00
Dominik Pschenitschniandkonrad 4b9b9da122 feat: use withDefaults for ReminderPeriod 2024-07-16 21:35:06 +00:00
Dominik Pschenitschniandkonrad 10832165c3 feat: use withDefaults for Pagination 2024-07-16 21:34:59 +00:00
Dominik Pschenitschniandkonrad d6c3b5a9a1 feat: use withDefaults for Reactions 2024-07-16 21:34:51 +00:00
Dominik Pschenitschniandkonrad 2f6338484b feat: use withDefaults for ProgressBar 2024-07-16 21:32:13 +00:00
Dominik Pschenitschniandkonrad f586e51aad feat: use withDefaults for DateTableCell 2024-07-16 21:32:06 +00:00
Dominik Pschenitschniandkonrad fd12c8705e feat: use withDefaults for SelectProject 2024-07-16 21:30:52 +00:00
Dominik Pschenitschniandkonrad 5545b0e447 feat: add withDefaults, defineEmits and defineSlots types for Dropdown 2024-07-16 21:30:33 +00:00
Dominik Pschenitschniandkonrad 043a6dd049 feat: type defineEmits for Card 2024-07-16 21:29:39 +00:00
Dominik Pschenitschniandkonrad 30769fb6ea feat: use withDefaults for Subscription 2024-07-16 21:29:33 +00:00
Dominik Pschenitschni 553a97f03d feat: remove props destructuring for ProjectView 2024-07-16 19:27:24 +02:00
renovate 8c9623dd0e fix(deps): update dependency @sentry/vue to v8.18.0 2024-07-16 15:12:44 +00:00
renovateandkonrad 9b232a326a fix(deps): update dependency @sentry/vue to v8.17.0 2024-07-16 08:32:45 +00:00
Frederick [Bot]andDominik Pschenitschni 68b9d391b0 chore(i18n): update translations via Crowdin 2024-07-16 00:23:00 +00:00
Dominik Pschenitschniandkonrad b718aa1fbd chore(popup): trigger close function directly 2024-07-15 19:51:14 +00:00
renovateandkonrad a114b9c8d3 fix(deps): update dependency @github/hotkey to v3.1.1 (#2329)
Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/2329
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-07-15 19:50:46 +00:00
Dominik Pschenitschni 48e045b4a3 fix(vscode): i18n-ally locales path 2024-07-15 18:21:00 +02:00
kolaente 8439c9cc1e fix(migration): failed migration typo 2024-07-15 12:10:29 +02:00
Frederick [Bot]anddrone 4bd02a1181 chore(i18n): update translations via Crowdin 2024-07-15 00:21:19 +00:00
kolaente f2d90f31bc fix(migration): revert to old path for migration routing
This change fixes a bug where the migration redirect handler changed the url which lead to the redirect with the auth code from external services to a 404 page, ultimately breaking the migration.

Resolves https://community.vikunja.io/t/geting-404-when-attempting-todoist-import/2588
2024-07-14 22:49:30 +02:00
Frederick [Bot]andDominik Pschenitschni dcf88560a7 chore(i18n): update translations via Crowdin 2024-07-14 00:23:03 +00:00
Dominik Pschenitschniandkonrad 2dc541571c fix: spelling mail 2024-07-13 11:03:43 +00:00
Frederick [Bot]anddrone 08c2cd6b63 chore(i18n): update translations via Crowdin 2024-07-13 00:22:53 +00:00
kolaente bb25a3397b fix(kanban): use correct assertion in the test 2024-07-12 14:21:46 +02:00
kolaente 918c2224d8 fix(kanban): move task to done bucket in all views when moved to done bucket in one view 2024-07-12 13:59:59 +02:00
kolaente 323ecdaab6 fix(kanban): move repeating task back to old bucket when moved to the done bucket 2024-07-12 13:44:45 +02:00
kolaente 51040cf1af fix(kanban): dispatch task updated event when task position is updated
This fixes a bug where a task would not be indexed in Typesense when its position changed.
2024-07-12 13:31:27 +02:00
kolaente e5e0db1f6a fix(kanban): dispatch task updated event when task is moved between buckets
This fixes a bug where a task would not be indexed in Typesense when its bucket changed.
2024-07-12 13:29:15 +02:00
kolaente 62412ce29b fix(kanban): do not move repeating task into a different bucket 2024-07-12 13:19:50 +02:00
kolaente 7c42fb5d75 fix: lint 2024-07-12 11:49:03 +02:00
Frederick [Bot]anddrone 42c2d0884b chore(i18n): update translations via Crowdin 2024-07-12 00:22:53 +00:00
kolaente ec0c05fd47 fix(kanban): update task done status after moving it into done bucket
Resolves https://kolaente.dev/vikunja/vikunja/issues/2518
2024-07-11 13:17:33 +02:00
kolaente 8711f7a935 fix(typesense): do not use modified opts for db fallback search 2024-07-11 12:52:09 +02:00
kolaente 7f27cee6a3 fix(tasks): do not use typesense modified options to search with database 2024-07-11 12:41:40 +02:00
kolaente 5644130f01 fix(filters): do not create a default filter for list view when creating a saved filter
Because a default filter in a filter is highly unexpected, it can lead to these filters cancelling each other out, resulting in confusing behaviour. See https://kolaente.dev/vikunja/vikunja/issues/2547#issuecomment-65180
2024-07-11 12:29:09 +02:00
kolaente 38c30d1eee fix(filters): trim spaces when parsing filter values
This fixes a bug where values would not be trimmed before parsing them. That resulted in a value like " 2" being invalid, even though it's a perfectly fine number.
Because the frontend sends the filters for projects and other values with comma-separated spaces like "1, 2, 3", this essentially broke filtering by these values.

Resolves https://kolaente.dev/vikunja/vikunja/issues/2547
2024-07-11 12:20:32 +02:00
kolaente 471d0fcd4a fix(typesense): reindex tasks when their position changed 2024-07-11 12:07:48 +02:00
kolaente 0b424fe95e fix(typesense): do not crash after creating a project when tasks are not yet indexed
Before this fix, creating a project with Typesense enabled would fail with an error because the tasks it fetches as part of that process do not have the task position property in their index. We now fall back to using the db for searching in that case.

In the long run, we should use typesense joins for the task position to make this more efficient.
2024-07-11 12:07:22 +02:00
kolaente 70615d6843 fix(filter): make sure filter values are properly escaped before executing them
Resolves https://community.vikunja.io/t/filtering-unexpected-character-relative-dates/2544
Resolves https://github.com/go-vikunja/vikunja/issues/286
2024-07-11 11:27:19 +02:00
kolaente 2982acc0ec fix(tasks): limit to max 250 entries when using typesense
Resolves https://community.vikunja.io/t/typesense-250-hits-limit/2536/8
2024-07-11 10:06:39 +02:00
kolaente 2c6bc7742f fix(tasks): explicitly add task position to select statement when looking up tasks with Typesense
Resolves https://kolaente.dev/vikunja/vikunja/issues/2546
Resolves https://community.vikunja.io/t/version-0-24-0-internal-server-error-breaking-change/2558/5
2024-07-11 09:28:22 +02:00
Frederick [Bot]andDominik Pschenitschni 02a7958865 chore(i18n): update translations via Crowdin 2024-07-11 00:22:47 +00:00
Dominik Pschenitschniandkonrad af639a180c fix: wrapped button
This somehow weirdly fixes the test. I have no clue why. Regardless it should be better than before, because a button  (DropdownItem) should never contain another interactive element (the setBucketLimit input and save button).

The test failed because I replaced a v-if with v-show. I do not know _why_ this made the test fail. When I reproduced the test step by step it worked for me…
2024-07-10 09:19:55 +00:00
Dominik Pschenitschniandkonrad faa797f461 feat: improve main nav spacing and open speed 2024-07-10 09:19:55 +00:00
Frederick [Bot]anddrone e2eb711ac9 chore(i18n): update translations via Crowdin 2024-07-10 00:22:50 +00:00
renovateandkonrad 7dcb5fec9a chore(deps): update dev-dependencies 2024-07-09 11:27:08 +00:00
Frederick [Bot]anddrone f507a46324 chore(i18n): update translations via Crowdin 2024-07-09 00:22:12 +00:00
renovate a863b5d375 fix(deps): update dependency dompurify to v3.1.6 2024-07-08 15:11:51 +00:00
Dominik Pschenitschniandkonrad 0a81855bc1 fix: muliselect optional props
also adds name and id props
2024-07-08 14:04:03 +00:00
renovateandkonrad 6064d72aa7 chore(deps): update dev-dependencies 2024-07-08 13:55:26 +00:00
Frederick [Bot]andDominik Pschenitschni 35dea13b22 chore(i18n): update translations via Crowdin 2024-07-08 00:21:34 +00:00
Dominik Pschenitschniandkonrad 7ef6ddf8f7 fix: gitignore dist path 2024-07-07 15:02:49 +00:00
Dominik Pschenitschniandkonrad c37fe49890 feat: improve BaseButtonEmits type 2024-07-07 15:02:41 +00:00
Dominik Pschenitschniandkonrad 413d1f9ad7 feat: withDefaults for Multiselect 2024-07-07 15:02:30 +00:00
Dominik Pschenitschniandkonrad 3317280062 fix: dropdown item disabled prop 2024-07-07 15:01:11 +00:00
Balázs Kereszturyandkonrad 136ef58820 fix: reorder mail options (#2533)
This PR fixes a bug where setting the SMTP port didn't have any effect, since it is overridden when `NewClient()` executes the `WithTLSPortPolicy()` option.

Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/2533
Resolves https://kolaente.dev/vikunja/vikunja/issues/2534
Co-authored-by: Balázs Keresztury <b@belidzs.hu>
Co-committed-by: Balázs Keresztury <b@belidzs.hu>
2024-07-07 15:00:19 +00:00
Dominik Pschenitschniandkonrad 1eb1aa2575 feat: remove unnecessary prop from Card 2024-07-07 14:58:58 +00:00
Dominik Pschenitschniandkonrad 43e38fae17 feat: rename TheNavigation to AppHeader 2024-07-07 14:57:23 +00:00
Frederick [Bot]anddrone af54ee6c5c chore(i18n): update translations via Crowdin 2024-07-07 00:22:51 +00:00
Frederick [Bot]andDominik Pschenitschni 161fd1f1b6 chore(i18n): update translations via Crowdin 2024-07-06 00:23:06 +00:00
Dominik Pschenitschniandkonrad 479b786761 feat: use withDefaults for ProjectCardGrid 2024-07-05 15:50:10 +00:00
Dominik Pschenitschniandkonrad daeefeb487 feat: define prop and emit types DatepickerWithRange 2024-07-05 15:35:06 +00:00
Dominik Pschenitschniandkonrad a56331d39d feat: use withDefaults in ReminderDetail 2024-07-05 15:34:12 +00:00
Dominik Pschenitschni 42c458a736 feat: simplify playPopSound setting check 2024-07-05 14:24:53 +02:00
Dominik Pschenitschniandkonrad 5cf57a520c feat: use withDefaults for Heading 2024-07-05 12:02:39 +00:00
Dominik Pschenitschniandkonrad b500981434 feat: use withDefaults for SelectUser 2024-07-05 12:01:53 +00:00
renovate b0c41ca436 fix(deps): update dependency @sentry/vue to v8.15.0 2024-07-05 10:15:52 +00:00
Dominik Pschenitschni df1f805294 feat: remove eslint rule vue/no-required-prop-with-default
This rule is not part of the recommended rule set. I added it before, because I thought it might make sense.
2024-07-05 11:49:29 +02:00
kolaente e640149a23 chore: go mod tidy 2024-07-05 10:40:56 +02:00
kolaente 5e36bf797e fix: add info log message when starting to run migrations 2024-07-05 10:26:26 +02:00
renovateandkonrad 038e4ba386 chore(deps): update dependency vue-tsc to v2.0.26 2024-07-05 06:49:44 +00:00
Frederick [Bot]anddrone 0231d79b5a chore(i18n): update translations via Crowdin 2024-07-05 00:21:53 +00:00
renovate fe0e22a5cd fix(deps): update dependency @sentry/vue to v8.14.0 2024-07-04 10:11:29 +00:00
Dominik Pschenitschniandkonrad 20efacfa59 feat: improve settings label casing 2024-07-04 09:17:58 +00:00
Dominik Pschenitschniandkonrad 5e84a237fb fix(translation): TOTP casing 2024-07-04 09:10:01 +00:00
Dominik Pschenitschniandkonrad f9e0b43707 feat: move powered by to bottom 2024-07-04 09:04:39 +00:00
Dominik Pschenitschniandkonrad ed0ef385e9 fix: add missing disabled prop 2024-07-04 09:00:35 +00:00
Frederick [Bot]andDominik Pschenitschni f2a0ae108c chore(i18n): update translations via Crowdin 2024-07-04 00:21:54 +00:00
Dominik Pschenitschniandkonrad a721d9286b fix: disable button if loading 2024-07-03 16:05:05 +00:00
Dominik Pschenitschniandkonrad bc2baa3bbd feat(Multiselect): hide multiselect during loading 2024-07-03 15:34:19 +00:00
Dominik Pschenitschniandkonrad 51660f7677 fix: app bottom padding
display: flow-root creates a new block-formatting context and thus prevents overflowing margin issues created by a last child element that has a margin bottom
2024-07-03 15:33:49 +00:00
Dominik Pschenitschni f08039b23c fix: ProjectSearch default value 2024-07-03 10:02:31 +02:00
renovate 9d0dcda60f chore(deps): update dev-dependencies 2024-07-03 05:11:06 +00:00
Frederick [Bot]anddrone c42fbd6e0e chore(i18n): update translations via Crowdin 2024-07-03 00:22:03 +00:00
kolaente 0b14c311b4 chore: 0.24.0 release preparations 2024-07-02 17:48:12 +02:00
Frederick [Bot]andkolaente 31d427546b [skip ci] Updated swagger docs 2024-07-02 14:52:12 +00:00
172 changed files with 6067 additions and 3553 deletions
+12 -12
View File
@@ -364,7 +364,7 @@ steps:
- api-build
- name: frontend-dependencies
image: node:20.14.0-alpine
image: node:20.16.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@@ -378,7 +378,7 @@ steps:
# - restore-cache
- name: frontend-lint
image: node:20.14.0-alpine
image: node:20.16.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@@ -390,7 +390,7 @@ steps:
- frontend-dependencies
- name: frontend-build-prod
image: node:20.14.0-alpine
image: node:20.16.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@@ -402,7 +402,7 @@ steps:
- frontend-dependencies
- name: frontend-test-unit
image: node:20.14.0-alpine
image: node:20.16.0-alpine
pull: always
commands:
- cd frontend
@@ -413,7 +413,7 @@ steps:
- name: frontend-typecheck
failure: ignore
image: node:20.14.0-alpine
image: node:20.16.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@@ -544,7 +544,7 @@ steps:
- git fetch --tags
- name: frontend-dependencies
image: node:20.14.0-alpine
image: node:20.16.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@@ -556,7 +556,7 @@ steps:
- pnpm install --fetch-timeout 100000
- name: frontend-build
image: node:20.14.0-alpine
image: node:20.16.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@@ -716,7 +716,7 @@ steps:
# Build os packages and push it to our bucket
- name: build-os-packages-unstable
image: goreleaser/nfpm:v2.37.1
image: goreleaser/nfpm:v2.38.0
pull: always
commands:
- apk add git go
@@ -732,7 +732,7 @@ steps:
depends_on: [ after-build-compress ]
- name: build-os-packages-version
image: goreleaser/nfpm:v2.37.1
image: goreleaser/nfpm:v2.38.0
pull: always
commands:
- apk add git go
@@ -901,7 +901,7 @@ steps:
- git fetch --tags
- name: build
image: node:20.14.0-alpine
image: node:20.16.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@@ -962,7 +962,7 @@ steps:
- git fetch --tags
- name: build
image: node:20.14.0-alpine
image: node:20.16.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@@ -1352,6 +1352,6 @@ steps:
- failure
---
kind: signature
hmac: 3cc4b45fcbbab5ccee14e5532b2d35f5db54cde29a8f6e854b563462e3bbb509
hmac: dc96a5f8aaa332b35fc02f1b8566493e494452d62bed52973d804f5347ae47ae
...
+3 -1
View File
@@ -1 +1,3 @@
use flake
source_url "https://raw.githubusercontent.com/cachix/devenv/95f329d49a8a5289d31e0982652f7058a189bfca/direnvrc" "sha256-d+8cBpDfDBj41inrADaJt+bDWhOktwslgoP5YiGJ1v0="
use devenv
+10 -1
View File
@@ -28,4 +28,13 @@ vendor/
os-packages/
mage_output_file.go
mage-static
.direnv/
# Devenv
.devenv*
devenv.local.nix
# direnv
.direnv
# pre-commit
.pre-commit-config.yaml
+3 -2
View File
@@ -4,7 +4,9 @@ run:
linters:
enable:
- megacheck
- gosimple
- staticcheck
- unused
- govet
- gocritic
- gocyclo
@@ -15,7 +17,6 @@ linters:
- revive
- misspell
disable:
- scopelint # Obsolete, using exportloopref instead
- durationcheck
- goconst
- musttag
+1 -1
View File
@@ -26,7 +26,7 @@
// i18n ally
"i18n-ally.localesPaths": [
"src/i18n/lang"
"frontend/src/i18n/lang"
],
"i18n-ally.sortKeys": true,
"i18n-ally.keepFulfilled": true,
+213 -1
View File
@@ -5,7 +5,219 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
All releases can be found on https://code.vikunja.io/api/releases.
All releases can be found on https://code.vikunja.io/vikunja/releases.
## [0.24.2] - 2024-08-12
### Bug Fixes
* *(i18n)* Change casing of Ukrainian language in selector
* *(kanban)* Always make cover image full width
* *(mail)* Do not fail testmail command when the connection could not be closed.
* *(migration)* Make sure tasks are associated to the correct view and bucket for data imported from Vikunja dump
* *(migration)* Ensure project background gets exported and imported
* *(projects)* Trigger only single mutation
* *(task)* Do not allow moving a task to the project the task already belongs to
* *(task)* Set current project after moving a task
* *(task)* Move task into new kanban bucket when moving between projects
* *(views)* Do not create task bucket and task position entries when duplicating a project* Emit for DatepickerWithValues ([3aaf363](3aaf3634134a6989337bad02ac99a9329d33b17f))
* Textarea autosize for LanguageTool ([d9f5555](d9f555554e5ecfa9d1243c565e2f42c77f7a7597))
* Remove console log ([0ca43dc](0ca43dc147acd04d9f9b566325ccde0a5782680f))
### Dependencies
* *(deps)* Update module github.com/coreos/go-oidc/v3 to v3.11.0
* *(deps)* Update flake
* *(deps)* Update go toolchain to 1.22.5
* *(deps)* Update dependency node to v20.16.0
### Documentation
* Clarify Todoist redirect url ([7117303](7117303d5705199bb39fb6661b20deebea96959f))
### Features
* *(editor)* Support custom protocol for links* Use withDefaults for Reminders ([8729c24](8729c24e1d2bdc783e750dc1166a8ab31a716107))
* Improve projects store ([d707e15](d707e1576a006baa73c529b432e694dee237db76))
* Improve label store ([a0e3efe](a0e3efe2d12521e773334a19ac985a2011575c3c))
* Improve priority visibility ([dddba4d](dddba4d64a9ddddd6f442c8180661d2d498de994))
* Add tailwind with prefix (#2513) ([d7c5451](d7c54517297e750d92618376f52045b11da6e82c))
* Improve ProjectSettingsViews ([811ccc1](811ccc1baa2fe1fb9fcc8b5515c8c3f3b591e96a))
* Add missing peer dependency ([d586f69](d586f691b7a245bbffd5df4e7a4937f0527047e0))
* Switch from nix flakes to devenv ([73f923b](73f923bc47d32a6a9a689e461901295b07646ebf))
### Miscellaneous Tasks
* *(i18n)* Update translations via Crowdin
* Remove lodash.debounce ([fc780a9](fc780a90ae856038db73d3ad63fcdd5627211c36))
* Improve error message ([6e38bcf](6e38bcf3498a15a1dc1f6fbef254c08513b408b3))
* Use nixpkgs unstable for more recent packages ([f5040ad](f5040ad2f4996fe1ccf03172b1a1ee6007068ee7))
### Other
* *(other)* [skip ci] Updated swagger docs
## [0.24.1] - 2024-07-18
### Bug Fixes
* *(api tokens)* Show error message when the user tries to create an api token without at least one permission selected
* *(filter)* Make sure filter values are properly escaped before executing them
* *(filters)* Add task to buckets of saved filters when creating the task
* *(filters)* Add tasks to filter buckets when updating the filter
* *(filters)* Do not create a default filter for list view when creating a saved filter
* *(filters)* Immediately emit filter query when editing saved filter
* *(filters)* Make sure filters are replaced case-insensitively before filtering on the server
* *(filters)* Only insert task buckets and positions when there are any
* *(filters)* Reload tasks silently when marking one done in the list
* *(filters)* Show actual error message from api when the filter query is invalid
* *(filters)* Trim spaces when parsing filter values
* *(kanban)* Dispatch task updated event when task is moved between buckets
* *(kanban)* Dispatch task updated event when task position is updated
* *(kanban)* Do not allow to create new tasks in saved filter
* *(kanban)* Do not move repeating task into a different bucket
* *(kanban)* Make sure tasks which changed their done status are moved around in buckets
* *(kanban)* Move repeating task back to old bucket when moved to the done bucket
* *(kanban)* Move task to done bucket in all views when moved to done bucket in one view
* *(kanban)* Move task to done bucket when it was marked done from the task detail view
* *(kanban)* Put task into correct bucket when creating via kanban board
* *(kanban)* Update task done status after moving it into done bucket
* *(kanban)* Use correct assertion in the test
* *(kanban)* Use correct text color for deletion button
* *(migration)* Correctly set bucket for related tasks
* *(migration)* Failed migration typo
* *(migration)* Revert to old path for migration routing
* *(project)* Do not use project id of nil project in error
* *(projects)* Do not create backlog bucket when duplicating views
* *(projects)* Do not create buckets in the original project when duplicating a project
* *(quick add magic)* Create the task even when it only contains quick add magic keywords
* *(settings)* Overflow of select on mobile
* *(task)* Use backdropView prop
* *(tasks)* Do not use typesense modified options to search with database
* *(tasks)* Explicitly add task position to select statement when looking up tasks with Typesense
* *(tasks)* Limit to max 250 entries when using typesense
* *(translation)* TOTP casing
* *(typesense)* Do not crash after creating a project when tasks are not yet indexed
* *(typesense)* Do not use modified opts for db fallback search
* *(typesense)* Reindex tasks when their position changed
* *(vscode)* I18n-ally locales path* ProjectSearch default value ([f08039b](f08039b23c1ff8ca208f5c5911f788780ecc80ee))
* Add info log message when starting to run migrations ([5e36bf7](5e36bf797e99d99f83cb084f8c1707d994d0559f))
* Add missing disabled prop ([ed0ef38](ed0ef385e9ba34524d3309bec87eeb44d9dba471))
* App bottom padding ([51660f7](51660f76779e189bca2a24f1e4fca34b8a1a2898))
* Disable button if loading ([a721d92](a721d9286bbb26e0a182992af6b2128e03d5670f))
* Dropdown item disabled prop ([3317280](3317280062c8506d092d5121b92eb4992177f9c2))
* Gitignore dist path ([7ef6ddf](7ef6ddf8f7630e6370bed762779b4ebabbf8962b))
* Lint ([7c42fb5](7c42fb5d75fd9a5aa01abaa852452875f07a1f61))
* Missing error handling ([744b40e](744b40e7f780851d5ce8a288271f78acc462ed14))
* Muliselect optional props ([0a81855](0a81855bc1b403233dec18f036c80f3cad70edee))
* Reorder mail options (#2533) ([136ef58](136ef58820b8e2d27ad0ca50bdd9adabd8e4a95d))
* Scss deprecation warning ([db81701](db81701d3841e12c39b751e919d70fdafc1869d6))
* Spelling mail ([2dc5415](2dc541571cbecc02a178182217d1e4596e4a62aa))
* Wrapped button ([af639a1](af639a180cf56519e9a3a31710da6fee5b305735))
### Dependencies
* *(deps)* Update dependency @github/hotkey to v3.1.1 (#2329)
* *(deps)* Update dependency @sentry/vue to v8.14.0
* *(deps)* Update dependency @sentry/vue to v8.15.0
* *(deps)* Update dependency @sentry/vue to v8.17.0
* *(deps)* Update dependency @sentry/vue to v8.18.0
* *(deps)* Update dependency dayjs to v1.11.12
* *(deps)* Update dependency dompurify to v3.1.6
* *(deps)* Update dependency ufo to v1.5.4
* *(deps)* Update dependency vue to v3.4.32
* *(deps)* Update dependency vue-tsc to v2.0.26
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update flake
* *(deps)* Update font awesome to v6.6.0
* *(deps)* Update goreleaser/nfpm docker tag to v2.38.0
* *(deps)* Update module github.com/arran4/golang-ical to v0.3.1 (#2606)
* *(deps)* Update module github.com/microcosm-cc/bluemonday to v1.0.27
* *(deps)* Update module golang.org/x/crypto to v0.25.0
* *(deps)* Update module golang.org/x/term to v0.22.0
* *(deps)* Update pnpm to v9.5.0
* *(deps)* Update tiptap to v2.5.4
### Features
* *(Multiselect)* Hide multiselect during loading
* *(project)* Add " - duplicate" suffix to duplicated projects title* Move powered by to bottom ([f9e0b43](f9e0b4370700b4cc9c8e99923737bb5aa022a1d0))
* Add withDefaults and emit types to PrioritySelect ([aaa2abc](aaa2abced4258645a9a47b228cece21741bfea25))
* Add withDefaults, defineEmits and defineSlots types for Dropdown ([5545b0e](5545b0e447d406837324186c765cf2e8c8ed47c4))
* Define prop and emit types DatepickerWithRange ([daeefeb](daeefeb487966bfc6535b3d5c8e1dd07ec8a0951))
* Define prop and emit types for FilterPopup ([9d2e79f](9d2e79f7253006d504bd916ab8d820187d50700b))
* DefineModel and withDefaults for PercentDoneSelect ([8ac0eb4](8ac0eb4aa4fbe5a3b147e7f31a29aa2c2f84a1ca))
* Improve BaseButtonEmits type ([c37fe49](c37fe4989015ee18b38bd89b9470c14484759cc8))
* Improve main nav spacing and open speed ([faa797f](faa797f461a57e8a9e115b760af92ab0024f6eed))
* Improve settings label casing ([20efacf](20efacfa59a7f7ff81fd17c2c3b3c9a1d5528e5d))
* Remove PropTypes helper from FilePreview ([0bc1832](0bc18320509e6db7ae484f7336282b7e8e5e841e))
* Remove PropTypes helper from ProjectInfo ([8ad7e7c](8ad7e7c9055e50768ea5b0ad5798d395bb5d3514))
* Remove PropTypes helper from ProjectSettingsEdit ([85889ff](85889fff5603f0b7c7a0f833cae7c32610eeede4))
* Remove eslint rule vue/no-required-prop-with-default ([df1f805](df1f805294e34f8bfaaf60ea65f18cabbd932d9c))
* Remove props destructuring ProjectSettingsViews ([20bdb01](20bdb011877fe17fe5c266eafe77b96dce1646d9))
* Remove props destructuring for DeferTask ([46aa2ff](46aa2fff0f01bf233e4edd39e6675c358c488794))
* Remove props destructuring for Filters ([3ff56d7](3ff56d79874d013ad97c8635fd580edf95290b39))
* Remove props destructuring for ProjectView ([553a97f](553a97f03de3e16593c006ca4a9043fadf5d6a6f))
* Remove props destructuring for ProjectWrapper ([38744df](38744dfd5db0acb53ae03db5e2021265876df2f7))
* Remove props destructuring from Attachments ([07130bd](07130bdc3a65bfb4ed1775e61312b68c62521a4a))
* Remove props destructuring from ColorPicker ([eb07be1](eb07be1a62a9af377b044a7e0268d9d7a8bab77c))
* Remove props destructuring from FilterDelete ([825d1ad](825d1add49595184593ff6fb3ab228cf3fbde9cb))
* Remove props destructuring from FilterEdit ([449e9a9](449e9a911c2fcca7712479cd78dbfe80d5a4bde2))
* Remove props destructuring from FilterInput ([fd6692e](fd6692ea1afce3950c2295a4e8fb551af80e52aa))
* Remove props destructuring from KanbanCard ([ddc18aa](ddc18aa17739963afa5d156241aa07b7100374f4))
* Remove props destructuring from NoAuthWrapper ([c9591fe](c9591fe464e24f38126d5562ef7d0e6060f14b39))
* Remove props destructuring from ProjectKanban ([5af908b](5af908b2e5508712004ee1c702915e4def0ca007))
* Remove props destructuring from ProjectList ([0c70aed](0c70aedeb1a5ca05db3cc2b87138d28427eac959))
* Remove props destructuring from ProjectTable ([99e90c0](99e90c0b02561f1c7b8f49b93ece11b878137e48))
* Remove props destructuring from ShowTasks ([d02c349](d02c349231f29326511755dcae5003d3b3c01ca0))
* Remove props destructuring from SingleTaskInProject ([a15831e](a15831eb33b5104e19f0b85b882dd8ec5336ba62))
* Remove props destructuring from TaskDetailView ([a10f9ca](a10f9ca22580f6fcc435f152776867b380d5be6c))
* Remove props destructuring from ViewEditForm ([2f92e40](2f92e407ccf42892675dc1583dfa057474cb2489))
* Remove unnecessary prop from Card ([1eb1aa2](1eb1aa257574733894f865913a4bb173e22774ea))
* Rename TheNavigation to AppHeader ([43e38fa](43e38fae17d987d96820dcdfd936fcf8d11899ff))
* Simplify playPopSound setting check ([42c458a](42c458a736d88bf4b0f18fa23c08273ddf9c7ca5))
* Type defineEmits for ApiConfig ([1966cc3](1966cc3c0e7a590651db2a5bd0acdcd83e948aa7))
* Type defineEmits for Card ([043a6dd](043a6dd049ba22fa1c87ed88aa0b2f8dba4f2593))
* Type defineEmits for CreateEdit ([098c99f](098c99fd2ede44ddf231e1b504b9cf6d8c0ed17b))
* Type defineEmits for Heading ([fd3f1de](fd3f1de861ac593c0f780c3f341a4b0c5d7b2f1b))
* Type defineEmits for ProjectSearch ([f38a5c9](f38a5c9220c6404152e261a78e4829e52cef5d1b))
* Use withDefaults for Comments ([f19f19b](f19f19bb75a457dc2073d8a06f064eae04a3b772))
* Use withDefaults for DateTableCell ([f586e51](f586e51aad5194043fec37e3b0af94a30c95f3a3))
* Use withDefaults for Datepicker ([78811d9](78811d916aeaa105560c5b7c091290f3b2e38683))
* Use withDefaults for DatepickerInline ([df6a9b6](df6a9b67fd2a462e5027538e521da174ecd24bc6))
* Use withDefaults for DatepickerWithValues ([cb70641](cb706416c61ba31d1fd54d8b9bd8549cc9a33528))
* Use withDefaults for EditLabels ([6e72606](6e72606d7454fbb3b17302d3ef1d2cdf186873ae))
* Use withDefaults for FancyCheckbox ([b4e9d94](b4e9d9437e4fb4b8e5e56c633fc4c74ee45a6225))
* Use withDefaults for Heading ([5cf57a5](5cf57a520cd7048aba0656ce00333cc7cfcc14f9))
* Use withDefaults for Pagination ([1083216](10832165c3798bef63590c292426feb67e178f15))
* Use withDefaults for Password ([577f5ae](577f5ae69a31e14cdff6e0aed5f3091d07484faa))
* Use withDefaults for PriorityLabel ([871e0ac](871e0acd8aa8ba6a9ad7fb08bda8badf20104f94))
* Use withDefaults for ProgressBar ([2f63384](2f6338484bda4670133f3db6c122dbaa6a231c15))
* Use withDefaults for ProjectCardGrid ([479b786](479b786761948b0a0427676c42e082e75579029c))
* Use withDefaults for Reactions ([d6c3b5a](d6c3b5a9a10a04c8fac47ad998ebf434e93dfa71))
* Use withDefaults for ReminderPeriod ([4b9b9da](4b9b9da122a2d085dcff8fbb8dd2693062689fd4))
* Use withDefaults for RepeatAfter ([c8585d1](c8585d1a691403e6331cd9aff852873c0f7771d3))
* Use withDefaults for SelectProject ([fd12c87](fd12c8705ea9b28db6947fd98f2d34ce46fc15b2))
* Use withDefaults for SelectUser ([b500981](b50098143474be8d86e0ae0543bab1787585dfe7))
* Use withDefaults for Subscription ([30769fb](30769fb6eadb21d45629dcf2c67cc1174d26e828))
* Use withDefaults for UserTeam ([f2fdbad](f2fdbad7d4bde5880694a611a4f9d68fe2017367))
* Use withDefaults in ReminderDetail ([a56331d](a56331d39d12c65838b13987fe2096c4ca2a703f))
* WithDefaults for EditAssignees ([f1481d7](f1481d702cc426cddfeda0edae6a6cd06d9f3380))
* WithDefaults for Multiselect ([413d1f9](413d1f9ad7e9ba0234d81a1564745cd03d9f1044))
### Miscellaneous Tasks
* *(i18n)* Update translations via Crowdin
* *(popup)* Trigger close function directly
* *(project)* Rename receiver
* 0.24.0 release preparations ([0b14c31](0b14c311b4d344aecf974489eee19ab939c676ad))
* Go mod tidy ([e640149](e640149a23c931838a93b737804287a9ed570268))
* Update golangci lint config ([d2602a7](d2602a7629ee3d8cd67beb8ef1a44ca3cd2dd7f9))
* *(other)* [skip ci] Updated swagger docs
## [0.24.0] - 2024-07-02
+1 -1
View File
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM node:20.14.0-alpine AS frontendbuilder
FROM --platform=$BUILDPLATFORM node:20.16.0-alpine AS frontendbuilder
WORKDIR /build
+1 -1
View File
@@ -2,7 +2,7 @@
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/vikunjaa/status.svg)](https://drone.kolaente.de/vikunja/vikunja)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
[![Download](https://img.shields.io/badge/download-v0.24.0-brightgreen.svg)](https://dl.vikunja.io)
[![Install](https://img.shields.io/badge/download-v0.24.2-brightgreen.svg)](https://vikunja.io/docs/installing)
[![Docker Pulls](https://img.shields.io/docker/pulls/vikunja/vikunja.svg)](https://hub.docker.com/r/vikunja/vikunja/)
[![Swagger Docs](https://img.shields.io/badge/swagger-docs-brightgreen.svg)](https://try.vikunja.io/api/v1/docs)
[![Go Report Card](https://goreportcard.com/badge/kolaente.dev/vikunja/vikunja)](https://goreportcard.com/report/kolaente.dev/vikunja/vikunja)
+6 -5
View File
@@ -227,12 +227,13 @@ migration:
clientid:
# The client secret, also required for making requests to the todoist api
clientsecret:
# The url where clients are redirected after they authorized Vikunja to access their todoist items.
# This needs to match the url you entered when registering your Vikunja instance at todoist.
# This is usually the frontend url where the frontend then makes a request to /migration/todoist/migrate
# The url where clients are redirected after they authorized Vikunja to access their Todoist items.
# In Todoist, this is called `OAuth redirect URL` and it needs to match the url you entered when registering
# your Vikunja instance at the Todoist developer console.
# When using the official Vikunja frontend, set this to <service.publicurl>/migrate/todoist.
# Otherwise, set this to an url which then makes a request to /api/v1/migration/todoist/migrate
# with the code obtained from the todoist api.
# Note that the Vikunja frontend expects this to be /migrate/todoist
redirecturl: <frontend url>/migrate/todoist
redirecturl: <service.publicurl>/migrate/todoist
trello:
# Whether to enable the trello migrator or not
enable: false
+1 -1
View File
@@ -51,7 +51,7 @@
}
},
"devDependencies": {
"electron": "29.4.3",
"electron": "29.4.5",
"electron-builder": "24.13.3"
},
"dependencies": {
+4 -4
View File
@@ -14,8 +14,8 @@ dependencies:
devDependencies:
electron:
specifier: 29.4.3
version: 29.4.3
specifier: 29.4.5
version: 29.4.5
electron-builder:
specifier: 24.13.3
version: 24.13.3(electron-builder-squirrel-windows@24.13.3)
@@ -917,8 +917,8 @@ packages:
- supports-color
dev: true
/electron@29.4.3:
resolution: {integrity: sha512-Fr/D7Iru7zGFK5DtNf3Ck/DBxLHtP/o/32pRXsSoGTVqyHKu2genIiSUiOhbxdu+gQZw4Do+BDDR6inkyr4CGQ==}
/electron@29.4.5:
resolution: {integrity: sha512-DlEuzGbWBYl1Qr0qUYgNZdoixJg4YGHy2HC6fkRjSXSlb01UrQ5ORi8hNLzelzyYx8rNQyyE3zDUuk9EnZwYuA==}
engines: {node: '>= 12.20.55'}
hasBin: true
requiresBuild: true
+139
View File
@@ -0,0 +1,139 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1723409744,
"owner": "cachix",
"repo": "devenv",
"rev": "116159a4bfeaa79662f4c39dbfaf2fce6a5737f7",
"treeHash": "ccbae63055f83e669996a88a3cbb4ef69febd002",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"treeHash": "2addb7b71a20a25ea74feeaf5c2f6a6b30898ecb",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"treeHash": "ca14199cabdfe1a06a7b1654c76ed49100a689f9",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1716977621,
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "4267e705586473d3e5c8d50299e71503f16a6fb6",
"treeHash": "6d9f1f7ca0faf1bc2eeb397c78a49623260d3412",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1723282977,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a781ff33ae258bbcfd4ed6e673860c3e923bf2cc",
"treeHash": "75cf71293251c603e56d6076098346b4128b2ec8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1723175592,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5e0ca22929f3342b19569b21b2f3462f053e497b",
"treeHash": "a6b38d5232ae0ae0c666c37f5ec9620c14f30369",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1723202784,
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "c7012d0c18567c889b948781bc74a501e92275d1",
"treeHash": "0016d5d748d61d3bfaa69d3c02abff74740a46b7",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"nixpkgs": "nixpkgs",
"nixpkgs-unstable": "nixpkgs-unstable",
"pre-commit-hooks": "pre-commit-hooks"
}
}
},
"root": "root",
"version": 7
}
+31
View File
@@ -0,0 +1,31 @@
{ pkgs, lib, config, inputs, ... }:
let
pkgs-unstable = import inputs.nixpkgs-unstable { system = pkgs.stdenv.system; };
in {
packages = with pkgs-unstable; [
# General tools
git-cliff
# Frontend tools
cypress
# API tools
golangci-lint mage
# Desktop
electron
];
languages = {
javascript = {
enable = true;
package = pkgs-unstable.nodejs-slim;
pnpm = {
enable = true;
package = pkgs-unstable.pnpm;
};
};
go = {
enable = true;
};
};
}
+17
View File
@@ -0,0 +1,17 @@
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
inputs:
nixpkgs:
url: github:cachix/devenv-nixpkgs/rolling
nixpkgs-unstable:
url: github:NixOS/nixpkgs/nixos-unstable
# If you're using non-OSS software, you can set allowUnfree to true.
allowUnfree: true
# If you're willing to use a package that's vulnerable
# permittedInsecurePackages:
# - "openssl-1.1.1w"
# If you have more than one devenv you can merge them
#imports:
# - ./backend
Generated
-25
View File
@@ -1,25 +0,0 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1718149104,
"narHash": "sha256-Ds1QpobBX2yoUDx9ZruqVGJ/uQPgcXoYuobBguyKEh8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e913ae340076bbb73d9f4d3d065c2bca7caafb16",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
-20
View File
@@ -1,20 +0,0 @@
{
description = "Vikunja dev environment";
outputs = { self, nixpkgs }:
let pkgs = nixpkgs.legacyPackages.x86_64-linux;
in {
defaultPackage.x86_64-linux =
pkgs.mkShell { buildInputs = with pkgs; [
# General tools
git-cliff
# Frontend tools
pnpm cypress
# API tools
go golangci-lint mage
# Desktop
electron
];
};
};
}
-1
View File
@@ -42,7 +42,6 @@ module.exports = {
'error',
{ 'script': { 'lang': 'ts' } },
],
'vue/no-required-prop-with-default': ['error', { 'autofix': true }],
'vue/no-duplicate-attr-inheritance': 'error',
'vue/no-empty-component-block': 'error',
'vue/html-indent': ['error', 'tab'],
+1 -1
View File
@@ -10,7 +10,7 @@ stats.html
node_modules
.DS_Store
/dist*
dist
coverage
*.zip
+1 -1
View File
@@ -1 +1 @@
20.15.0
20.16.0
@@ -116,9 +116,9 @@ describe('Project View Kanban', () => {
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
.contains('Limit: Not Set')
.click()
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field input.input')
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .field input.input')
.first()
.type(3)
.type('3')
cy.get('[data-cy="setBucketLimit"]')
.first()
.click()
+58 -58
View File
@@ -13,7 +13,7 @@
},
"homepage": "https://vikunja.io/",
"funding": "https://opencollective.com/vikunja",
"packageManager": "pnpm@9.4.0",
"packageManager": "pnpm@9.6.0",
"keywords": [
"todo",
"productivity",
@@ -50,49 +50,50 @@
"story:preview": "histoire preview"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "6.5.2",
"@fortawesome/free-regular-svg-icons": "6.5.2",
"@fortawesome/free-solid-svg-icons": "6.5.2",
"@fortawesome/fontawesome-svg-core": "6.6.0",
"@fortawesome/free-regular-svg-icons": "6.6.0",
"@fortawesome/free-solid-svg-icons": "6.6.0",
"@fortawesome/vue-fontawesome": "3.0.8",
"@github/hotkey": "3.1.0",
"@github/hotkey": "3.1.1",
"@infectoone/vue-ganttastic": "2.3.2",
"@intlify/unplugin-vue-i18n": "4.0.0",
"@kyvg/vue3-notification": "3.2.1",
"@sentry/tracing": "7.114.0",
"@sentry/vue": "8.13.0",
"@tiptap/core": "2.4.0",
"@tiptap/extension-blockquote": "2.4.0",
"@tiptap/extension-bold": "2.4.0",
"@tiptap/extension-bullet-list": "2.4.0",
"@tiptap/extension-code": "2.4.0",
"@tiptap/extension-code-block-lowlight": "2.4.0",
"@tiptap/extension-document": "2.4.0",
"@tiptap/extension-dropcursor": "2.4.0",
"@tiptap/extension-gapcursor": "2.4.0",
"@tiptap/extension-hard-break": "2.4.0",
"@tiptap/extension-heading": "2.4.0",
"@tiptap/extension-history": "2.4.0",
"@tiptap/extension-horizontal-rule": "2.4.0",
"@tiptap/extension-image": "2.4.0",
"@tiptap/extension-italic": "2.4.0",
"@tiptap/extension-link": "2.4.0",
"@tiptap/extension-list-item": "2.4.0",
"@tiptap/extension-ordered-list": "2.4.0",
"@tiptap/extension-paragraph": "2.4.0",
"@tiptap/extension-placeholder": "2.4.0",
"@tiptap/extension-strike": "2.4.0",
"@tiptap/extension-table": "2.4.0",
"@tiptap/extension-table-cell": "2.4.0",
"@tiptap/extension-table-header": "2.4.0",
"@tiptap/extension-table-row": "2.4.0",
"@tiptap/extension-task-item": "2.4.0",
"@tiptap/extension-task-list": "2.4.0",
"@tiptap/extension-text": "2.4.0",
"@tiptap/extension-typography": "2.4.0",
"@tiptap/extension-underline": "2.4.0",
"@tiptap/pm": "2.4.0",
"@tiptap/suggestion": "2.4.0",
"@tiptap/vue-3": "2.4.0",
"@sentry/vue": "8.18.0",
"@tiptap/core": "2.5.4",
"@tiptap/extension-blockquote": "2.5.4",
"@tiptap/extension-bold": "2.5.4",
"@tiptap/extension-bullet-list": "2.5.4",
"@tiptap/extension-code": "2.5.4",
"@tiptap/extension-code-block": "2.5.4",
"@tiptap/extension-code-block-lowlight": "2.5.4",
"@tiptap/extension-document": "2.5.4",
"@tiptap/extension-dropcursor": "2.5.4",
"@tiptap/extension-gapcursor": "2.5.4",
"@tiptap/extension-hard-break": "2.5.4",
"@tiptap/extension-heading": "2.5.4",
"@tiptap/extension-history": "2.5.4",
"@tiptap/extension-horizontal-rule": "2.5.4",
"@tiptap/extension-image": "2.5.4",
"@tiptap/extension-italic": "2.5.4",
"@tiptap/extension-link": "2.5.4",
"@tiptap/extension-list-item": "2.5.4",
"@tiptap/extension-ordered-list": "2.5.4",
"@tiptap/extension-paragraph": "2.5.4",
"@tiptap/extension-placeholder": "2.5.4",
"@tiptap/extension-strike": "2.5.4",
"@tiptap/extension-table": "2.5.4",
"@tiptap/extension-table-cell": "2.5.4",
"@tiptap/extension-table-header": "2.5.4",
"@tiptap/extension-table-row": "2.5.4",
"@tiptap/extension-task-item": "2.5.4",
"@tiptap/extension-task-list": "2.5.4",
"@tiptap/extension-text": "2.5.4",
"@tiptap/extension-typography": "2.5.4",
"@tiptap/extension-underline": "2.5.4",
"@tiptap/pm": "2.5.4",
"@tiptap/suggestion": "2.5.4",
"@tiptap/vue-3": "2.5.4",
"@vueuse/core": "10.11.0",
"@vueuse/router": "10.11.0",
"axios": "1.7.2",
@@ -100,22 +101,22 @@
"bulma-css-variables": "0.9.33",
"change-case": "5.4.4",
"date-fns": "3.6.0",
"dayjs": "1.11.11",
"dompurify": "3.1.5",
"dayjs": "1.11.12",
"dompurify": "3.1.6",
"fast-deep-equal": "3.1.3",
"flatpickr": "4.6.13",
"flexsearch": "0.7.31",
"floating-vue": "5.2.2",
"is-touch-device": "1.0.1",
"klona": "2.0.6",
"lodash.debounce": "4.0.8",
"lowlight": "2.9.0",
"pinia": "2.1.7",
"register-service-worker": "1.7.2",
"sortablejs": "1.15.2",
"tailwindcss": "3.4.4",
"tippy.js": "6.3.7",
"ufo": "1.5.3",
"vue": "3.4.31",
"ufo": "1.5.4",
"vue": "3.4.32",
"vue-advanced-cropper": "2.8.9",
"vue-flatpickr-component": "11.0.5",
"vue-i18n": "9.13.1",
@@ -138,49 +139,48 @@
"@types/flexsearch": "0.7.6",
"@types/is-touch-device": "1.0.2",
"@types/lodash.clonedeep": "4.5.9",
"@types/lodash.debounce": "4.0.9",
"@types/node": "20.14.9",
"@types/node": "20.14.11",
"@types/sortablejs": "1.15.8",
"@typescript-eslint/eslint-plugin": "7.15.0",
"@typescript-eslint/parser": "7.15.0",
"@typescript-eslint/eslint-plugin": "7.16.1",
"@typescript-eslint/parser": "7.16.1",
"@vitejs/plugin-legacy": "5.4.1",
"@vitejs/plugin-vue": "5.0.5",
"@vue/eslint-config-typescript": "13.0.0",
"@vue/test-utils": "2.4.6",
"@vue/tsconfig": "0.5.1",
"autoprefixer": "10.4.19",
"browserslist": "4.23.1",
"caniuse-lite": "1.0.30001639",
"browserslist": "4.23.2",
"caniuse-lite": "1.0.30001642",
"csstype": "3.1.3",
"cypress": "13.13.0",
"cypress": "13.13.1",
"esbuild": "0.23.0",
"eslint": "8.57.0",
"eslint-plugin-vue": "9.26.0",
"eslint-plugin-vue": "9.27.0",
"happy-dom": "14.12.3",
"histoire": "0.17.17",
"postcss": "8.4.39",
"postcss-easing-gradients": "3.0.1",
"postcss-easings": "4.0.0",
"postcss-preset-env": "9.5.15",
"rollup": "4.18.0",
"postcss-preset-env": "9.6.0",
"rollup": "4.18.1",
"rollup-plugin-visualizer": "5.12.0",
"sass": "1.77.6",
"sass": "1.77.8",
"start-server-and-test": "2.0.4",
"typescript": "5.5.3",
"unplugin-inject-preload": "2.0.4",
"vite": "5.3.2",
"vite": "5.3.4",
"vite-plugin-pwa": "0.20.0",
"vite-plugin-sentry": "1.4.0",
"vite-svg-loader": "5.1.0",
"vitest": "1.6.0",
"vue-tsc": "2.0.24",
"vue-tsc": "2.0.26",
"wait-on": "7.2.0",
"workbox-cli": "7.1.0"
},
"pnpm": {
"patchedDependencies": {
"flexsearch@0.7.31": "patches/flexsearch@0.7.31.patch",
"@github/hotkey@3.1.0": "patches/@github__hotkey@3.1.0.patch"
"@github/hotkey@3.1.1": "patches/@github__hotkey@3.1.1.patch"
}
}
}
+1232 -1071
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,7 +1,7 @@
<template>
<Ready>
<template v-if="authUser">
<TheNavigation />
<AppHeader />
<ContentAuth />
</template>
<ContentLinkShare v-else-if="authLinkShare" />
@@ -30,7 +30,7 @@ import Notification from '@/components/misc/Notification.vue'
import UpdateNotification from '@/components/home/UpdateNotification.vue'
import KeyboardShortcuts from '@/components/misc/keyboard-shortcuts/index.vue'
import TheNavigation from '@/components/home/TheNavigation.vue'
import AppHeader from '@/components/home/AppHeader.vue'
import ContentAuth from '@/components/home/ContentAuth.vue'
import ContentLinkShare from '@/components/home/ContentLinkShare.vue'
import NoAuthWrapper from '@/components/misc/NoAuthWrapper.vue'
+1 -1
View File
@@ -72,7 +72,7 @@ export interface BaseButtonProps extends /* @vue-ignore */ HTMLAttributes {
openExternalInNewTab?: boolean
}
export interface BaseButtonEmits {
export type BaseButtonEmits = {
click: [payload: MouseEvent]
}
@@ -114,13 +114,19 @@ import BaseButton from '@/components/base/BaseButton.vue'
import DatemathHelp from '@/components/date/DatemathHelp.vue'
import {getFlatpickrLanguage} from '@/helpers/flatpickrLanguage'
const props = defineProps({
const props = defineProps<{
modelValue: {
required: false,
dateFrom: Date | string,
dateTo: Date | string,
},
})
}>()
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits<{
'update:modelValue': [value: {
dateFrom: Date | string,
dateTo: Date | string
}]
}>()
const {t} = useI18n({useScope: 'global'})
@@ -2,7 +2,7 @@
<div class="datepicker-with-range-container">
<Popup
:open="open"
@close="() => emit('close')"
@update:open="(open) => !open && $emit('update:open', false)"
>
<template #content="{isOpen}">
<div
@@ -91,18 +91,18 @@ import BaseButton from '@/components/base/BaseButton.vue'
import DatemathHelp from '@/components/date/DatemathHelp.vue'
import {getFlatpickrLanguage} from '@/helpers/flatpickrLanguage'
const props = defineProps({
modelValue: {
required: false,
default: null,
},
open: {
type: Boolean,
default: false,
},
const props = withDefaults(defineProps<{
modelValue: string | Date | null,
open: boolean
}>(), {
modelValue: null,
open: false,
})
const emit = defineEmits(['update:modelValue', 'close'])
const emit = defineEmits<{
'update:modelValue': [value: string | Date | null],
'update:open': [open: boolean],
}>()
const {t} = useI18n({useScope: 'global'})
@@ -70,7 +70,7 @@
>
<span class="username">{{ authStore.userDisplayName }}</span>
<span
class="icon is-small"
class="ml-1 dropdown-icon icon is-small"
:style="{
transform: open ? 'rotate(180deg)' : 'rotate(0)',
}"
@@ -161,13 +161,8 @@ $user-dropdown-width-mobile: 5rem;
background: var(--site-background);
@media screen and (max-width: $tablet) {
padding-right: .5rem;
}
@media screen and (min-width: $tablet) {
padding-left: 2rem;
padding-right: 1rem;
align-items: stretch;
}
@@ -263,11 +258,19 @@ $user-dropdown-width-mobile: 5rem;
}
.username-dropdown-trigger {
padding-left: 1rem;
padding-left: .75rem;
display: inline-flex;
align-items: center;
font-size: .85rem;
font-weight: 700;
@media screen and (max-width: $tablet) {
padding-right: .5rem;
}
@media screen and (min-width: $tablet) {
padding-right: .75rem;
}
}
.username {
@@ -278,6 +281,10 @@ $user-dropdown-width-mobile: 5rem;
}
}
.dropdown-icon {
transition: transform $transition;
}
.avatar {
border-radius: 100%;
vertical-align: middle;
@@ -160,6 +160,7 @@ projectStore.loadAllProjects()
}
.app-content {
display: flow-root;
z-index: 10;
position: relative;
padding: 1.5rem 0.5rem 0;
+3 -1
View File
@@ -109,7 +109,7 @@
</nav>
</template>
<PoweredByLink />
<PoweredByLink class="mt-auto" />
</aside>
</template>
@@ -146,6 +146,8 @@ const savedFilterProjects = computed(() => projectStore.savedFilterProjects)
}
.menu-container {
display: flex;
flex-direction: column;
background: var(--site-background);
color: $vikunja-nav-color;
padding: 1rem 0;
+3
View File
@@ -8,6 +8,7 @@
'has-no-shadow': !shadow || variant === 'tertiary',
}
]"
:disabled="disabled || loading"
:style="{
'--button-white-space': wrap ? 'break-spaces' : 'nowrap',
}"
@@ -44,6 +45,7 @@ export interface ButtonProps extends /* @vue-ignore */ BaseButtonProps {
icon?: IconProp
iconColor?: string
loading?: boolean
disabled?: boolean
shadow?: boolean
wrap?: boolean
}
@@ -53,6 +55,7 @@ const props = withDefaults(defineProps<ButtonProps>(), {
icon: undefined,
iconColor: undefined,
loading: false,
disabled: false,
shadow: true,
wrap: true,
})
@@ -70,13 +70,7 @@ import {computed, ref, watch} from 'vue'
import {createRandomID} from '@/helpers/randomId'
import XButton from '@/components/input/Button.vue'
const {
modelValue,
} = defineProps<{
modelValue: string,
}>()
const emit = defineEmits(['update:modelValue'])
const model = defineModel<string>({ required: true })
const DEFAULT_COLORS = [
'#1973ff',
@@ -93,7 +87,7 @@ const defaultColors = ref(DEFAULT_COLORS)
const colorListID = ref(createRandomID())
watch(
() => modelValue,
model,
(newValue) => {
if (newValue === '' || newValue.startsWith('var(')) {
color.value = ''
@@ -123,7 +117,7 @@ function update(force = false) {
}
lastChangeTimeout.value = setTimeout(() => {
emit('update:modelValue', color.value)
model.value = color.value
}, 500)
}
+16 -18
View File
@@ -33,7 +33,7 @@
</template>
<script setup lang="ts">
import {ref, onMounted, onBeforeUnmount, toRef, watch, type PropType} from 'vue'
import {ref, onMounted, onBeforeUnmount, toRef, watch} from 'vue'
import CustomTransition from '@/components/misc/CustomTransition.vue'
import DatepickerInline from '@/components/input/DatepickerInline.vue'
@@ -44,26 +44,24 @@ import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {createDateFromString} from '@/helpers/time/createDateFromString'
import {useI18n} from 'vue-i18n'
const props = defineProps({
modelValue: {
type: [Date, null, String] as PropType<Date | null | string>,
validator: prop => prop instanceof Date || prop === null || typeof prop === 'string',
default: null,
},
chooseDateLabel: {
type: String,
default() {
const {t} = useI18n({useScope: 'global'})
return t('input.datepicker.chooseDate')
},
},
disabled: {
type: Boolean,
default: false,
const props = withDefaults(defineProps<{
modelValue: Date | null | string,
chooseDateLabel: string,
disabled?: boolean,
}>(), {
modelValue: null,
chooseDateLabel: () => {
const {t} = useI18n({useScope: 'global'})
return t('input.datepicker.chooseDate')
},
disabled: false,
})
const emit = defineEmits(['update:modelValue', 'close', 'closeOnChange'])
const emit = defineEmits<{
'update:modelValue': [value: Date | null],
'close': [value: boolean],
'closeOnChange': [value: boolean],
}>()
const date = ref<Date | null>()
const show = ref(false)
@@ -71,7 +71,7 @@
</template>
<script lang="ts" setup>
import {computed, onBeforeUnmount, onMounted, type PropType, ref, toRef, watch} from 'vue'
import {computed, onBeforeUnmount, onMounted, ref, toRef, watch} from 'vue'
import flatPickr from 'vue-flatpickr-component'
import 'flatpickr/dist/flatpickr.css'
@@ -84,15 +84,15 @@ import {createDateFromString} from '@/helpers/time/createDateFromString'
import {useI18n} from 'vue-i18n'
import {getFlatpickrLanguage} from '@/helpers/flatpickrLanguage'
const props = defineProps({
modelValue: {
type: [Date, null, String] as PropType<Date | null | string>,
validator: prop => prop instanceof Date || prop === null || typeof prop === 'string',
default: null,
},
const props = withDefaults(defineProps<{
modelValue: Date | null | string
}>(), {
modelValue: null,
})
const emit = defineEmits(['update:modelValue', 'close-on-change'])
const emit = defineEmits<{
'update:modelValue': [Date | null],
}>()
const {t} = useI18n({useScope: 'global'})
@@ -21,24 +21,20 @@
<script setup lang="ts">
import CheckboxIcon from '@/assets/checkbox.svg?component'
import BaseCheckbox from '@/components/base/BaseCheckbox.vue'
defineProps({
modelValue: {
type: Boolean,
},
disabled: {
type: Boolean,
},
isBlock: {
type: Boolean,
default: false,
},
withDefaults(defineProps<{
modelValue: boolean,
disabled?: boolean,
isBlock?: boolean
}>(), {
modelValue: false,
disabled: false,
isBlock: false,
})
const emit = defineEmits<{
(event: 'update:modelValue', value: boolean): void
'update:modelValue': [value: boolean]
}>()
</script>
+61 -114
View File
@@ -40,10 +40,12 @@
</slot>
<input
:id="id"
ref="searchInput"
v-model="query"
type="text"
class="input"
:name="name"
:placeholder="placeholder"
:autocomplete="autocompleteEnabled ? undefined : 'off'"
:spellcheck="autocompleteEnabled ? undefined : 'false'"
@@ -119,9 +121,7 @@
</template>
<script setup lang="ts">
import {
computed, onBeforeUnmount, onMounted, ref, toRefs, watch, type ComponentPublicInstance, type PropType,
} from 'vue'
import {computed, onBeforeUnmount, onMounted, ref, toRefs, watch, type ComponentPublicInstance} from 'vue'
import {useI18n} from 'vue-i18n'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
@@ -129,132 +129,79 @@ import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import BaseButton from '@/components/base/BaseButton.vue'
import CustomTransition from '@/components/misc/CustomTransition.vue'
const props = defineProps({
/**
* When true, shows a loading spinner
*/
loading: {
type: Boolean,
default: false,
},
/**
* The placeholder of the search input
*/
placeholder: {
type: String,
default: '',
},
/**
* The search results where the @search listener needs to put the results into
*/
searchResults: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type: Array as PropType<{ [id: string]: any }>,
default: () => [],
},
/**
* The name of the property of the searched object to show the user.
* If empty the component will show all raw data of an entry.
*/
label: {
type: String,
default: '',
},
/**
* The object with the value, updated every time an entry is selected.
*/
modelValue: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type: [Object] as PropType<{ [key: string]: any }>,
default: null,
},
/**
* If true, will provide an "add this as a new value" entry which fires an @create event when clicking on it.
*/
creatable: {
type: Boolean,
default: false,
},
/**
* The text shown next to the new value option.
*/
createPlaceholder: {
type: String,
default() {
const {t} = useI18n({useScope: 'global'})
return t('input.multiselect.createPlaceholder')
},
},
/**
* The text shown next to an option.
*/
selectPlaceholder: {
type: String,
default() {
const {t} = useI18n({useScope: 'global'})
return t('input.multiselect.selectPlaceholder')
},
},
/**
* If true, allows for selecting multiple items. v-model will be an array with all selected values in that case.
*/
multiple: {
type: Boolean,
default: false,
},
/**
* If true, displays the search results inline instead of using a dropdown.
*/
inline: {
type: Boolean,
default: false,
},
/**
* If true, shows search results when no query is specified.
*/
showEmpty: {
type: Boolean,
default: true,
},
/**
* The delay in ms after which the search event will be fired. Used to avoid hitting the network on every keystroke.
*/
searchDelay: {
type: Number,
default: 200,
},
closeAfterSelect: {
type: Boolean,
default: true,
},
/**
* If false, the search input will get the autocomplete="off" attributes attached to it.
*/
autocompleteEnabled: {
type: Boolean,
default: true,
},
const props = withDefaults(defineProps<{
/** The object with the value, updated every time an entry is selected */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
modelValue: { [id: string]: any } | null
/** When true, shows a loading spinner */
loading?: boolean
/** The placeholder of the search input */
placeholder?: string
/** The search results where the @search listener needs to put the results into */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
searchResults?: { [id: string]: any }[]
/** The name of the property of the searched object to show the user. If empty the component will show all raw data of an entry */
label?: string
/** The id attribute of the input element */
id?: string
/** The name attribute of the input element */
name?: string
/** If true, will provide an 'add this as a new value' entry which fires an @create event when clicking on it. */
creatable?: boolean
/** The text shown next to the new value option. */
createPlaceholder?: string
/** The text shown next to an option. */
selectPlaceholder: string
/** If true, allows for selecting multiple items. v-model will be an array with all selected values in that case. */
multiple?: boolean
/** If true, displays the search results inline instead of using a dropdown. */
inline?: boolean
/** If true, shows search results when no query is specified. */
showEmpty?: boolean
/** The delay in ms after which the search event will be fired. Used to avoid hitting the network on every keystroke. */
searchDelay?: number
/** If true, closes the dropdown after an entry is selected */
closeAfterSelect?: boolean
/** If false, the search input will get the autocomplete="off" attributes attached to it. */
autocompleteEnabled?: boolean
}>(), {
modelValue: null,
loading: false,
placeholder: '',
searchResults: () => [],
label: '',
creatable: false,
createPlaceholder: () => useI18n().t('input.multiselect.createPlaceholder'),
selectPlaceholder: () => useI18n().t('input.multiselect.selectPlaceholder'),
multiple: false,
inline: false,
showEmpty: false,
searchDelay: 200,
closeAfterSelect: true,
autocompleteEnabled: true,
})
const emit = defineEmits<{
(e: 'update:modelValue', value: null): void
// eslint-disable-next-line @typescript-eslint/no-explicit-any
'update:modelValue': [value: { [key: string]: any }[] | null],
/**
* Triggered every time the search query input changes
*/
(e: 'search', query: string): void
'search': [query: string],
/**
* Triggered every time an option from the search results is selected. Also triggers a change in v-model.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(e: 'select', value: { [key: string]: any }): void
'select': [value: { [key: string]: any }],
/**
* If nothing or no exact match was found and `creatable` is true, this event is triggered with the current value of the search query.
*/
(e: 'create', query: string): void
'create': [query: string],
/**
* If `multiple` is enabled, this will be fired every time an item is removed from the array of selected items.
*/
(e: 'remove', value: null): void
// eslint-disable-next-line @typescript-eslint/no-explicit-any
'remove': [value: { [key: string]: any }],
}>()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -324,7 +271,7 @@ const hasMultiple = computed(() => {
return props.multiple && Array.isArray(internalValue.value) && internalValue.value.length > 0
})
const removalAvailable = computed(() => !props.multiple && internalValue.value !== null && query.value !== '')
const removalAvailable = computed(() => !props.multiple && internalValue.value !== null && query.value !== '' && !(props.loading || localLoading.value))
function resetSelectedValue() {
select(null)
}
+14 -15
View File
@@ -32,33 +32,32 @@
</template>
<script lang="ts" setup>
import {ref, watch} from 'vue'
import {ref, watchEffect} from 'vue'
import {useDebounceFn} from '@vueuse/core'
import {useI18n} from 'vue-i18n'
import BaseButton from '@/components/base/BaseButton.vue'
const props = defineProps({
tabindex: String,
modelValue: String,
const props = withDefaults(defineProps<{
modelValue: string,
tabindex?: string,
// This prop is a workaround to trigger validation from the outside when the user never had focus in the input.
validateInitially: Boolean,
validateMinLength: {
type: Boolean,
default: true,
},
validateInitially?: boolean,
validateMinLength?: boolean,
}>(), {
validateMinLength: true,
})
const emit = defineEmits(['submit', 'update:modelValue'])
const emit = defineEmits<{
'update:modelValue': [value: string],
'submit': [event: Event],
}>()
const {t} = useI18n()
const passwordFieldType = ref('password')
const password = ref('')
const isValid = ref<true | string>(props.validateInitially === true ? true : '')
const validateAfterFirst = ref(false)
watch(
() => props.validateInitially,
() => props.validateInitially && validate(),
{immediate: true},
)
watchEffect(() => props.validateInitially && validate())
const validate = useDebounceFn(() => {
if (password.value === '') {
+10 -12
View File
@@ -13,27 +13,25 @@ import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {useAuthStore} from '@/stores/auth'
import {useColorScheme} from '@/composables/useColorScheme'
const {
entityKind,
entityId,
disabled = false,
} = defineProps<{
const props = withDefaults(defineProps<{
entityKind: ReactionKind,
entityId: number,
disabled?: boolean,
}>()
}>(), {
disabled: false,
})
const model = defineModel<IReactionPerEntity>()
const authStore = useAuthStore()
const {t} = useI18n()
const reactionService = new ReactionService()
const {isDark} = useColorScheme()
const model = defineModel<IReactionPerEntity>()
async function addReaction(value: string) {
const reaction = new ReactionModel({
id: entityId,
kind: entityKind,
id: props.entityId,
kind: props.entityKind,
value,
})
await reactionService.create(reaction)
@@ -52,8 +50,8 @@ async function addReaction(value: string) {
async function removeReaction(value: string) {
const reaction = new ReactionModel({
id: entityId,
kind: entityKind,
id: props.entityId,
kind: props.entityKind,
value,
})
await reactionService.delete(reaction)
@@ -11,7 +11,7 @@
</template>
<script setup lang="ts">
import {computed, ref, shallowReactive, watchEffect, type PropType} from 'vue'
import {computed, ref, shallowReactive, watchEffect} from 'vue'
import Multiselect from '@/components/input/Multiselect.vue'
@@ -20,23 +20,16 @@ import type {IProject} from '@/modelTypes/IProject'
import ProjectService from '@/services/project'
import {includesById} from '@/helpers/utils'
type ProjectFilterFunc = (p: IProject) => boolean
const props = defineProps({
modelValue: {
type: Array as PropType<IProject[]>,
default: () => [],
},
projectFilter: {
type: Function as PropType<ProjectFilterFunc>,
default: () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
return (_: IProject) => true
},
},
const props = withDefaults(defineProps<{
modelValue: IProject[],
projectFilter: (p: IProject) => boolean
}>(), {
modelValue: () => [],
projectFilter: () => () => true,
})
const emit = defineEmits<{
(e: 'update:modelValue', value: IProject[]): void
'update:modelValue': [value: IProject[]]
}>()
const projects = ref<IProject[]>([])
+7 -7
View File
@@ -11,7 +11,7 @@
</template>
<script setup lang="ts">
import {computed, ref, shallowReactive, watchEffect, type PropType} from 'vue'
import {computed, ref, shallowReactive, watchEffect} from 'vue'
import Multiselect from '@/components/input/Multiselect.vue'
@@ -20,14 +20,14 @@ import type {IUser} from '@/modelTypes/IUser'
import UserService from '@/services/user'
import {includesById} from '@/helpers/utils'
const props = defineProps({
modelValue: {
type: Array as PropType<IUser[]>,
default: () => [],
},
const props = withDefaults(defineProps<{
modelValue: IUser[] | undefined
}>(), {
modelValue: () => [],
})
const emit = defineEmits<{
(e: 'update:modelValue', value: IUser[]): void
'update:modelValue': [value: IUser[]]
}>()
const users = ref<IUser[]>([])
@@ -325,6 +325,13 @@ watch(
},
)
const additionalLinkProtocols = [
'ftp',
'git',
'obsidian',
'notion',
]
const extensions : Extensions = [
// Starterkit:
Blockquote,
@@ -379,7 +386,11 @@ const extensions : Extensions = [
Underline,
Link.configure({
openOnClick: false,
validate: (href: string) => /^https?:\/\//.test(href),
validate: (href: string) => (new RegExp(
`^(https?|${additionalLinkProtocols.join('|')}):\\/\\/`,
'i',
)).test(href),
protocols: additionalLinkProtocols,
}),
Table.configure({
resizable: true,
@@ -696,6 +707,9 @@ watch(
<style lang="scss">
.tiptap__editor {
transition: box-shadow $transition;
border-radius: $radius;
&.tiptap__editor-is-edit-enabled {
min-height: 10rem;
@@ -711,9 +725,6 @@ watch(
cursor: text;
}
}
transition: box-shadow $transition;
border-radius: $radius;
}
.tiptap p::before {
+7 -7
View File
@@ -71,14 +71,14 @@ import {success} from '@/message'
import Message from '@/components/misc/Message.vue'
import ButtonLink from '@/components/misc/ButtonLink.vue'
const props = defineProps({
configureOpen: {
type: Boolean,
required: false,
default: false,
},
const props = withDefaults(defineProps<{
configureOpen?: boolean
}>(), {
configureOpen: false,
})
const emit = defineEmits(['foundApi'])
const emit = defineEmits<{
'foundApi': [url: string],
}>()
const apiUrl = ref(window.API_URL)
const configureApi = ref(window.API_URL === '')
+4 -4
View File
@@ -11,7 +11,7 @@
{{ title }}
</p>
<BaseButton
v-if="hasClose"
v-if="$attrs.onClose"
v-tooltip="$t('misc.close')"
class="card-header-icon"
:aria-label="$t('misc.close')"
@@ -51,7 +51,6 @@ import BaseButton from '@/components/base/BaseButton.vue'
withDefaults(defineProps<{
title?: string
padding?: boolean
hasClose?: boolean
closeIcon?: IconProp
shadow?: boolean
hasContent?: boolean
@@ -59,14 +58,15 @@ withDefaults(defineProps<{
}>(), {
title: '',
padding: true,
hasClose: false,
closeIcon: 'times',
shadow: true,
hasContent: true,
loading: false,
})
defineEmits(['close'])
defineEmits<{
'close': []
}>()
</script>
<style lang="scss" scoped>
+10 -7
View File
@@ -9,7 +9,6 @@
:shadow="false"
:padding="false"
class="has-text-left"
:has-close="true"
:loading="loading"
@close="$router.back()"
>
@@ -23,7 +22,7 @@
v-if="tertiary !== ''"
:shadow="false"
variant="tertiary"
@click.prevent.stop="$emit('tertiary')"
@click.prevent.stop="$emit('tertiary', $event)"
>
{{ tertiary }}
</x-button>
@@ -39,7 +38,7 @@
:icon="primaryIcon"
:disabled="primaryDisabled || loading"
class="ml-2"
@click.prevent.stop="primary()"
@click.prevent.stop="primary"
>
{{ primaryLabel || $t('misc.create') }}
</x-button>
@@ -71,10 +70,14 @@ withDefaults(defineProps<{
loading: false,
})
const emit = defineEmits(['create', 'primary', 'tertiary'])
const emit = defineEmits<{
'create': [event: MouseEvent],
'primary': [event: MouseEvent],
'tertiary': [event: MouseEvent]
}>()
function primary() {
emit('create')
emit('primary')
function primary(event: MouseEvent) {
emit('create', event)
emit('primary', event)
}
</script>
+24 -9
View File
@@ -2,6 +2,7 @@
<div
ref="dropdown"
class="dropdown"
@pointerenter="initialMount = true"
>
<slot
name="trigger"
@@ -22,7 +23,8 @@
<CustomTransition name="fade">
<div
v-if="open"
v-if="initialMount || open"
v-show="open"
class="dropdown-menu"
>
<div class="dropdown-content">
@@ -34,21 +36,34 @@
</template>
<script setup lang="ts">
import {ref, type PropType} from 'vue'
import {ref} from 'vue'
import {onClickOutside} from '@vueuse/core'
import type {IconProp} from '@fortawesome/fontawesome-svg-core'
import CustomTransition from '@/components/misc/CustomTransition.vue'
import BaseButton from '@/components/base/BaseButton.vue'
defineProps({
triggerIcon: {
type: String as PropType<IconProp>,
default: 'ellipsis-h',
},
withDefaults(defineProps<{
triggerIcon?: IconProp
}>(), {
triggerIcon: 'ellipsis-h',
})
const emit = defineEmits(['close'])
const emit = defineEmits<{
'close': [event: PointerEvent]
}>()
defineSlots<{
'trigger': (props: {
close: () => void,
toggleOpen: () => void,
open: boolean
}) => void,
'default': () => void
}>()
const initialMount = ref(false)
const open = ref(false)
function close() {
@@ -60,7 +75,7 @@ function toggleOpen() {
}
const dropdown = ref()
onClickOutside(dropdown, (e: Event) => {
onClickOutside(dropdown, (e) => {
if (!open.value) {
return
}
@@ -1,6 +1,7 @@
<template>
<BaseButton
class="dropdown-item"
:disabled="disabled"
:class="{'is-disabled': disabled}"
>
<span
+2 -2
View File
@@ -28,7 +28,7 @@ import {
faDownload,
faEllipsisH,
faEllipsisV,
faExclamation,
faExclamationCircle,
faEye,
faEyeSlash,
faFile,
@@ -138,7 +138,7 @@ library.add(faCopy)
library.add(faDownload)
library.add(faEllipsisH)
library.add(faEllipsisV)
library.add(faExclamation)
library.add(faExclamationCircle)
library.add(faEye)
library.add(faEyeSlash)
library.add(faFillDrip)
@@ -53,11 +53,11 @@ import ApiConfig from '@/components/misc/ApiConfig.vue'
import {useTitle} from '@/composables/useTitle'
import {useConfigStore} from '@/stores/config'
const {
showApiConfig = true,
} = defineProps<{
withDefaults(defineProps<{
showApiConfig?: boolean
}>()
}>(), {
showApiConfig: true,
})
const configStore = useConfigStore()
const motd = computed(() => configStore.motd)
+5 -9
View File
@@ -45,15 +45,11 @@
<script lang="ts" setup>
import {computed} from 'vue'
const props = defineProps({
totalPages: {
type: Number,
required: true,
},
currentPage: {
type: Number,
default: 0,
},
const props = withDefaults(defineProps<{
totalPages: number,
currentPage?: number
}>(), {
currentPage: 0,
})
function createPagination(totalPages: number, currentPage: number) {
+23 -8
View File
@@ -34,7 +34,22 @@ const props = withDefaults(defineProps<{
open: false,
})
const emit = defineEmits(['close'])
const emit = defineEmits<{
'update:open': [open: boolean]
}>()
defineSlots<{
trigger(props: {
isOpen: boolean,
toggle: () => boolean,
close: () => void,
}) : void
content(props: {
isOpen: boolean,
toggle: () => boolean,
close: () => void
}): void
}>()
const openValue = ref(props.open)
watchEffect(() => {
@@ -42,22 +57,22 @@ watchEffect(() => {
})
function close() {
if (!openValue.value) {
return
}
openValue.value = false
emit('close')
emit('update:open', false)
}
function toggle() {
openValue.value = !openValue.value
emit('update:open', openValue.value)
return openValue.value
}
const popup = ref<HTMLElement | null>(null)
onClickOutside(popup, () => {
if (!openValue.value) {
return
}
close()
})
onClickOutside(popup, () => close())
</script>
<style scoped lang="scss">
+7 -13
View File
@@ -13,19 +13,13 @@
</template>
<script setup lang="ts">
defineProps({
value: {
type: Number,
required: true,
},
isSmall: {
type: Boolean,
default: false,
},
isPrimary: {
type: Boolean,
required: false,
},
withDefaults(defineProps<{
value: number
isSmall?: boolean
isPrimary?: boolean
}>(), {
isSmall: false,
isPrimary: false,
})
</script>
@@ -47,18 +47,20 @@ import {success} from '@/message'
import type { IconProp } from '@fortawesome/fontawesome-svg-core'
const props = withDefaults(defineProps<{
modelValue: ISubscription | null,
entity: ISubscription['entity'],
entityId: number,
isButton?: boolean,
modelValue?: ISubscription,
type?: 'button' | 'dropdown',
}>(), {
isButton: true,
modelValue: null,
isButton: true,
type: 'button',
})
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits<{
'update:modelValue': [subscription: ISubscription | null]
}>()
const subscriptionEntity = computed<string | null>(() => props.modelValue?.entity ?? null)
@@ -91,7 +93,7 @@ const tooltipText = computed(() => {
const buttonText = computed(() => props.modelValue ? t('task.subscription.unsubscribe') : t('task.subscription.subscribe'))
const iconName = computed<IconProp>(() => props.modelValue ? ['far', 'bell-slash'] : 'bell')
const disabled = computed(() => props.modelValue && subscriptionEntity.value !== props.entity)
const disabled = computed(() => props.modelValue && subscriptionEntity.value !== props.entity || false)
function changeSubscription() {
if (disabled.value) {
@@ -16,13 +16,13 @@
class="switch-view"
>
<BaseButton
v-for="v in views"
:key="v.id"
v-for="view in views"
:key="view.id"
class="switch-view-button"
:class="{'is-active': v.id === viewId}"
:to="{ name: 'project.view', params: { projectId, viewId: v.id } }"
:class="{'is-active': view.id === viewId}"
:to="{ name: 'project.view', params: { projectId, viewId: view.id } }"
>
{{ getViewTitle(v) }}
{{ getViewTitle(view) }}
</BaseButton>
</div>
<slot name="header" />
@@ -62,10 +62,7 @@ import type {IProject} from '@/modelTypes/IProject'
import type {IProjectView} from '@/modelTypes/IProjectView'
import {useI18n} from 'vue-i18n'
const {
projectId,
viewId,
} = defineProps<{
const props = defineProps<{
projectId: IProject['id'],
viewId: IProjectView['id'],
}>()
@@ -88,7 +85,7 @@ const currentProject = computed<IProject>(() => {
})
useTitle(() => currentProject.value?.id ? getProjectTitle(currentProject.value) : '')
const views = computed(() => projectStore.projects[projectId]?.views)
const views = computed(() => projectStore.projects[props.projectId]?.views)
// watchEffect would be called every time the prop would get a value assigned, even if that value was the same as before.
// This resulted in loading and setting the project multiple times, even when navigating away from it.
@@ -96,7 +93,7 @@ const views = computed(() => projectStore.projects[projectId]?.views)
// project background and then navigating to home. It also highlighted the project in the menu and didn't allow changing any
// of it, most likely due to the rights not being properly populated.
watch(
() => projectId,
() => props.projectId,
// loadProject
async (projectIdToLoad: number) => {
const projectData = {id: projectIdToLoad}
@@ -112,7 +109,7 @@ watch(
)
&& typeof currentProject.value !== 'undefined' && currentProject.value.maxRight !== null
) {
loadedProjectId.value = projectId
loadedProjectId.value = projectIdToLoad
return
}
@@ -122,21 +119,29 @@ watch(
loadedProjectId.value = 0
const projectFromStore = projectStore.projects[projectData.id]
if (projectFromStore) {
baseStore.handleSetCurrentProject({project: projectFromStore})
baseStore.handleSetCurrentProject({project: projectFromStore, currentProjectViewId: props.viewId})
}
// We create an extra project object instead of creating it in project.value because that would trigger a ui update which would result in bad ux.
const project = new ProjectModel(projectData)
try {
const loadedProject = await projectService.value.get(project)
baseStore.handleSetCurrentProject({project: loadedProject})
baseStore.handleSetCurrentProject({project: loadedProject, currentProjectViewId: props.viewId})
} finally {
loadedProjectId.value = projectId
loadedProjectId.value = projectIdToLoad
}
},
{immediate: true},
)
watch(
() => props.viewId,
() => {
baseStore.setCurrentProjectViewId(props.viewId)
},
{immediate: true},
)
function getViewTitle(view: IProjectView) {
switch (view.title) {
case 'List':
@@ -23,17 +23,16 @@ import {
import {useDebounceFn} from '@vueuse/core'
import {createRandomID} from '@/helpers/randomId'
const {
modelValue,
projectId,
inputLabel = undefined,
} = defineProps<{
const props = defineProps<{
modelValue: string,
projectId?: number,
inputLabel?: string,
}>()
const emit = defineEmits(['update:modelValue', 'blur'])
const emit = defineEmits<{
'update:modelValue': [value: string],
'blur': [],
}>()
const userService = new UserService()
const projectUserService = new ProjectUserService()
@@ -49,9 +48,9 @@ const {
const id = ref(createRandomID())
watch(
() => modelValue,
() => props.modelValue,
() => {
filterQuery.value = modelValue
filterQuery.value = props.modelValue
},
{immediate: true},
)
@@ -59,7 +58,7 @@ watch(
watch(
() => filterQuery.value,
() => {
if (filterQuery.value !== modelValue) {
if (filterQuery.value !== props.modelValue) {
emit('update:modelValue', filterQuery.value)
}
},
@@ -204,6 +203,7 @@ const autocompleteResultType = ref<'labels' | 'assignees' | 'projects' | null>(n
const autocompleteResults = ref<any[]>([])
function handleFieldInput() {
if (!filterInput.value) return
const cursorPosition = filterInput.value.selectionStart
const textUpToCursor = filterQuery.value.substring(0, cursorPosition)
autocompleteResults.value = []
@@ -230,15 +230,15 @@ function handleFieldInput() {
}
if (matched.startsWith('assignee')) {
autocompleteResultType.value = 'assignees'
if (projectId) {
projectUserService.getAll({projectId}, {s: search})
if (props.projectId) {
projectUserService.getAll({projectId: props.projectId}, {s: search})
.then(users => autocompleteResults.value = users.length > 1 ? users : [])
} else {
userService.getAll({}, {s: search})
.then(users => autocompleteResults.value = users.length > 1 ? users : [])
}
}
if (!projectId && matched.startsWith('project')) {
if (!props.projectId && matched.startsWith('project')) {
autocompleteResultType.value = 'projects'
autocompleteResults.value = projectStore.searchProject(search)
}
@@ -304,8 +304,7 @@ const blurDebounced = useDebounceFn(() => emit('blur'), 500)
/>
<DatepickerWithValues
v-model="currentDatepickerValue"
:open="datePickerPopupOpen"
@close="() => datePickerPopupOpen = false"
v-model:open="datePickerPopupOpen"
@update:modelValue="updateDateInQuery"
/>
</div>
@@ -19,8 +19,8 @@
v-model="value"
:has-title="true"
class="filter-popup"
@update:modelValue="emitChanges"
@showResultsButtonClicked="() => modalOpen = false"
:change-immediately="false"
@showResults="showResults"
/>
</Modal>
</template>
@@ -32,8 +32,12 @@ import Filters from '@/components/project/partials/Filters.vue'
import {type TaskFilterParams} from '@/services/taskCollection'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const props = defineProps<{
modelValue: TaskFilterParams,
}>()
const emit = defineEmits<{
'update:modelValue': [value: TaskFilterParams]
}>()
const value = ref<TaskFilterParams>({})
@@ -42,23 +46,27 @@ watch(
(modelValue: TaskFilterParams) => {
value.value = modelValue
},
{immediate: true},
{
immediate: true,
deep: true,
},
)
function emitChanges(newValue: TaskFilterParams) {
emit('update:modelValue', {
...value.value,
filter: newValue.filter,
s: newValue.s,
})
}
const hasFilters = computed(() => {
return value.value.filter !== '' ||
value.value.s !== ''
})
const modalOpen = ref(false)
function showResults() {
emit('update:modelValue', {
...value.value,
filter: value.value.filter,
s: value.value.s,
})
modalOpen.value = false
}
</script>
<style scoped lang="scss">
@@ -7,13 +7,14 @@
<FilterInput
v-model="filterQuery"
:project-id="projectId"
@blur="change()"
@update:modelValue="() => change('modelValue')"
@blur="() => change('blur')"
/>
<div class="field is-flex is-flex-direction-column">
<FancyCheckbox
v-model="params.filter_include_nulls"
@blur="change()"
@change="() => change('always')"
>
{{ $t('filters.attributes.includeNulls') }}
</FancyCheckbox>
@@ -58,17 +59,21 @@ import {useProjectStore} from '@/stores/projects'
import {FILTER_OPERATORS, transformFilterStringForApi, transformFilterStringFromApi} from '@/helpers/filters'
import FilterInputDocs from '@/components/project/partials/FilterInputDocs.vue'
const {
hasTitle = false,
hasFooter = true,
modelValue,
} = defineProps<{
const props = withDefaults(defineProps<{
modelValue: TaskFilterParams,
hasTitle?: boolean,
hasFooter?: boolean,
modelValue: TaskFilterParams,
}>()
changeImmediately?: boolean,
}>(), {
hasTitle: false,
hasFooter: true,
changeImmediately: false,
})
const emit = defineEmits(['update:modelValue', 'showResultsButtonClicked'])
const emit = defineEmits<{
'update:modelValue': [value: TaskFilterParams],
'showResults': [],
}>()
const route = useRoute()
const projectId = computed(() => {
@@ -102,7 +107,7 @@ const projectStore = useProjectStore()
// Using watchDebounced to prevent the filter re-triggering itself.
watch(
() => modelValue,
() => props.modelValue,
(value: TaskFilterParams) => {
const val = {...value}
val.filter = transformFilterStringFromApi(
@@ -112,10 +117,28 @@ watch(
)
params.value = val
},
{immediate: true},
{
immediate: true,
deep: true,
},
)
function change() {
function change(event: 'blur' | 'modelValue' | 'always') {
if (event !== 'always') {
// The filter edit setting needs to save immediately, but the filter query edit in project views should
// only change on blur, or it will show the filter replaced for api when the query is not yet complete.
// This is highly confusing UX, hence we want to avoid that.
// The approach taken here allows us to either toggle on blur or immediately, depending on the prop
// value provided. This probably is a hacky way to do this, but it is also the most effective.
if (props.changeImmediately && event === 'blur') {
return
}
if (!props.changeImmediately && event === 'modelValue') {
return
}
}
const filter = transformFilterStringForApi(
filterQuery.value,
labelTitle => labelStore.getLabelByExactTitle(labelTitle)?.id || null,
@@ -139,7 +162,7 @@ function change() {
s,
}
if (JSON.stringify(modelValue) === JSON.stringify(newParams)) {
if (JSON.stringify(props.modelValue) === JSON.stringify(newParams)) {
return
}
@@ -148,7 +171,7 @@ function change() {
function changeAndEmitButton() {
change()
emit('showResultsButtonClicked')
emit('showResults')
}
function clearFiltersAndEmit() {
@@ -11,24 +11,19 @@
</template>
<script lang="ts" setup>
import {computed, type PropType} from 'vue'
import {computed} from 'vue'
import type {IProject} from '@/modelTypes/IProject'
import ProjectCard from './ProjectCard.vue'
const props = defineProps({
projects: {
type: Array as PropType<IProject[]>,
default: () => [],
},
showArchived: {
default: false,
type: Boolean,
},
itemLimit: {
type: Boolean,
default: false,
},
const props = withDefaults(defineProps<{
projects: IProject[],
showArchived?: boolean,
itemLimit?: boolean
}>(), {
projects: () => [],
showArchived: false,
itemLimit: false,
})
const filteredProjects = computed(() => {
@@ -71,41 +71,40 @@
trigger-icon="ellipsis-v"
@close="() => showSetLimitInput = false"
>
<div
v-if="showSetLimitInput"
class="field has-addons"
>
<div class="control">
<input
ref="bucketLimitInputRef"
v-focus.always
:value="bucket.limit"
class="input"
type="number"
min="0"
@keyup.esc="() => showSetLimitInput = false"
@keyup.enter="() => {setBucketLimit(bucket.id, true); showSetLimitInput = false}"
@input="setBucketLimit(bucket.id)"
>
</div>
<div class="control">
<x-button
v-cy="'setBucketLimit'"
:disabled="bucket.limit < 0"
:icon="['far', 'save']"
:shadow="false"
@click="() => {setBucketLimit(bucket.id, true); showSetLimitInput = false}"
/>
</div>
</div>
<DropdownItem
v-else
@click.stop="showSetLimitInput = true"
>
<div
v-if="showSetLimitInput"
class="field has-addons"
>
<div class="control">
<input
ref="bucketLimitInputRef"
v-focus.always
:value="bucket.limit"
class="input"
type="number"
min="0"
@keyup.esc="() => showSetLimitInput = false"
@keyup.enter="() => {setBucketLimit(bucket.id, true); showSetLimitInput = false}"
@input="setBucketLimit(bucket.id)"
>
</div>
<div class="control">
<x-button
v-cy="'setBucketLimit'"
:disabled="bucket.limit < 0"
:icon="['far', 'save']"
:shadow="false"
@click="setBucketLimit(bucket.id, true)"
/>
</div>
</div>
<template v-else>
{{
$t('project.kanban.limit', {limit: bucket.limit > 0 ? bucket.limit : $t('project.kanban.noLimit')})
}}
</template>
{{
$t('project.kanban.limit', {limit: bucket.limit > 0 ? bucket.limit : $t('project.kanban.noLimit')})
}}
</DropdownItem>
<DropdownItem
v-tooltip="$t('project.kanban.doneBucketHintExtended')"
@@ -131,6 +130,7 @@
</DropdownItem>
<DropdownItem
v-tooltip="buckets.length <= 1 ? $t('project.kanban.deleteLast') : ''"
class="has-text-danger"
:class="{'is-disabled': buckets.length <= 1}"
icon-class="has-text-danger"
icon="trash-alt"
@@ -156,7 +156,7 @@
>
<template #footer>
<div
v-if="canWrite"
v-if="canCreateTasks"
class="bucket-footer"
>
<div
@@ -309,10 +309,7 @@ import ProjectViewModel from '@/models/projectView'
import TaskBucketService from '@/services/taskBucket'
import TaskBucketModel from '@/models/taskBucket'
const {
projectId,
viewId,
} = defineProps<{
const props = defineProps<{
projectId: number,
viewId: IProjectView['id'],
}>()
@@ -392,9 +389,10 @@ const bucketDraggableComponentData = computed(() => ({
{'dragging-disabled': !canWrite.value},
],
}))
const project = computed(() => projectId ? projectStore.projects[projectId] : null)
const view = computed<IProjectView | null>(() => project.value?.views.find(v => v.id === viewId) || null)
const project = computed(() => props.projectId ? projectStore.projects[props.projectId] : null)
const view = computed(() => project.value?.views.find(v => v.id === props.viewId) as IProjectView || null)
const canWrite = computed(() => baseStore.currentProject?.maxRight > Rights.READ && view.value.bucketConfigurationMode === 'manual')
const canCreateTasks = computed(() => canWrite.value && props.projectId > 0)
const buckets = computed(() => kanbanStore.buckets)
const loading = computed(() => kanbanStore.isLoading)
@@ -403,10 +401,10 @@ const taskLoading = computed(() => taskStore.isLoading || taskPositionService.va
watch(
() => ({
params: params.value,
projectId,
viewId,
projectId: props.projectId,
viewId: props.viewId,
}),
({params}) => {
({params, projectId, viewId}) => {
if (projectId === undefined || Number(projectId) === 0) {
return
}
@@ -435,8 +433,8 @@ function handleTaskContainerScroll(id: IBucket['id'], el: HTMLElement) {
}
kanbanStore.loadNextTasksForBucket(
projectId,
viewId,
props.projectId,
props.viewId,
params.value,
id,
)
@@ -490,12 +488,6 @@ async function updateTaskPosition(e) {
taskBefore !== null ? taskBefore.position : null,
taskAfter !== null ? taskAfter.position : null,
)
if (
oldBucket !== undefined && // This shouldn't actually be `undefined`, but let's play it safe.
newBucket.id !== oldBucket.id
) {
newTask.done = project.value?.doneBucketId === newBucket.id
}
let bucketHasChanged = false
if (
@@ -516,18 +508,23 @@ async function updateTaskPosition(e) {
try {
const newPosition = new TaskPositionModel({
position,
projectViewId: viewId,
projectViewId: props.viewId,
taskId: newTask.id,
})
await taskPositionService.value.update(newPosition)
if(bucketHasChanged) {
await taskBucketService.value.update(new TaskBucketModel({
const updatedTaskBucket = await taskBucketService.value.update(new TaskBucketModel({
taskId: newTask.id,
bucketId: newTask.bucketId,
projectViewId: viewId,
projectViewId: props.viewId,
projectId: project.value.id,
}))
newTask.done = updatedTaskBucket.taskDone
if (updatedTaskBucket.bucketId !== newTask.bucketId) {
kanbanStore.moveTaskToBucket(newTask, updatedTaskBucket.bucketId)
}
kanbanStore.setTaskInBucket(newTask)
}
// Make sure the first and second task don't both get position 0 assigned
@@ -586,7 +583,7 @@ async function createNewBucket() {
await kanbanStore.createBucket(new BucketModel({
title: newBucketTitle.value,
projectId: project.value.id,
projectViewId: viewId,
projectViewId: props.viewId,
}))
newBucketTitle.value = ''
}
@@ -606,7 +603,7 @@ async function deleteBucket() {
bucket: new BucketModel({
id: bucketToDelete.value,
projectId: project.value.id,
projectViewId: viewId,
projectViewId: props.viewId,
}),
params: params.value,
})
@@ -635,7 +632,7 @@ async function saveBucketTitle(bucketId: IBucket['id'], bucketTitle: string) {
await kanbanStore.updateBucket({
id: bucketId,
title: bucketTitle,
projectId,
projectId: props.projectId,
})
success({message: i18n.global.t('project.kanban.bucketTitleSavedSuccess')})
bucketTitleEditable.value = false
@@ -657,7 +654,7 @@ function updateBucketPosition(e: { newIndex: number }) {
kanbanStore.updateBucket({
id: bucket.id,
projectId,
projectId: props.projectId,
position: calculateItemPosition(
bucketBefore !== null ? bucketBefore.position : null,
bucketAfter !== null ? bucketAfter.position : null,
@@ -672,7 +669,7 @@ async function saveBucketLimit(bucketId: IBucket['id'], limit: number) {
await kanbanStore.updateBucket({
...kanbanStore.getBucketById(bucketId),
projectId,
projectId: props.projectId,
limit,
})
success({message: t('project.kanban.bucketLimitSavedSuccess')})
@@ -120,10 +120,7 @@ import type {IProjectView} from '@/modelTypes/IProjectView'
import TaskPositionService from '@/services/taskPosition'
import TaskPositionModel from '@/models/taskPosition'
const {
projectId,
viewId,
} = defineProps<{
const props = defineProps<{
projectId: IProject['id'],
viewId: IProjectView['id'],
}>()
@@ -144,7 +141,7 @@ const {
loadTasks,
params,
sortByParam,
} = useTaskList(() => projectId, () => viewId, {position: 'asc'})
} = useTaskList(() => props.projectId, () => props.viewId, {position: 'asc'})
const taskPositionService = ref(new TaskPositionService())
@@ -153,7 +150,7 @@ watch(
allTasks,
() => {
tasks.value = [...allTasks.value]
if (projectId < 0) {
if (props.projectId < 0) {
return
}
const tasksById = {}
@@ -223,6 +220,13 @@ function updateTaskList(task: ITask) {
}
function updateTasks(updatedTask: ITask) {
if (props.projectId < 0) {
// In the case of a filter, we'll reload the filter in the background to avoid tasks which do
// not match the filter show up here
loadTasks(false)
return
}
for (const t in tasks.value) {
if (tasks.value[t].id === updatedTask.id) {
tasks.value[t] = updatedTask
@@ -242,7 +246,7 @@ async function saveTaskPosition(e) {
await taskPositionService.value.update(new TaskPositionModel({
position,
projectViewId: viewId,
projectViewId: props.viewId,
taskId: task.id,
}))
tasks.value[e.newIndex] = {
@@ -311,7 +315,7 @@ function prepareFiltersAndLoadTasks() {
.list-view {
padding-bottom: 1rem;
:deep(.card) {
margin-bottom: 0;
}
@@ -290,10 +290,7 @@ import type {IProject} from '@/modelTypes/IProject'
import AssigneeList from '@/components/tasks/partials/AssigneeList.vue'
import type {IProjectView} from '@/modelTypes/IProjectView'
const {
projectId,
viewId,
} = defineProps<{
const props = defineProps<{
projectId: IProject['id'],
viewId: IProjectView['id'],
}>()
@@ -322,7 +319,7 @@ const SORT_BY_DEFAULT: SortBy = {
const activeColumns = useStorage('tableViewColumns', {...ACTIVE_COLUMNS_DEFAULT})
const sortBy = useStorage<SortBy>('tableViewSortBy', {...SORT_BY_DEFAULT})
const taskList = useTaskList(() => projectId, () => viewId, sortBy.value)
const taskList = useTaskList(() => props.projectId, () => props.viewId, sortBy.value)
const {
loading,
@@ -8,17 +8,19 @@ import {useLabelStore} from '@/stores/labels'
import {useProjectStore} from '@/stores/projects'
import FilterInputDocs from '@/components/project/partials/FilterInputDocs.vue'
const {
modelValue,
loading = false,
showSaveButtons = false,
} = defineProps<{
const props = withDefaults(defineProps<{
modelValue: IProjectView,
loading?: bool,
showSaveButtons?: bool,
}>()
loading?: boolean,
showSaveButtons?: boolean,
}>(), {
loading: false,
showSaveButtons: false,
})
const emit = defineEmits(['update:modelValue', 'cancel'])
const emit = defineEmits<{
'update:modelValue': [value: IProjectView],
'cancel': [],
}>()
const view = ref<IProjectView>()
@@ -26,16 +28,16 @@ const labelStore = useLabelStore()
const projectStore = useProjectStore()
onBeforeMount(() => {
const transform = filterString => transformFilterStringFromApi(
const transform = (filterString: string) => transformFilterStringFromApi(
filterString,
labelId => labelStore.getLabelById(labelId)?.title,
projectId => projectStore.projects[projectId]?.title || null,
)
const transformed = {
...modelValue,
filter: transform(modelValue.filter),
bucketConfiguration: modelValue.bucketConfiguration.map(bc => ({
...props.modelValue,
filter: transform(props.modelValue.filter),
bucketConfiguration: props.modelValue.bucketConfiguration.map(bc => ({
title: bc.title,
filter: transform(bc.filter),
})),
@@ -47,7 +49,7 @@ onBeforeMount(() => {
})
function save() {
const transformFilter = filterQuery => transformFilterStringForApi(
const transformFilter = (filterQuery: string) => transformFilterStringForApi(
filterQuery,
labelTitle => labelStore.filterLabelsByQuery([], labelTitle)[0]?.id || null,
projectTitle => {
@@ -73,7 +75,7 @@ function validateTitle() {
}
function handleBubbleSave() {
if (showSaveButtons) {
if (props.showSaveButtons) {
return
}
+10 -18
View File
@@ -162,7 +162,6 @@ export default {name: 'UserTeamShare'}
<script setup lang="ts">
import {ref, reactive, computed, shallowReactive, type Ref} from 'vue'
import type {PropType} from 'vue'
import {useI18n} from 'vue-i18n'
import UserProjectService from '@/services/userProject'
@@ -192,23 +191,16 @@ import User from '@/components/misc/User.vue'
// FIXME: I think this whole thing can now only manage user/team sharing for projects? Maybe remove a little generalization?
const props = defineProps({
type: {
type: String as PropType<'project'>,
default: '',
},
shareType: {
type: String as PropType<'user' | 'team'>,
default: '',
},
id: {
type: Number,
default: 0,
},
userIsAdmin: {
type: Boolean,
default: false,
},
const props = withDefaults(defineProps<{
type: 'project',
shareType: 'user' | 'team',
id: number,
userIsAdmin?: boolean
}>(), {
type: '',
shareType: '',
id: 0,
userIsAdmin: false,
})
const {t} = useI18n({useScope: 'global'})
@@ -194,15 +194,17 @@ import {useTaskStore} from '@/stores/tasks'
import {useI18n} from 'vue-i18n'
import FilePreview from '@/components/tasks/partials/FilePreview.vue'
const {
task,
editEnabled = true,
} = defineProps<{
const props = withDefaults(defineProps<{
task: ITask,
editEnabled: boolean,
}>()
}>(), {
editEnabled: true,
})
// FIXME: this should go through the store
const emit = defineEmits(['taskChanged'])
const emit = defineEmits<{
'taskChanged': [ITask],
}>()
const taskStore = useTaskStore()
const {t} = useI18n({useScope: 'global'})
@@ -238,7 +240,7 @@ function uploadNewAttachment() {
}
function uploadFilesToTask(files: File[] | FileList) {
uploadFiles(attachmentService, task.id, files)
uploadFiles(attachmentService, props.task.id, files)
}
const attachmentToDelete = ref<IAttachment | null>(null)
@@ -279,11 +281,11 @@ function canPreview(attachment: IAttachment): boolean {
const copy = useCopyToClipboard()
function copyUrl(attachment: IAttachment) {
copy(generateAttachmentUrl(task.id, attachment.id))
copy(generateAttachmentUrl(props.task.id, attachment.id))
}
async function setCoverImage(attachment: IAttachment | null) {
const updatedTask = await taskStore.setCoverImage(task, attachment)
const updatedTask = await taskStore.setCoverImage(props.task, attachment)
emit('taskChanged', updatedTask)
success({message: t('task.attachment.successfullyChangedCoverImage')})
}
@@ -200,15 +200,11 @@ import {useConfigStore} from '@/stores/config'
import {useAuthStore} from '@/stores/auth'
import Reactions from '@/components/input/Reactions.vue'
const props = defineProps({
taskId: {
type: Number,
required: true,
},
canWrite: {
type: Boolean,
default: true,
},
const props = withDefaults(defineProps<{
taskId: number,
canWrite?: boolean
}>(), {
canWrite: true,
})
const {t} = useI18n({useScope: 'global'})
@@ -9,10 +9,9 @@
<script setup lang="ts">
import {formatISO, formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
defineProps({
date: {
type: Date,
default: 0,
},
withDefaults(defineProps<{
date?: Date
}>(), {
date: 0,
})
</script>
@@ -46,13 +46,13 @@ import TaskService from '@/services/task'
import type {ITask} from '@/modelTypes/ITask'
import { getFlatpickrLanguage } from '@/helpers/flatpickrLanguage'
const {
modelValue,
} = defineProps<{
const props = defineProps<{
modelValue: ITask,
}>()
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits<{
'update:modelValue': [value: ITask]
}>()
const {t} = useI18n({useScope: 'global'})
@@ -65,7 +65,7 @@ const lastValue = ref<Date | null>()
const changeInterval = ref<ReturnType<typeof setInterval>>()
watch(
() => modelValue,
() => props.modelValue,
(value) => {
task.value = { ...value }
dueDate.value = value.dueDate
@@ -31,7 +31,7 @@
</template>
<script setup lang="ts">
import {ref, shallowReactive, watch, nextTick, type PropType} from 'vue'
import {ref, shallowReactive, watch, nextTick} from 'vue'
import {useI18n} from 'vue-i18n'
import User from '@/components/misc/User.vue'
@@ -46,25 +46,19 @@ import type {IUser} from '@/modelTypes/IUser'
import {getDisplayName} from '@/models/user'
import AssigneeList from '@/components/tasks/partials/AssigneeList.vue'
const props = defineProps({
taskId: {
type: Number,
required: true,
},
projectId: {
type: Number,
required: true,
},
disabled: {
type: Boolean,
default: false,
},
modelValue: {
type: Array as PropType<IUser[]>,
default: () => [],
},
const props = withDefaults(defineProps<{
modelValue: IUser[] | undefined,
taskId: number,
projectId: number,
disabled?: boolean,
}>(), {
modelValue: () => [],
disabled: false,
})
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits<{
'update:modelValue': [value: IUser[] | undefined],
}>()
const taskStore = useTaskStore()
const {t} = useI18n({useScope: 'global'})
@@ -46,7 +46,7 @@
</template>
<script setup lang="ts">
import {type PropType, ref, computed, shallowReactive, watch} from 'vue'
import {ref, computed, shallowReactive, watch} from 'vue'
import {useI18n} from 'vue-i18n'
import LabelModel from '@/models/label'
@@ -60,27 +60,21 @@ import {useLabelStore} from '@/stores/labels'
import {useTaskStore} from '@/stores/tasks'
import {getRandomColorHex} from '@/helpers/color/randomColor'
const props = defineProps({
modelValue: {
type: Array as PropType<ILabel[]>,
default: () => [],
},
taskId: {
type: Number,
required: false,
default: 0,
},
disabled: {
type: Boolean,
default: false,
},
creatable: {
type: Boolean,
default: true,
},
const props = withDefaults(defineProps<{
modelValue: ILabel[] | undefined
taskId?: number
disabled?: boolean
creatable?: boolean
}>(), {
modelValue: () => [],
taskId: 0,
disabled: false,
creatable: true,
})
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits<{
'update:modelValue': [labels: ILabel[]],
}>()
const {t} = useI18n({useScope: 'global'})
@@ -19,17 +19,14 @@
</template>
<script setup lang="ts">
import {type PropType, ref, shallowReactive, watchEffect} from 'vue'
import {ref, shallowReactive, watchEffect} from 'vue'
import AttachmentService from '@/services/attachment'
import type {IAttachment} from '@/modelTypes/IAttachment'
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
const props = defineProps({
modelValue: {
type: Object as PropType<IAttachment>,
default: undefined,
},
})
const props = defineProps<{
modelValue?: IAttachment
}>()
const attachmentService = shallowReactive(new AttachmentService())
const blobUrl = ref<string | undefined>(undefined)
@@ -49,7 +49,7 @@
</template>
<script setup lang="ts">
import {ref, computed, type PropType} from 'vue'
import {ref, computed} from 'vue'
import {useRouter} from 'vue-router'
import BaseButton from '@/components/base/BaseButton.vue'
@@ -63,18 +63,16 @@ import {useTaskStore} from '@/stores/tasks'
import type {ITask} from '@/modelTypes/ITask'
import {getHexColor, getTaskIdentifier} from '@/models/task'
const props = defineProps({
task: {
type: Object as PropType<ITask>,
required: true,
},
canWrite: {
type: Boolean,
default: false,
},
const props = withDefaults(defineProps<{
task: ITask,
canWrite: boolean | undefined,
}>(), {
canWrite: false,
})
const emit = defineEmits(['update:task'])
const emit = defineEmits<{
'update:task': [task: ITask]
}>()
const router = useRouter()
const copy = useCopyToClipboard()
@@ -16,7 +16,7 @@
v-if="coverImageBlobUrl"
:src="coverImageBlobUrl"
alt=""
class="cover-image"
class="tw-w-full"
>
<div class="p-2">
<span class="task-id">
@@ -111,23 +111,21 @@ import {formatDateLong, formatISO, formatDateSince} from '@/helpers/time/formatD
import {colorIsDark} from '@/helpers/color/colorIsDark'
import {useTaskStore} from '@/stores/tasks'
import AssigneeList from '@/components/tasks/partials/AssigneeList.vue'
import {useAuthStore} from '@/stores/auth'
import {playPopSound} from '@/helpers/playPop'
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
const {
task,
loading = false,
} = defineProps<{
const props = withDefaults(defineProps<{
task: ITask,
loading: boolean,
}>()
}>(), {
loading: false,
})
const router = useRouter()
const loadingInternal = ref(false)
const color = computed(() => getHexColor(task.hexColor))
const color = computed(() => getHexColor(props.task.hexColor))
async function toggleTaskDone(task: ITask) {
loadingInternal.value = true
@@ -137,7 +135,7 @@ async function toggleTaskDone(task: ITask) {
done: !task.done,
})
if (updatedTask.done && useAuthStore().settings.frontendSettings.playSoundWhenDone) {
if (updatedTask.done) {
playPopSound()
}
} finally {
@@ -148,7 +146,7 @@ async function toggleTaskDone(task: ITask) {
function openTaskDetail() {
router.push({
name: 'task.detail',
params: {id: task.id},
params: {id: props.task.id},
state: {backdropView: router.currentRoute.value.fullPath},
})
}
@@ -156,12 +154,12 @@ function openTaskDetail() {
const coverImageBlobUrl = ref<string | null>(null)
async function maybeDownloadCoverImage() {
if (!task.coverImageAttachmentId) {
if (!props.task.coverImageAttachmentId) {
coverImageBlobUrl.value = null
return
}
const attachment = task.attachments.find(a => a.id === task.coverImageAttachmentId)
const attachment = props.task.attachments.find(a => a.id === props.task.coverImageAttachmentId)
if (!attachment || !SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.toLowerCase().endsWith(suffix))) {
return
}
@@ -171,7 +169,7 @@ async function maybeDownloadCoverImage() {
}
watch(
() => task.coverImageAttachmentId,
() => props.task.coverImageAttachmentId,
maybeDownloadCoverImage,
{immediate: true},
)
@@ -4,63 +4,25 @@
v-model.number="percentDone"
:disabled="disabled || undefined"
>
<option value="0">
0%
</option>
<option value="0.1">
10%
</option>
<option value="0.2">
20%
</option>
<option value="0.3">
30%
</option>
<option value="0.4">
40%
</option>
<option value="0.5">
50%
</option>
<option value="0.6">
60%
</option>
<option value="0.7">
70%
</option>
<option value="0.8">
80%
</option>
<option value="0.9">
90%
</option>
<option value="1">
100%
<option
v-for="option in PERCENT_OPTIONS"
:key="option"
:value="option"
>
{{ option * 100 }}%
</option>
</select>
</div>
</template>
<script setup lang="ts">
import {computed} from 'vue'
const props = defineProps({
modelValue: {
default: 0,
type: Number,
},
disabled: {
type: Boolean,
default: false,
},
withDefaults(defineProps<{
disabled?: boolean
}>(), {
disabled: false,
})
const emit = defineEmits(['update:modelValue'])
const percentDone = defineModel<number>({ required: true })
const percentDone = computed({
get: () => props.modelValue,
set(percentDone) {
emit('update:modelValue', percentDone)
},
})
const PERCENT_OPTIONS = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] as const
</script>
@@ -1,14 +1,17 @@
<template>
<span
v-if="!done && (showAll || priority >= priorities.HIGH)"
:class="{'not-so-high': priority === priorities.HIGH, 'high-priority': priority >= priorities.HIGH}"
:class="{
'not-so-high': priority === priorities.HIGH,
'high-priority': priority >= priorities.HIGH
}"
class="priority-label"
>
<span
v-if="priority >= priorities.HIGH"
class="icon"
>
<Icon icon="exclamation" />
<Icon icon="exclamation-circle" />
</span>
<span>
<template v-if="priority === priorities.UNSET">{{ $t('task.priority.unset') }}</template>
@@ -18,47 +21,36 @@
<template v-if="priority === priorities.URGENT">{{ $t('task.priority.urgent') }}</template>
<template v-if="priority === priorities.DO_NOW">{{ $t('task.priority.doNow') }}</template>
</span>
<span
v-if="priority === priorities.DO_NOW"
class="icon pr-0"
>
<Icon icon="exclamation" />
</span>
</span>
</template>
<script setup lang="ts">
import {PRIORITIES as priorities} from '@/constants/priorities'
defineProps({
priority: {
default: 0,
type: Number,
},
showAll: {
type: Boolean,
default: false,
},
done: {
type: Boolean,
default: false,
},
withDefaults(defineProps<{
priority: number,
showAll?: boolean,
done?: boolean
}>(), {
priority: priorities.UNSET,
showAll: false,
done: false,
})
</script>
<style lang="scss" scoped>
span.high-priority {
.high-priority {
color: var(--danger);
width: auto !important; // To override the width set in tasks
}
.icon {
vertical-align: top;
width: auto !important;
padding: 0 .5rem;
}
.not-so-high {
color: var(--warning);
}
&.not-so-high {
color: var(--warning);
}
.icon {
vertical-align: top;
width: auto !important;
padding-right: .5rem;
}
</style>
@@ -3,7 +3,6 @@
<select
v-model="priority"
:disabled="disabled || undefined"
@change="updateData"
>
<option :value="PRIORITIES.UNSET">
{{ $t('task.priority.unset') }}
@@ -28,32 +27,17 @@
</template>
<script setup lang="ts">
import {ref, watch} from 'vue'
import {PRIORITIES} from '@/constants/priorities'
const props = defineProps({
modelValue: {
type: Number,
default: 0,
},
disabled: {
type: Boolean,
default: false,
},
withDefaults(defineProps<{
disabled?: boolean
}>(), {
disabled: false,
})
const emit = defineEmits(['update:modelValue'])
const priority = ref(0)
const priority = defineModel<number>({
required: true,
default: 0,
})
watch(
() => props.modelValue,
(value) => {
priority.value = value
},
{immediate: true},
)
function updateData() {
emit('update:modelValue', priority.value)
}
</script>
@@ -31,19 +31,23 @@ import {useProjectStore} from '@/stores/projects'
import {getProjectTitle} from '@/helpers/getProjectTitle'
import ProjectModel from '@/models/project'
import Multiselect from '@/components/input/Multiselect.vue'
const props = withDefaults(defineProps<{
modelValue: IProject | null
modelValue?: IProject
savedFiltersOnly?: boolean
filter?: (project: IProject) => boolean,
}>(), {
modelValue: () => new ProjectModel(),
savedFiltersOnly: false,
filter: () => true,
})
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits<{
'update:modelValue': [value: IProject]
}>()
const project: IProject = reactive(new ProjectModel())
const project = reactive<IProject>(new ProjectModel())
watch(
() => props.modelValue,
@@ -55,6 +59,7 @@ watch(
)
const projectStore = useProjectStore()
const foundProjects = ref<IProject[]>([])
function findProjects(query: string) {
if (query === '') {
@@ -62,11 +67,13 @@ function findProjects(query: string) {
}
if (props.savedFiltersOnly) {
foundProjects.value = projectStore.searchSavedFilter(query)
const found = projectStore.searchSavedFilter(query)
foundProjects.value = found.filter(props.filter)
return
}
foundProjects.value = projectStore.searchProject(query)
const found = projectStore.searchProject(query)
foundProjects.value = found.filter(props.filter)
}
function select(p: IProject | null) {
@@ -204,7 +204,6 @@ import FancyCheckbox from '@/components/input/FancyCheckbox.vue'
import {error, success} from '@/message'
import {useTaskStore} from '@/stores/tasks'
import {useProjectStore} from '@/stores/projects'
import {useAuthStore} from '@/stores/auth'
import {playPopSound} from '@/helpers/playPop'
const props = defineProps({
@@ -380,7 +379,7 @@ async function createAndRelateTask(title: string) {
async function toggleTaskDone(task: ITask) {
await taskStore.update(task)
if (task.done && useAuthStore().settings.frontendSettings.playSoundWhenDone) {
if (task.done) {
playPopSound()
}
@@ -1,6 +1,6 @@
<template>
<div>
<Popup @close="showFormSwitch = null">
<Popup @update:open="showFormSwitch = null">
<template #trigger="{toggle}">
<SimpleButton
v-tooltip="reminder.reminder && reminder.relativeTo !== null ? formatDateShort(reminder.reminder) : null"
@@ -50,7 +50,7 @@
/>
<DatepickerInline
v-if="activeForm === 'absolute'"
v-else-if="activeForm === 'absolute'"
v-model="reminderDate"
@update:modelValue="setReminderDateAndClose(close)"
/>
@@ -88,38 +88,38 @@ import Card from '@/components/misc/Card.vue'
import SimpleButton from '@/components/input/SimpleButton.vue'
import {useDebounceFn} from '@vueuse/core'
const {
modelValue,
clearAfterUpdate = false,
defaultRelativeTo = REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE,
} = defineProps<{
modelValue?: ITaskReminder,
const props = withDefaults(defineProps<{
modelValue: ITaskReminder | undefined,
clearAfterUpdate?: boolean,
defaultRelativeTo?: null | IReminderPeriodRelativeTo,
}>()
defaultRelativeTo?: IReminderPeriodRelativeTo | null,
}>(), {
modelValue: new TaskReminderModel() as ITaskReminder,
clearAfterUpdate: false,
defaultRelativeTo: REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE,
})
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits<{
'update:modelValue': [value: ITaskReminder | undefined],
}>()
const {t} = useI18n({useScope: 'global'})
const reminder = ref<ITaskReminder>(new TaskReminderModel())
const presets = computed<TaskReminderModel[]>(() => [
{reminder: null, relativePeriod: 0, relativeTo: defaultRelativeTo},
{reminder: null, relativePeriod: -2 * SECONDS_A_HOUR, relativeTo: defaultRelativeTo},
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY, relativeTo: defaultRelativeTo},
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY * 3, relativeTo: defaultRelativeTo},
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY * 7, relativeTo: defaultRelativeTo},
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY * 30, relativeTo: defaultRelativeTo},
])
const presets = computed(() => [
{reminder: null, relativePeriod: 0, relativeTo: props.defaultRelativeTo},
{reminder: null, relativePeriod: -2 * SECONDS_A_HOUR, relativeTo: props.defaultRelativeTo},
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY, relativeTo: props.defaultRelativeTo},
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY * 3, relativeTo: props.defaultRelativeTo},
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY * 7, relativeTo: props.defaultRelativeTo},
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY * 30, relativeTo: props.defaultRelativeTo},
] as ITaskReminder[])
const reminderDate = ref<Date | null>(null)
type availableForms = null | 'relative' | 'absolute'
const showFormSwitch = ref<null | 'relative' | 'absolute'>(null)
const showFormSwitch = ref<availableForms>(null)
const activeForm = computed<availableForms>(() => {
if (defaultRelativeTo === null) {
const activeForm = computed(() => {
if (props.defaultRelativeTo === null) {
return 'absolute'
}
@@ -127,7 +127,6 @@ const activeForm = computed<availableForms>(() => {
})
const reminderText = computed(() => {
if (reminder.value.relativeTo !== null) {
return formatReminder(reminder.value)
}
@@ -140,7 +139,7 @@ const reminderText = computed(() => {
})
watch(
() => modelValue,
() => props.modelValue,
(newReminder) => {
if (newReminder) {
reminder.value = newReminder
@@ -160,12 +159,12 @@ watch(
function updateData() {
emit('update:modelValue', reminder.value)
if (clearAfterUpdate) {
if (props.clearAfterUpdate) {
reminder.value = new TaskReminderModel()
}
}
function setReminderDateAndClose(close) {
function setReminderDateAndClose(close: () => void) {
reminder.value.reminder = reminderDate.value === null
? null
: new Date(reminderDate.value)
@@ -175,7 +174,7 @@ function setReminderDateAndClose(close) {
}
function setReminderFromPreset(preset, close) {
function setReminderFromPreset(preset: ITaskReminder, close: () => void) {
reminder.value = preset
updateData()
close()
@@ -183,14 +182,14 @@ function setReminderFromPreset(preset, close) {
const updateDataAndMaybeClose = useDebounceFn(updateDataAndMaybeCloseNow, 500)
function updateDataAndMaybeCloseNow(close) {
function updateDataAndMaybeCloseNow(close: () => void) {
updateData()
if (clearAfterUpdate) {
if (props.clearAfterUpdate) {
close()
}
}
function formatReminder(reminder: TaskReminderModel) {
function formatReminder(reminder: ITaskReminder) {
const period = secondsToPeriod(reminder.relativePeriod)
if (period.amount === 0) {
@@ -248,6 +247,8 @@ function translateUnit(amount: number, unit: PeriodUnit): string {
return t('time.units.weeks', amount)
case 'years':
return t('time.units.years', amount)
default:
throw new Error(`Unknown unit: ${unit}`)
}
}
</script>
@@ -73,13 +73,13 @@ import TaskReminderModel from '@/models/taskReminder'
import type {ITaskReminder} from '@/modelTypes/ITaskReminder'
import {type IReminderPeriodRelativeTo, REMINDER_PERIOD_RELATIVE_TO_TYPES} from '@/types/IReminderPeriodRelativeTo'
const {
modelValue,
} = defineProps<{
modelValue?: ITaskReminder,
const props = defineProps<{
modelValue: ITaskReminder,
}>()
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits<{
'update:modelValue': [ITaskReminder]
}>()
const reminder = ref<ITaskReminder>(new TaskReminderModel())
@@ -98,14 +98,17 @@ const period = ref<PeriodInput>({
})
watch(
() => modelValue,
() => props.modelValue,
(value) => {
const p = secondsToPeriod(value?.relativePeriod)
period.value.durationUnit = p.unit
period.value.duration = Math.abs(p.amount)
period.value.relativeTo = value?.relativeTo || REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE
},
{immediate: true},
{
immediate: true,
deep: true,
},
)
watch(
@@ -3,7 +3,7 @@
<div
v-for="(r, index) in reminders"
:key="index"
:class="{ 'overdue': r.reminder < new Date() }"
:class="{ 'overdue': r.reminder < now }"
class="reminder-input"
>
<ReminderDetail
@@ -40,21 +40,25 @@ import BaseButton from '@/components/base/BaseButton.vue'
import ReminderDetail from '@/components/tasks/partials/ReminderDetail.vue'
import type {ITask} from '@/modelTypes/ITask'
import {REMINDER_PERIOD_RELATIVE_TO_TYPES} from '@/types/IReminderPeriodRelativeTo'
import { useNow } from '@vueuse/core'
const {
modelValue,
disabled = false,
} = defineProps<{
const props = withDefaults(defineProps<{
modelValue: ITask,
disabled?: boolean,
}>()
}>(), {
disabled: false,
})
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits<{
'update:modelValue': [ITask]
}>()
const reminders = ref<ITaskReminder[]>([])
const now = useNow({interval: 1000})
watch(
() => modelValue.reminders,
() => props.modelValue.reminders,
(newVal) => {
reminders.value = newVal
},
@@ -62,19 +66,19 @@ watch(
)
const defaultRelativeTo = computed(() => {
if (typeof modelValue === 'undefined') {
if (typeof props.modelValue === 'undefined') {
return null
}
if (modelValue?.dueDate) {
if (props.modelValue?.dueDate) {
return REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE
}
if (modelValue.dueDate === null && modelValue.startDate !== null) {
if (props.modelValue.dueDate === null && props.modelValue.startDate !== null) {
return REMINDER_PERIOD_RELATIVE_TO_TYPES.STARTDATE
}
if (modelValue.dueDate === null && modelValue.startDate === null && modelValue.endDate !== null) {
if (props.modelValue.dueDate === null && props.modelValue.startDate === null && props.modelValue.endDate !== null) {
return REMINDER_PERIOD_RELATIVE_TO_TYPES.ENDDATE
}
@@ -83,7 +87,7 @@ const defaultRelativeTo = computed(() => {
function updateData() {
emit('update:modelValue', {
...modelValue,
...props.modelValue,
reminders: reminders.value,
})
}
@@ -94,7 +94,7 @@
</template>
<script setup lang="ts">
import {ref, reactive, watch, type PropType} from 'vue'
import {ref, reactive, watch} from 'vue'
import {useI18n} from 'vue-i18n'
import {error} from '@/message'
@@ -104,19 +104,17 @@ import type {IRepeatAfter} from '@/types/IRepeatAfter'
import type {ITask} from '@/modelTypes/ITask'
import TaskModel from '@/models/task'
const props = defineProps({
modelValue: {
type: Object as PropType<ITask>,
default: () => ({}),
required: false,
},
disabled: {
type: Boolean,
default: false,
},
const props = withDefaults(defineProps<{
modelValue: ITask | undefined,
disabled: boolean
}>(), {
modelValue: () => {},
disabled: false,
})
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits<{
'update:modelValue': [value: ITask | undefined],
}>()
const {t} = useI18n({useScope: 'global'})
@@ -134,7 +132,10 @@ watch(
Object.assign(repeatAfter, value.repeatAfter)
}
},
{immediate: true},
{
immediate: true,
deep: true,
},
)
function updateData() {
@@ -205,34 +205,34 @@ import {useTaskStore} from '@/stores/tasks'
import AssigneeList from '@/components/tasks/partials/AssigneeList.vue'
import {useIntervalFn} from '@vueuse/core'
import {playPopSound} from '@/helpers/playPop'
import {useAuthStore} from '@/stores/auth'
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
import {TASK_REPEAT_MODES} from '@/types/IRepeatMode'
const {
theTask,
isArchived = false,
showProject = false,
disabled = false,
canMarkAsDone = true,
allTasks = [],
} = defineProps<{
const props = withDefaults(defineProps<{
theTask: ITask,
isArchived?: boolean,
showProject?: boolean,
disabled?: boolean,
canMarkAsDone?: boolean,
allTasks?: ITask[],
}>(), {
isArchived: false,
showProject: false,
disabled: false,
canMarkAsDone: true,
allTasks: () => [],
})
const emit = defineEmits<{
'taskUpdated': [task: ITask],
}>()
const emit = defineEmits(['taskUpdated'])
function getTaskById(taskId: number): ITask | undefined {
if (typeof allTasks === 'undefined' || allTasks.length === 0) {
if (typeof props.allTasks === 'undefined' || props.allTasks.length === 0) {
return null
}
return allTasks.find(t => t.id === taskId)
return props.allTasks.find(t => t.id === taskId)
}
const {t} = useI18n({useScope: 'global'})
@@ -244,14 +244,17 @@ const showDefer = ref(false)
const isRepeating = computed(() => task.value.repeatAfter.amount > 0 || (task.value.repeatAfter.amount === 0 && task.value.repeatMode === TASK_REPEAT_MODES.REPEAT_MODE_MONTH))
watch(
() => theTask,
() => props.theTask,
newVal => {
task.value = newVal
},
{
immediate: true,
deep: true,
},
)
onMounted(() => {
task.value = theTask
document.addEventListener('click', hideDeferDueDatePopup)
})
@@ -266,7 +269,7 @@ const taskStore = useTaskStore()
const project = computed(() => projectStore.projects[task.value.projectId])
const projectColor = computed(() => project.value ? project.value?.hexColor : '')
const showProjectSeparately = computed(() => !showProject && currentProject.value?.id !== task.value.projectId && project.value)
const showProjectSeparately = computed(() => !props.showProject && currentProject.value?.id !== task.value.projectId && project.value)
const currentProject = computed(() => {
return typeof baseStore.currentProject === 'undefined' ? {
@@ -311,7 +314,7 @@ async function markAsDone(checked: boolean, wasReverted: boolean = false) {
return
}
if (checked && useAuthStore().settings.frontendSettings.playSoundWhenDone) {
if (checked) {
playPopSound()
}
emit('taskUpdated', newTask)
+4 -2
View File
@@ -105,8 +105,10 @@ export function useTaskList(
const totalPages = computed(() => taskCollectionService.totalPages)
const tasks = ref<ITask[]>([])
async function loadTasks() {
tasks.value = []
async function loadTasks(resetBeforeLoad: boolean = true) {
if(resetBeforeLoad) {
tasks.value = []
}
try {
tasks.value = await taskCollectionService.getAll(...getAllTasksParams.value)
} catch (e) {
@@ -1,4 +1,7 @@
export const calculateItemPosition = (positionBefore: number | null, positionAfter: number | null): number => {
export const calculateItemPosition = (
positionBefore: number | null = null,
positionAfter: number | null = null,
): number => {
if (positionBefore === null) {
if (positionAfter === null) {
return 0
@@ -13,6 +16,6 @@ export const calculateItemPosition = (positionBefore: number | null, positionAft
return positionBefore + Math.pow(2, 16)
}
// If we have both a task before and after it, we acually calculate the position
// If we have both a task before and after it, we actually calculate the position
return positionBefore + (positionAfter - positionBefore) / 2
}
+10
View File
@@ -137,6 +137,16 @@ describe('Filter Transformation', () => {
expect(transformed).toBe('labels in 2 || labels in l')
})
it('should correctly transform the cases of fields', () => {
const transformed = transformFilterStringForApi(
'startdate > now',
nullTitleToIdResolver,
nullTitleToIdResolver,
)
expect(transformed).toBe('start_date > now')
})
})
describe('To API', () => {
+7 -1
View File
@@ -66,10 +66,16 @@ export function transformFilterStringForApi(
labelResolver: (title: string) => number | null,
projectResolver: (title: string) => number | null,
): string {
filter = filter.trim()
if (filter.trim() === '') {
if (filter === '') {
return ''
}
AVAILABLE_FILTER_FIELDS.forEach(f => {
filter = filter.replace(new RegExp(f, 'ig'), f)
})
// Transform labels to ids
LABEL_FIELDS.forEach(field => {
+7 -2
View File
@@ -1,8 +1,13 @@
import {useAuthStore} from '@/stores/auth'
import popSoundFile from '@/assets/audio/pop.mp3'
export const playSoundWhenDoneKey = 'playSoundWhenTaskDone'
export function playPopSound() {
const playSoundWhenDone = useAuthStore().settings.frontendSettings.playSoundWhenDone
if (!playSoundWhenDone)
return
try {
const popSound = new Audio(popSoundFile)
popSound.play()
+1 -1
View File
@@ -24,7 +24,7 @@ export const SUPPORTED_LOCALES = {
'sl-SI': 'Slovenščina',
'pt-BR': 'Português Brasileiro',
'hr-HR': 'Hrvatski',
'uk-UA': 'українська',
'uk-UA': 'Українська',
// IMPORTANT: Also add new languages to useDayjsLanguageSync
} as const
+22 -20
View File
@@ -43,7 +43,7 @@
"forgotPassword": "نسيت كلمة المرور؟",
"resetPassword": "إعادة تعيين كلمة المرور",
"resetPasswordAction": "أرسل لي رابط إعادة تعيين كلمة المرور",
"resetPasswordSuccess": "تحقق من صندوق الوارد الخاص بك! يجب أن تصلك رسالة مع إرشادات حول كيفية إعادة تعيين كلمة المرور الخاصة بك.",
"resetPasswordSuccess": "Check your inbox! You should have an email with instructions on how to reset your password.",
"passwordsDontMatch": "كلمتي المرور غير متطابقتين",
"confirmEmailSuccess": "تم تأكيد بريدك الإلكتروني بنجاح! يمكنك تسجيل الدخول الآن.",
"totpTitle": "رمز المصادقة الثنائية 2FA",
@@ -72,21 +72,21 @@
"settings": {
"title": "الإعدادات",
"newPasswordTitle": "تحديث كلمة مرورك",
"newPassword": "كلمة مرور جديدة",
"newPasswordConfirm": "تأكيد كلمة المرور الجديدة",
"currentPassword": "كلمة المرور الحالية",
"newPassword": "New password",
"newPasswordConfirm": "New password confirmation",
"currentPassword": "Current password",
"currentPasswordPlaceholder": "كلمة المرور الحالية الخاصة بك",
"passwordsDontMatch": "كلمة المرور الجديدة وتأكيدها غير متطابقة.",
"passwordUpdateSuccess": "تم تحديث كلمة المرور بنجاح.",
"updateEmailTitle": "تحديث عنوان بريدك الإلكتروني",
"updateEmailNew": "عنوان البريد الإلكتروني الجديد",
"updateEmailNew": "New email address",
"updateEmailSuccess": "تم تحديث عنوان بريدك الإلكتروني بنجاح. لقد أرسلنا لك رابط لتأكيده.",
"general": {
"title": "الإعدادات العامة",
"name": "الاسم",
"newName": "الاسم الجديد",
"name": "My Name",
"newName": "The new name",
"savedSuccess": "تم تحديث الإعدادات بنجاح.",
"emailReminders": "أرسل لي تذكيرات للمهام عبر البريد الإلكتروني",
"emailReminders": "Send me reminders for tasks via email",
"overdueReminders": "أرسل لي ملخصاً للمهام المتأخرة ولم تنجز كل يوم",
"discoverableByName": "السماح للمستخدمين الآخرين بإضافتي كعضو إلى الفرق أو المشاريع عند بحثهم عن اسمي",
"discoverableByEmail": "السماح للمستخدمين الآخرين بإضافتي كعضو إلى الفرق أو المشاريع عند بحثهم عن بريدي الإلكتروني الكامل",
@@ -95,24 +95,24 @@
"weekStartSunday": "الأحد",
"weekStartMonday": "الإثنين",
"language": "اللّغة",
"defaultProject": "المشروع الافتراضي",
"defaultView": "Default View",
"timezone": "منطقة التوقيت",
"defaultProject": "Default project",
"defaultView": "Default view",
"timezone": "Time zone",
"overdueTasksRemindersTime": "وقت تذكير للمهام المتأخرة بالبريد الإلكتروني",
"filterUsedOnOverview": "الفلتر المحفوظ والمستخدم في صفحة العرض العام"
},
"totp": {
"title": "رمز المصادقة الثنائية 2FA",
"enroll": "تسجيل",
"finishSetupPart1": "لإنهاء التثبيت، استخدم هذا الرمز السرّي في تطبيق Totp الخاص بك (Google Authenticator أو ما شابه):",
"finishSetupPart1": "To finish your setup, use this secret in your TOTP app (Google Authenticator or similar):",
"finishSetupPart2": "بعد ذلك، أدخل الرمز من التطبيق الخاص بك أدناه.",
"scanQR": "أو يمكنك مسح رمز الاستجابة السريعة QR هذا:",
"passcode": "الرمز السّري",
"passcodePlaceholder": "الرمز السّري تم إنشاؤه بواسطة تطبيق Totp الخاص بك",
"passcodePlaceholder": "A code generated by your TOTP application",
"setupSuccess": "لقد قمت بنجاح بإعداد المصادقة الثنائية 2FA!",
"enterPassword": "الرجاء إدخال كلمة المرور",
"disable": "تعطيل المصادقة الثنائية",
"confirmSuccess": "لقد قمت بتأكيد إعداد Totp الخاص بك بنجاح ويمكنك استخدامه من الآن فصاعداً!",
"confirmSuccess": "You've successfully confirmed your TOTP setup and can use it from now on!",
"disableSuccess": "تم تعطيل المصادقة الثنائية بنجاح."
},
"caldav": {
@@ -163,6 +163,7 @@
"90d": "90 يوماً",
"permissionExplanation": "نطاق الصلاحيات المسموح به هو ما يسمح لك القيام به رمز Api.",
"titleRequired": "العنوان إجباري",
"permissionRequired": "Please select at least one permission from the list.",
"expired": "انتهت صلاحية هذا الرمز {ago}.",
"tokenCreatedSuccess": "إليك رمز Api الجديد: {token}",
"tokenCreatedNotSeeAgain": "قم بتخزينه في مكان آمن، لن تتمكن من عرضه مرة أخرى!",
@@ -847,7 +848,8 @@
"delete": "حذف هذا التعليق",
"deleteText1": "هل أنت متأكد من رغبتك بحذف هذا التعليق؟",
"deleteSuccess": "تم حذف التعليق بنجاح.",
"addedSuccess": "تمت إضافة التعليق بنجاح."
"addedSuccess": "تمت إضافة التعليق بنجاح.",
"permalink": "Copy permalink to this comment"
},
"deferDueDate": {
"title": "تأجيل تاريخ الاستحقاق",
@@ -1141,9 +1143,9 @@
"1012": "لم يتم تأكيد عنوان البريد الإلكتروني للمستخدم.",
"1013": "كلمة المرور الجديدة فارغة.",
"1014": "كلمة المرور القديمة فارغة.",
"1015": "تم تفعيل Totp مسبقاً لهذا المستخدم.",
"1016": "لم يتم تفعيل Totp لهذا المستخدم.",
"1017": "رمز المرور الخاص بـ Totp غير صالح.",
"1015": "TOTP is already enabled for this user.",
"1016": "TOTP is not enabled for this user.",
"1017": "The TOTP passcode is invalid.",
"1018": "إعدادات نوع الصورة الرمزية للمستخدم غير صالحة.",
"2001": "لا يمكن أن يكون الرقم التعريفي ID فارغاً أو 0.",
"2002": "بعض بيانات الطلب غير صالحة.",
@@ -1153,8 +1155,8 @@
"3006": "المشاركة في المشروع غير موجودة.",
"3007": "يوجد مشروع بهذا المعرف مسبقاً.",
"3008": "المشروع مؤرشف وبالتالي يمكن الوصول إليه للقراءة فقط، وهذا ينطبق أيضا على جميع المهام المرتبطة بهذا المشروع.",
"4001": "لا يمكن ترك نص مهمة المشروع فارغاً.",
"4002": "مهمة المشروع غير موجودة.",
"4001": "The task title cannot be empty.",
"4002": "The task does not exist.",
"4003": "يجب أن تنتمي جميع إجراءات التعديل المجمعة لنفس المشروع.",
"4004": "تحتاج إلى مهمة واحدة على الأقل عند القيام بإجراءات التعديل بالجملة.",
"4005": "ليس لديك الصلاحية لعرض المهمة.",
+22 -20
View File
@@ -43,7 +43,7 @@
"forgotPassword": "Forgot your password?",
"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.",
"resetPasswordSuccess": "Check your inbox! You should have an email 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",
@@ -72,21 +72,21 @@
"settings": {
"title": "Settings",
"newPasswordTitle": "Update Your Password",
"newPassword": "New Password",
"newPasswordConfirm": "New Password Confirmation",
"currentPassword": "Current 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 Email Address",
"updateEmailNew": "New Email 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",
"name": "My Name",
"newName": "The new name",
"savedSuccess": "The settings were successfully updated.",
"emailReminders": "Send me reminders for tasks via Email",
"emailReminders": "Send me reminders for tasks via email",
"overdueReminders": "Send me a summary of my undone overdue tasks every day",
"discoverableByName": "Allow other users to add me as a member to teams or projects when they search for my name",
"discoverableByEmail": "Allow other users to add me as a member to teams or projects when they search for my full email",
@@ -95,24 +95,24 @@
"weekStartSunday": "Sunday",
"weekStartMonday": "Monday",
"language": "Language",
"defaultProject": "Default Project",
"defaultView": "Default View",
"timezone": "Time Zone",
"defaultProject": "Default project",
"defaultView": "Default view",
"timezone": "Time zone",
"overdueTasksRemindersTime": "Overdue tasks reminder email time",
"filterUsedOnOverview": "Saved filter used on the overview page"
},
"totp": {
"title": "Two Factor Authentication",
"enroll": "Enroll",
"finishSetupPart1": "To finish your setup, use this secret in your totp app (Google Authenticator or similar):",
"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",
"passcodePlaceholder": "A code generated by your TOTP application",
"setupSuccess": "You've successfully 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!",
"confirmSuccess": "You've successfully confirmed your TOTP setup and can use it from now on!",
"disableSuccess": "Two factor authentication was successfully disabled."
},
"caldav": {
@@ -163,6 +163,7 @@
"90d": "90 Days",
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
"titleRequired": "The title is required",
"permissionRequired": "Please select at least one permission from the list.",
"expired": "This token has expired {ago}.",
"tokenCreatedSuccess": "Here is your new api token: {token}",
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
@@ -847,7 +848,8 @@
"delete": "Delete this comment",
"deleteText1": "Are you sure you want to delete this comment?",
"deleteSuccess": "The comment was deleted successfully.",
"addedSuccess": "The comment was added successfully."
"addedSuccess": "The comment was added successfully.",
"permalink": "Copy permalink to this comment"
},
"deferDueDate": {
"title": "Defer due date",
@@ -1141,9 +1143,9 @@
"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.",
"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.",
@@ -1153,8 +1155,8 @@
"3006": "The project share does not exist.",
"3007": "A project with this identifier already exists.",
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
"4001": "The project task text cannot be empty.",
"4002": "The project task does not exist.",
"4001": "The task title cannot be empty.",
"4002": "The task does not exist.",
"4003": "All bulk editing tasks must belong to the same project.",
"4004": "Need at least one task when bulk editing tasks.",
"4005": "You do not have the right to see the task.",
+22 -20
View File
@@ -43,7 +43,7 @@
"forgotPassword": "Zapomenuté heslo?",
"resetPassword": "Obnovit heslo",
"resetPasswordAction": "Poslat odkaz na obnovení hesla",
"resetPasswordSuccess": "Zkontrolujte doručenou poštu! Měli byste mít e-mail s pokyny, jak obnovit své heslo.",
"resetPasswordSuccess": "Check your inbox! You should have an email with instructions on how to reset your password.",
"passwordsDontMatch": "Hesla se neshodují",
"confirmEmailSuccess": "Úspěšně jste potvrdili svůj e-mail! Nyní se můžete přihlásit.",
"totpTitle": "Kód dvoufaktorového ověření",
@@ -72,21 +72,21 @@
"settings": {
"title": "Nastavení",
"newPasswordTitle": "Aktualizujte své heslo",
"newPassword": "Nové heslo",
"newPasswordConfirm": "Potvrzení nového hesla",
"currentPassword": "Stávající heslo",
"newPassword": "New password",
"newPasswordConfirm": "New password confirmation",
"currentPassword": "Current password",
"currentPasswordPlaceholder": "Vaše současné heslo",
"passwordsDontMatch": "Nové heslo se neshoduje s potvrzením hesla.",
"passwordUpdateSuccess": "Heslo bylo úspěšně změněno.",
"updateEmailTitle": "Aktualizujte svou e-mailovou adresu",
"updateEmailNew": "Nová e-mailová adresa",
"updateEmailNew": "New email address",
"updateEmailSuccess": "Vaše e-mailová adresa byla úspěšně aktualizována. Poslali jsme vám odkaz pro její potvrzení.",
"general": {
"title": "Obecná nastavení",
"name": "Jméno",
"newName": "Nové jméno",
"name": "My Name",
"newName": "The new name",
"savedSuccess": "Nastavení bylo úspěšně aktualizováno.",
"emailReminders": "Posílat mi připomenutí pro úkoly e-mailem",
"emailReminders": "Send me reminders for tasks via email",
"overdueReminders": "Pošlete mi každý den shrnutí mých zpožděných úkolů",
"discoverableByName": "Umožnit ostatním uživatelům přidat mě jako člena do týmů nebo projektů, když hledají mé jméno",
"discoverableByEmail": "Umožnit ostatním uživatelům, aby mě přidali jako člena do týmů nebo projektů, když hledají můj úplný e-mail",
@@ -95,24 +95,24 @@
"weekStartSunday": "Neděle",
"weekStartMonday": "Pondělí",
"language": "Jazyk",
"defaultProject": "Výchozí projekt",
"defaultView": "Default View",
"timezone": "Časové pásmo",
"defaultProject": "Default project",
"defaultView": "Default view",
"timezone": "Time zone",
"overdueTasksRemindersTime": "Čas odeslání emailu o zpožděných úkolech",
"filterUsedOnOverview": "Uložený filtr použitý na stránce přehledu"
},
"totp": {
"title": "Dvoufaktorové ověření",
"enroll": "Zápis",
"finishSetupPart1": "Chcete-li dokončit nastavení, použijte tento tajný klíč ve vaší aplikaci (Google Authenticator nebo podobné):",
"finishSetupPart1": "To finish your setup, use this secret in your TOTP app (Google Authenticator or similar):",
"finishSetupPart2": "Poté zadejte kód z vaší aplikace.",
"scanQR": "Případně můžete naskenovat tento QR kód:",
"passcode": "Přístupový kód",
"passcodePlaceholder": "Kód vygenerovaný vaší aplikací",
"passcodePlaceholder": "A code generated by your TOTP application",
"setupSuccess": "Úspěšně jste nastavili dvoufaktorové ověření!",
"enterPassword": "Zadejte prosím heslo",
"disable": "Zakázat dvoufaktorové ověření",
"confirmSuccess": "Úspěšně jste potvrdili nastavení a od nynějška jej můžete používat!",
"confirmSuccess": "You've successfully confirmed your TOTP setup and can use it from now on!",
"disableSuccess": "Dvoufázové ověření bylo úspěšně vypnuto."
},
"caldav": {
@@ -163,6 +163,7 @@
"90d": "90 dní",
"permissionExplanation": "Oprávnění vám umožní nastavit, k čemu lze api token použít.",
"titleRequired": "Je vyžadován název",
"permissionRequired": "Please select at least one permission from the list.",
"expired": "Platnost tohoto tokenu vypršela {ago}.",
"tokenCreatedSuccess": "Zde je tvůj nový api token: {token}",
"tokenCreatedNotSeeAgain": "Ulož jej na zabezpečeném místě, už ho znovu neuvidíš!",
@@ -847,7 +848,8 @@
"delete": "Smazat tento komentář",
"deleteText1": "Opravdu chcete smazat tento komentář?",
"deleteSuccess": "Komentář byl úspěšně odstraněn.",
"addedSuccess": "Komentář byl úspěšně přidán."
"addedSuccess": "Komentář byl úspěšně přidán.",
"permalink": "Copy permalink to this comment"
},
"deferDueDate": {
"title": "Odložit datum dokončení",
@@ -1141,9 +1143,9 @@
"1012": "E-mailová adresa uživatele nebyla potvrzena.",
"1013": "Nové heslo je prázdné.",
"1014": "Staré heslo je prázdné.",
"1015": "Totp je již pro tohoto uživatele povolen.",
"1016": "Totp není pro tohoto uživatele povolen.",
"1017": "Kód totp je neplatný.",
"1015": "TOTP is already enabled for this user.",
"1016": "TOTP is not enabled for this user.",
"1017": "The TOTP passcode is invalid.",
"1018": "Nastavení typu avatara uživatele je neplatné.",
"2001": "ID nemůže být prázdné nebo 0.",
"2002": "Některé údaje požadavku byly neplatné.",
@@ -1153,8 +1155,8 @@
"3006": "Sdílení projektu neexistuje.",
"3007": "Projekt s tímto identifikátorem již existuje.",
"3008": "Projekt je archivován, a proto je přístupný pouze pro čtení. To platí i pro všechny úkoly spojené s tímto projektem.",
"4001": "Text úkolu projektu nemůže být prázdný.",
"4002": "Úkol projektu neexistuje.",
"4001": "The task title cannot be empty.",
"4002": "The task does not exist.",
"4003": "Všechny úkoly pro hromadnou úpravu musí patřit do stejného projektu.",
"4004": "Při hromadných úpravách úkolů je potřeba alespoň jeden úkol.",
"4005": "Nemáte právo vidět tento úkol.",
+22 -20
View File
@@ -43,7 +43,7 @@
"forgotPassword": "Har du glemt din adgangskode?",
"resetPassword": "Nulstil din adgangskode",
"resetPasswordAction": "Send mig et link til nulstilling af adgangskode",
"resetPasswordSuccess": "Tjek din indbakke! Du bør have en e-mail med instruktioner om, hvordan du nulstiller din adgangskode.",
"resetPasswordSuccess": "Check your inbox! You should have an email with instructions on how to reset your password.",
"passwordsDontMatch": "Adgangskoderne er ikke ens",
"confirmEmailSuccess": "Du har bekræftet din e-mail! Du kan logge ind nu.",
"totpTitle": "To Faktor Godkendelseskode",
@@ -72,21 +72,21 @@
"settings": {
"title": "Indstillinger",
"newPasswordTitle": "Opdater din adgangskode",
"newPassword": "Ny adgangskode",
"newPasswordConfirm": "Bekræftelse Af Ny Adgangskode",
"currentPassword": "Nuværende Adgangskode",
"newPassword": "New password",
"newPasswordConfirm": "New password confirmation",
"currentPassword": "Current password",
"currentPasswordPlaceholder": "Din nuværende adgangskode",
"passwordsDontMatch": "Din nye adgangskode og bekræftelsesadgangskoden er ikke ens.",
"passwordUpdateSuccess": "Adgangskoden blev ændret.",
"updateEmailTitle": "Opdater din e-mailadresse",
"updateEmailNew": "Ny e-mailadresse",
"updateEmailNew": "New email address",
"updateEmailSuccess": "Din e-mailadresse blev opdateret. Vi har sendt dig et link til at bekræfte den.",
"general": {
"title": "Generelle Indstillinger",
"name": "Navn",
"newName": "Det nye navn",
"name": "My Name",
"newName": "The new name",
"savedSuccess": "Indstillingerne er gemt.",
"emailReminders": "Send mig påmindelser for opgaver via e-mail",
"emailReminders": "Send me reminders for tasks via email",
"overdueReminders": "Send mig en oversigt over mine ufærdige opgaver hver dag",
"discoverableByName": "Allow other users to add me as a member to teams or projects when they search for my name",
"discoverableByEmail": "Allow other users to add me as a member to teams or projects when they search for my full email",
@@ -95,24 +95,24 @@
"weekStartSunday": "Søndag",
"weekStartMonday": "Mandag",
"language": "Sprog",
"defaultProject": "Default Project",
"defaultView": "Default View",
"timezone": "Tidszone",
"defaultProject": "Default project",
"defaultView": "Default view",
"timezone": "Time zone",
"overdueTasksRemindersTime": "Forfaldne opgaver påmindelse e-mail tidspunkt",
"filterUsedOnOverview": "Saved filter used on the overview page"
},
"totp": {
"title": "To-faktor godkendelse",
"enroll": "Tilmeld",
"finishSetupPart1": "For at afslutte din opsætning, brug denne nøgle i din totp app (Google Authenticator eller tilsvarende):",
"finishSetupPart1": "To finish your setup, use this secret in your TOTP app (Google Authenticator or similar):",
"finishSetupPart2": "Herefter skal du indtaste en kode fra din app nedenfor.",
"scanQR": "Alternativt kan du scanne denne QR-kode:",
"passcode": "Kode",
"passcodePlaceholder": "En kode genereret af din totp applikation",
"passcodePlaceholder": "A code generated by your TOTP application",
"setupSuccess": "Du har aktiveret to-faktor autentificering!",
"enterPassword": "Indtast venligst din adgangskode",
"disable": "Deaktiver to-faktor autentificering",
"confirmSuccess": "Du har bekræftet din totp opsætning og kan bruge den fra nu af!",
"confirmSuccess": "You've successfully confirmed your TOTP setup and can use it from now on!",
"disableSuccess": "To faktor godkendelse blev deaktiveret."
},
"caldav": {
@@ -163,6 +163,7 @@
"90d": "90 Days",
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
"titleRequired": "The title is required",
"permissionRequired": "Please select at least one permission from the list.",
"expired": "This token has expired {ago}.",
"tokenCreatedSuccess": "Here is your new api token: {token}",
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
@@ -847,7 +848,8 @@
"delete": "Slet denne kommentar",
"deleteText1": "Er du sikker på du vil slette denne kommentar?",
"deleteSuccess": "Kommentaren blev slettet.",
"addedSuccess": "Din kommentar blev tilføjet."
"addedSuccess": "Din kommentar blev tilføjet.",
"permalink": "Copy permalink to this comment"
},
"deferDueDate": {
"title": "Udsæt forfaldsdato",
@@ -1141,9 +1143,9 @@
"1012": "E-mail adresse for brugeren ikke bekræftet.",
"1013": "Ny adgangskode er tom.",
"1014": "Gammel adgangskode er tom.",
"1015": "Totp er allerede aktiveret for denne bruger.",
"1016": "Totp er ikke aktiveret for denne bruger.",
"1017": "Totp koden er ugyldig.",
"1015": "TOTP is already enabled for this user.",
"1016": "TOTP is not enabled for this user.",
"1017": "The TOTP passcode is invalid.",
"1018": "Brugerens avatartype indstilling er ugyldig.",
"2001": "ID kan ikke være tomt eller 0.",
"2002": "Nogle af forespørgselsdataene var ugyldige.",
@@ -1153,8 +1155,8 @@
"3006": "The project share does not exist.",
"3007": "A project with this identifier already exists.",
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
"4001": "The project task text cannot be empty.",
"4002": "The project task does not exist.",
"4001": "The task title cannot be empty.",
"4002": "The task does not exist.",
"4003": "All bulk editing tasks must belong to the same project.",
"4004": "Der skal være mindst én opgave for at masseredigere opgaver.",
"4005": "Du har ikke rettigheder til at se opgaven.",
+25 -23
View File
@@ -43,7 +43,7 @@
"forgotPassword": "Passwort vergessen?",
"resetPassword": "Setze dein Passwort zurück",
"resetPasswordAction": "Sende mir einen Link zum Zurücksetzen des Passworts",
"resetPasswordSuccess": "Prüfe deinen Posteingang! Du solltest eine E-Mail mit Anweisungen zum Zurücksetzen deines Passworts erhalten haben.",
"resetPasswordSuccess": "Überprüfe deinen Posteingang! Du solltest eine E-Mail mit Anweisungen zum Zurücksetzen deines Passwortes haben.",
"passwordsDontMatch": "Passwörter stimmen nicht überein",
"confirmEmailSuccess": "Du hast deine E-Mail-Adresse erfolgreich bestätigt! Du kannst dich jetzt einloggen.",
"totpTitle": "Zwei-Faktor-Authentifizierungscode",
@@ -83,7 +83,7 @@
"updateEmailSuccess": "Deine E-Mail-Adresse wurde erfolgreich aktualisiert. Wir haben dir einen Link zur Bestätigung geschickt.",
"general": {
"title": "Allgemeine Einstellungen",
"name": "Name",
"name": "Mein Name",
"newName": "Neuer Name",
"savedSuccess": "Die Einstellungen wurden erfolgreich aktualisiert.",
"emailReminders": "Erinnerungen an Aufgaben per E-Mail senden",
@@ -95,7 +95,7 @@
"weekStartSunday": "Sonntag",
"weekStartMonday": "Montag",
"language": "Sprache",
"defaultProject": "Standard-Projekt",
"defaultProject": "Standardprojekt",
"defaultView": "Standardansicht",
"timezone": "Zeitzone",
"overdueTasksRemindersTime": "Zeit der E-Mail-Zusammenfassung der überfälligen Aufgaben",
@@ -104,15 +104,15 @@
"totp": {
"title": "Zwei-Faktor-Authentifizierung",
"enroll": "Aktivieren",
"finishSetupPart1": "Um die Aktivierung zu vollenden, benutze diesen Token in deiner TOTP App (Google Authenticator oder ähnlich):",
"finishSetupPart1": "Um die Aktivierung abzuschließen, benutze diesen Token in deiner TOTP App (Google Authenticator oder ähnliche):",
"finishSetupPart2": "Gib anschließend unten einen Code aus deiner Anwendung ein.",
"scanQR": "Alternativ kannst du auch diesen QR-Code scannen:",
"passcode": "Code",
"passcodePlaceholder": "Ein von deiner TOTP-App generierter code",
"passcodePlaceholder": "Ein von deiner TOTP-App generierter Code",
"setupSuccess": "Du hast die Zwei-Faktor-Authentifizierung erfolgreich eingerichtet!",
"enterPassword": "Bitte gib dein Passwort ein",
"disable": "Zwei-Faktor-Authentifizierung deaktivieren",
"confirmSuccess": "TOTP wurde verifiziert und ist nun verwendbar.",
"confirmSuccess": "TOTP wurde verifiziert und ist nun verwendbar!",
"disableSuccess": "Die Zwei-Faktor-Authentifizierung wurde erfolgreich deaktiviert."
},
"caldav": {
@@ -163,6 +163,7 @@
"90d": "90 Tage",
"permissionExplanation": "Mit Berechtigungen kannst du einschränken, was ein API-Token tun darf.",
"titleRequired": "Titel ist erforderlich",
"permissionRequired": "Bitte wähle mindestens eine Berechtigung aus der Liste.",
"expired": "Dieses Token ist {ago} abgelaufen.",
"tokenCreatedSuccess": "Hier ist dein neues API Token: {token}",
"tokenCreatedNotSeeAgain": "Speichere es an einem sicheren Ort, du wirst es nicht mehr sehen!",
@@ -387,19 +388,19 @@
"secretDocs": "In der Dokumentation findest du weitere Informationen zum Umgang mit Schlüsseln."
},
"views": {
"header": "Edit views",
"title": "Title",
"actions": "Actions",
"kind": "Kind",
"bucketConfigMode": "Bucket configuration mode",
"bucketConfig": "Bucket configuration",
"bucketConfigManual": "Manual",
"header": "Ansichten bearbeiten",
"title": "Titel",
"actions": "Aktionen",
"kind": "Ansicht",
"bucketConfigMode": "Bucket-Konfigurationsmodus",
"bucketConfig": "Bucket-Konfiguration",
"bucketConfigManual": "Manuell",
"filter": "Filter",
"create": "Create view",
"createSuccess": "The view was created successfully.",
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"create": "Ansicht erstellen",
"createSuccess": "Die Ansicht wurde erfolgreich erstellt.",
"titleRequired": "Bitte gib einen Titel ein.",
"delete": "Diese Ansicht löschen",
"deleteText": "Bist du sicher, dass du diese Ansicht entfernen möchtest? Es wird nicht mehr möglich sein, sie zu verwenden, um Aufgaben in diesem Projekt anzuzeigen. Diese Aktion löscht keine Aufgaben. Dies kann nicht rückgängig gemacht werden!",
"deleteSuccess": "Diese Ansicht wurde erfolgreich gelöscht.",
"onlyAdminsCanEdit": "Nur Projektadministrator:innen können Ansichten bearbeiten.",
"updateSuccess": "Die Ansicht wurde erfolgreich aktualisiert."
@@ -847,7 +848,8 @@
"delete": "Diesen Kommentar löschen",
"deleteText1": "Bist du sicher, dass du diesen Kommentar löschen willst?",
"deleteSuccess": "Der Kommentar wurde erfolgreich gelöscht.",
"addedSuccess": "Der Kommentar wurde erfolgreich hinzugefügt."
"addedSuccess": "Der Kommentar wurde erfolgreich hinzugefügt.",
"permalink": "Permalink zu diesem Kommentar kopieren"
},
"deferDueDate": {
"title": "Fälligkeitsdatum verschieben",
@@ -1072,7 +1074,7 @@
"createProject": "Projekt erstellen",
"cantArchiveIsDefault": "Du kannst dieses Projekt nicht archivieren, da es dein Standardprojekt ist.",
"cantDeleteIsDefault": "Du kannst dieses Projekt nicht löschen, da es dein Standardprojekt ist.",
"views": "Views"
"views": "Ansichten"
},
"apiConfig": {
"url": "Vikunja-URL",
@@ -1141,8 +1143,8 @@
"1012": "Die E-Mail-Adresse der:s Benutzer:in ist nicht bestätigt.",
"1013": "Das neue Passwort ist leer.",
"1014": "Das alte Passwort ist leer.",
"1015": "Dieser Benutzer verwendet TOTP bereits.",
"1016": "Dieser Benutzer verwendet TOTP nicht.",
"1015": "Diese:r Benutzer:in verwendet TOTP bereits.",
"1016": "Diese:r Benutzer:in verwendet TOTP nicht.",
"1017": "Falscher TOTP Code.",
"1018": "Die Avatareinstellungen sind falsch.",
"2001": "Die ID kann nicht leer oder 0 sein.",
@@ -1153,7 +1155,7 @@
"3006": "Diese Linkfreigabe existiert nicht.",
"3007": "Ein Projekt mit diesem Bezeichner existiert bereits.",
"3008": "Dieses Projekt ist archiviert und kann deshalb nur gelesen werden. Dies gilt auch für alle Aufgaben, die mit diesem Projekt verbunden sind.",
"4001": "Der Aufgabentitel kann nicht leer sein.",
"4001": "Der Titel darf nicht leer sein.",
"4002": "Diese Aufgabe existiert nicht.",
"4003": "Alle Massenbearbeitungen an Aufgaben müssen zum selben Projekt gehören.",
"4004": "Es benötigt mindestens einen Task, um eine Massenänderung durchzuführen.",
+32 -30
View File
@@ -43,7 +43,7 @@
"forgotPassword": "Passwort vergessen?",
"resetPassword": "Setz diis Passwort zrugg",
"resetPasswordAction": "Schick mir en Passwort zruggsetz Link",
"resetPasswordSuccess": "Prüfe deinen Posteingang! Du solltest eine E-Mail mit Anweisungen zum Zurücksetzen deines Passworts erhalten haben.",
"resetPasswordSuccess": "Überprüfe deinen Posteingang! Du solltest eine E-Mail mit Anweisungen zum Zurücksetzen deines Passwortes haben.",
"passwordsDontMatch": "Passwörter stimmed nid überiih",
"confirmEmailSuccess": "Du hesch dini E-Mail erfolgriich bestätigt! Du chasch dich jetzt iihlogge.",
"totpTitle": "Zweifaktor Authentifizierigs Ziffere",
@@ -72,21 +72,21 @@
"settings": {
"title": "Iihstellige",
"newPasswordTitle": "Diis Passwort aktualisierä",
"newPassword": "Neus Passwort",
"newPasswordConfirm": "Neui Passwort Bestätigung",
"currentPassword": "Momentans Passwort",
"newPassword": "Neues Passwort",
"newPasswordConfirm": "Bestätigung des neuen Passworts",
"currentPassword": "Aktuelles Passwort",
"currentPasswordPlaceholder": "Diis jetzige Passwort",
"passwordsDontMatch": "Dis neue Passwort und siini Bestätigung stimmed nid überiih.",
"passwordUpdateSuccess": "Dis Passwort isch erfolgriich aktualisiert wordä.",
"updateEmailTitle": "Aktualisiere deine E-Mail-Adresse",
"updateEmailNew": "Neui E-Mail Adrässä",
"updateEmailNew": "Neue E-Mail-Adresse",
"updateEmailSuccess": "Dini E-Mail Adrässä isch erfolgriich gänderet worde. Mir hend dir en Link gschickt, um si zu bestätigä.",
"general": {
"title": "Allgemeini Iihstellige",
"name": "Namä",
"newName": "De neu Namä",
"name": "Mein Name",
"newName": "Neuer Name",
"savedSuccess": "Die Iihstellige sind erfolgriich aktualisiert wordä.",
"emailReminders": "Schick mir e Errinnerig für Uufgabe per E-Mail",
"emailReminders": "Erinnerungen an Aufgaben per E-Mail senden",
"overdueReminders": "Sende mir jeden Tag eine Zusammenfassung meiner überfälligen Aufgaben",
"discoverableByName": "Erlaube anderen Benutzer:innen, mich als Mitglied zu Teams oder Projekten hinzuzufügen, wenn sie nach meinem Namen suchen",
"discoverableByEmail": "Erlaube anderen Benutzer:innen, mich als Mitglied zu Teams oder Projekten hinzuzufügen, wenn sie nach meiner vollständigen E-Mail Adresse suchen",
@@ -95,7 +95,7 @@
"weekStartSunday": "Sunntig",
"weekStartMonday": "Määntig",
"language": "Sproch",
"defaultProject": "Standard-Projekt",
"defaultProject": "Standardprojekt",
"defaultView": "Standardansicht",
"timezone": "Zeitzone",
"overdueTasksRemindersTime": "Zeit der E-Mail-Zusammenfassung der überfälligen Aufgaben",
@@ -104,15 +104,15 @@
"totp": {
"title": "Zweifaktor Authentifizierig",
"enroll": "Aktiviere",
"finishSetupPart1": "Um dini Iihrichtig fertigzstelle, benutz das Gheimniss i dinere Authentifizierigs App (Google Authentifikator oder ähnliches):",
"finishSetupPart1": "Um die Aktivierung abzuschließen, benutze diesen Token in deiner TOTP App (Google Authenticator oder ähnliche):",
"finishSetupPart2": "Denoch, gib de Code vo dinere App une iih.",
"scanQR": "Alternativ chasch au de QR Code scanne:",
"passcode": "Code",
"passcodePlaceholder": "En vo dinere Authentifikator App generierte code",
"passcodePlaceholder": "Ein von deiner TOTP-App generierter Code",
"setupSuccess": "Du hast die Zwei-Faktor-Authentifizierung erfolgreich eingerichtet!",
"enterPassword": "Bitte gib diis Passwort iih",
"disable": "Zweifaktor Authentifizierig uusschalte",
"confirmSuccess": "Du hesch erfolgriich dini Zweifaktor Authentifizierig aktiviert und chasch se vo jetzt au benutze!",
"confirmSuccess": "TOTP wurde verifiziert und ist nun verwendbar!",
"disableSuccess": "Die Zwei-Faktor-Authentifizierung wurde erfolgreich deaktiviert."
},
"caldav": {
@@ -163,6 +163,7 @@
"90d": "90 Tage",
"permissionExplanation": "Mit Berechtigungen kannst du einschränken, was ein API-Token tun darf.",
"titleRequired": "Titel ist erforderlich",
"permissionRequired": "Bitte wähle mindestens eine Berechtigung aus der Liste.",
"expired": "Dieses Token ist {ago} abgelaufen.",
"tokenCreatedSuccess": "Hier ist dein neues API Token: {token}",
"tokenCreatedNotSeeAgain": "Speichere es an einem sicheren Ort, du wirst es nicht mehr sehen!",
@@ -387,19 +388,19 @@
"secretDocs": "In der Dokumentation findest du weitere Informationen zum Umgang mit Schlüsseln."
},
"views": {
"header": "Edit views",
"title": "Title",
"actions": "Actions",
"kind": "Kind",
"bucketConfigMode": "Bucket configuration mode",
"bucketConfig": "Bucket configuration",
"bucketConfigManual": "Manual",
"header": "Ansichten bearbeiten",
"title": "Titel",
"actions": "Aktionen",
"kind": "Ansicht",
"bucketConfigMode": "Bucket-Konfigurationsmodus",
"bucketConfig": "Bucket-Konfiguration",
"bucketConfigManual": "Manuell",
"filter": "Filter",
"create": "Create view",
"createSuccess": "The view was created successfully.",
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"create": "Ansicht erstellen",
"createSuccess": "Die Ansicht wurde erfolgreich erstellt.",
"titleRequired": "Bitte gib einen Titel ein.",
"delete": "Diese Ansicht löschen",
"deleteText": "Bist du sicher, dass du diese Ansicht entfernen möchtest? Es wird nicht mehr möglich sein, sie zu verwenden, um Aufgaben in diesem Projekt anzuzeigen. Diese Aktion löscht keine Aufgaben. Dies kann nicht rückgängig gemacht werden!",
"deleteSuccess": "Diese Ansicht wurde erfolgreich gelöscht.",
"onlyAdminsCanEdit": "Nur Projektadministrator:innen können Ansichten bearbeiten.",
"updateSuccess": "Die Ansicht wurde erfolgreich aktualisiert."
@@ -847,7 +848,8 @@
"delete": "De Kommentar chüble",
"deleteText1": "Bisch du dir sicher, dass du de Kommentar chüble wetsch?",
"deleteSuccess": "Der Kommentar wurde erfolgreich gelöscht.",
"addedSuccess": "Din Kommentar isch erfolgriich hinzuegfüegt worde."
"addedSuccess": "Din Kommentar isch erfolgriich hinzuegfüegt worde.",
"permalink": "Permalink zu diesem Kommentar kopieren"
},
"deferDueDate": {
"title": "Fälligkeitsdatum verschiebe",
@@ -1072,7 +1074,7 @@
"createProject": "Projekt erstellen",
"cantArchiveIsDefault": "Du kannst dieses Projekt nicht archivieren, da es dein Standardprojekt ist.",
"cantDeleteIsDefault": "Du kannst dieses Projekt nicht löschen, da es dein Standardprojekt ist.",
"views": "Views"
"views": "Ansichten"
},
"apiConfig": {
"url": "Vikunja URL",
@@ -1141,9 +1143,9 @@
"1012": "Die E-Mail vo dem Benutzer isch nid bestätigt.",
"1013": "Neus Passwort isch läär.",
"1014": "Alts Passwort isch läär.",
"1015": "Totp isch für de Benutzer scho aktiv.",
"1016": "Totp isch für de Benutzer nid aktiv.",
"1017": "De Totp Code nid gültig.",
"1015": "Diese:r Benutzer:in verwendet TOTP bereits.",
"1016": "Diese:r Benutzer:in verwendet TOTP nicht.",
"1017": "Falscher TOTP Code.",
"1018": "Die Benutzer Profilbild Iihstellige sind nid gültig.",
"2001": "ID chann nid leer oder 0 sii.",
"2002": "Ebbis vo de Ahfragedate isch ungültig.",
@@ -1153,7 +1155,7 @@
"3006": "Diese Linkfreigabe existiert nicht.",
"3007": "Ein Projekt mit diesem Bezeichner existiert bereits.",
"3008": "Dieses Projekt ist archiviert und kann deshalb nur gelesen werden. Dies gilt auch für alle Aufgaben, die mit diesem Projekt verbunden sind.",
"4001": "Der Aufgabentitel kann nicht leer sein.",
"4001": "Der Titel darf nicht leer sein.",
"4002": "Diese Aufgabe existiert nicht.",
"4003": "Alle Massenbearbeitungen an Aufgaben müssen zum selben Projekt gehören.",
"4004": "Es bruucht mindestens ei Uufgab, um e Masseänderig durezfüehre.",
+20 -19
View File
@@ -43,7 +43,7 @@
"forgotPassword": "Forgot your password?",
"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.",
"resetPasswordSuccess": "Check your inbox! You should have an email 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",
@@ -72,21 +72,21 @@
"settings": {
"title": "Settings",
"newPasswordTitle": "Update Your Password",
"newPassword": "New Password",
"newPasswordConfirm": "New Password Confirmation",
"currentPassword": "Current 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 Email Address",
"updateEmailNew": "New Email 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",
"name": "My Name",
"newName": "The new name",
"savedSuccess": "The settings were successfully updated.",
"emailReminders": "Send me reminders for tasks via Email",
"emailReminders": "Send me reminders for tasks via email",
"overdueReminders": "Send me a summary of my undone overdue tasks every day",
"discoverableByName": "Allow other users to add me as a member to teams or projects when they search for my name",
"discoverableByEmail": "Allow other users to add me as a member to teams or projects when they search for my full email",
@@ -95,24 +95,24 @@
"weekStartSunday": "Sunday",
"weekStartMonday": "Monday",
"language": "Language",
"defaultProject": "Default Project",
"defaultView": "Default View",
"timezone": "Time Zone",
"defaultProject": "Default project",
"defaultView": "Default view",
"timezone": "Time zone",
"overdueTasksRemindersTime": "Overdue tasks reminder email time",
"filterUsedOnOverview": "Saved filter used on the overview page"
},
"totp": {
"title": "Two Factor Authentication",
"enroll": "Enroll",
"finishSetupPart1": "To finish your setup, use this secret in your totp app (Google Authenticator or similar):",
"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",
"passcodePlaceholder": "A code generated by your TOTP application",
"setupSuccess": "You've successfully 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!",
"confirmSuccess": "You've successfully confirmed your TOTP setup and can use it from now on!",
"disableSuccess": "Two factor authentication was successfully disabled."
},
"caldav": {
@@ -163,6 +163,7 @@
"90d": "90 Days",
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
"titleRequired": "The title is required",
"permissionRequired": "Please select at least one permission from the list.",
"expired": "This token has expired {ago}.",
"tokenCreatedSuccess": "Here is your new api token: {token}",
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
@@ -1141,9 +1142,9 @@
"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.",
"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.",
@@ -1153,8 +1154,8 @@
"3006": "The project share does not exist.",
"3007": "A project with this identifier already exists.",
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
"4001": "The project task text cannot be empty.",
"4002": "The project task does not exist.",
"4001": "The task title cannot be empty.",
"4002": "The task does not exist.",
"4003": "All bulk editing tasks must belong to the same project.",
"4004": "Need at least one task when bulk editing tasks.",
"4005": "You do not have the right to see the task.",
+22 -20
View File
@@ -43,7 +43,7 @@
"forgotPassword": "Forgot your password?",
"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.",
"resetPasswordSuccess": "Check your inbox! You should have an email 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",
@@ -72,21 +72,21 @@
"settings": {
"title": "Settings",
"newPasswordTitle": "Update Your Password",
"newPassword": "New Password",
"newPasswordConfirm": "New Password Confirmation",
"currentPassword": "Current 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 Email Address",
"updateEmailNew": "New Email 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",
"name": "My Name",
"newName": "The new name",
"savedSuccess": "The settings were successfully updated.",
"emailReminders": "Send me reminders for tasks via Email",
"emailReminders": "Send me reminders for tasks via email",
"overdueReminders": "Send me a summary of my undone overdue tasks every day",
"discoverableByName": "Allow other users to add me as a member to teams or projects when they search for my name",
"discoverableByEmail": "Allow other users to add me as a member to teams or projects when they search for my full email",
@@ -95,24 +95,24 @@
"weekStartSunday": "Sunday",
"weekStartMonday": "Monday",
"language": "Language",
"defaultProject": "Default Project",
"defaultView": "Default View",
"timezone": "Time Zone",
"defaultProject": "Default project",
"defaultView": "Default view",
"timezone": "Time zone",
"overdueTasksRemindersTime": "Overdue tasks reminder email time",
"filterUsedOnOverview": "Saved filter used on the overview page"
},
"totp": {
"title": "Two Factor Authentication",
"enroll": "Enroll",
"finishSetupPart1": "To finish your setup, use this secret in your totp app (Google Authenticator or similar):",
"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",
"passcodePlaceholder": "A code generated by your TOTP application",
"setupSuccess": "You've successfully 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!",
"confirmSuccess": "You've successfully confirmed your TOTP setup and can use it from now on!",
"disableSuccess": "Two factor authentication was successfully disabled."
},
"caldav": {
@@ -163,6 +163,7 @@
"90d": "90 Days",
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
"titleRequired": "The title is required",
"permissionRequired": "Please select at least one permission from the list.",
"expired": "This token has expired {ago}.",
"tokenCreatedSuccess": "Here is your new api token: {token}",
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
@@ -847,7 +848,8 @@
"delete": "Delete this comment",
"deleteText1": "Are you sure you want to delete this comment?",
"deleteSuccess": "The comment was deleted successfully.",
"addedSuccess": "The comment was added successfully."
"addedSuccess": "The comment was added successfully.",
"permalink": "Copy permalink to this comment"
},
"deferDueDate": {
"title": "Defer due date",
@@ -1141,9 +1143,9 @@
"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.",
"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.",
@@ -1153,8 +1155,8 @@
"3006": "The project share does not exist.",
"3007": "A project with this identifier already exists.",
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
"4001": "The project task text cannot be empty.",
"4002": "The project task does not exist.",
"4001": "The task title cannot be empty.",
"4002": "The task does not exist.",
"4003": "All bulk editing tasks must belong to the same project.",
"4004": "Need at least one task when bulk editing tasks.",
"4005": "You do not have the right to see the task.",
+22 -20
View File
@@ -43,7 +43,7 @@
"forgotPassword": "¿Olvidaste tu contraseña?",
"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.",
"resetPasswordSuccess": "Check your inbox! You should have an email with instructions on how to reset your password.",
"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",
@@ -72,21 +72,21 @@
"settings": {
"title": "Ajustes",
"newPasswordTitle": "Actualiza tu contraseña",
"newPassword": "Nueva contraseña",
"newPasswordConfirm": "Confirmar contraseña",
"currentPassword": "Contraseña actual",
"newPassword": "New password",
"newPasswordConfirm": "New password confirmation",
"currentPassword": "Current password",
"currentPasswordPlaceholder": "Tu contraseña actual",
"passwordsDontMatch": "La nueva contraseña y su confirmación no coinciden.",
"passwordUpdateSuccess": "La contraseña se actualizó con éxito.",
"updateEmailTitle": "Actualiza tu Dirección de Correo Electrónico",
"updateEmailNew": "Nueva dirección de correo electrónico",
"updateEmailNew": "New email address",
"updateEmailSuccess": "Tu dirección de correo electrónico se ha actualizado con éxito. Te hemos enviado un enlace para confirmarlo.",
"general": {
"title": "Ajustes Generales",
"name": "Nombre",
"newName": "El nombre nuevo",
"name": "My Name",
"newName": "The new name",
"savedSuccess": "Ajustes actualizados con éxito.",
"emailReminders": "Enviarme recordatorios de tareas vía Correo Electrónico",
"emailReminders": "Send me reminders for tasks via email",
"overdueReminders": "Enviarme un resumen de mis tareas pendientes todos los días",
"discoverableByName": "Permitir que otros usuarios me agreguen como miembro a equipos o proyectos cuando busquen mi nombre",
"discoverableByEmail": "Permitir a otros usuarios añadirme como miembro a equipos o proyectos cuando busquen mi email completo",
@@ -95,24 +95,24 @@
"weekStartSunday": "Domingo",
"weekStartMonday": "Lunes",
"language": "Idioma",
"defaultProject": "Proyecto por defecto",
"defaultView": "Default View",
"timezone": "Zona Horaria",
"defaultProject": "Default project",
"defaultView": "Default view",
"timezone": "Time zone",
"overdueTasksRemindersTime": "Hora de envío del correo electrónico de tareas pendientes",
"filterUsedOnOverview": "Filtro guardado utilizado en la página de resumen"
},
"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):",
"finishSetupPart1": "To finish your setup, use this secret in your TOTP app (Google Authenticator or 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",
"passcodePlaceholder": "A code generated by your TOTP application",
"setupSuccess": "¡Has configurado con éxito la autenticación de dos factores!",
"enterPassword": "Por favor, introduce tu contraseña",
"disable": "Deshabilitar la autenticación de dos factores",
"confirmSuccess": "¡Has confirmado con éxito tu configuración totp y puedes usarla a partir de ahora!",
"confirmSuccess": "You've successfully confirmed your TOTP setup and can use it from now on!",
"disableSuccess": "La autenticación de dos factores desactivada con éxito."
},
"caldav": {
@@ -163,6 +163,7 @@
"90d": "90 Days",
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
"titleRequired": "The title is required",
"permissionRequired": "Please select at least one permission from the list.",
"expired": "This token has expired {ago}.",
"tokenCreatedSuccess": "Here is your new api token: {token}",
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
@@ -847,7 +848,8 @@
"delete": "Eliminar este comentario",
"deleteText1": "¿Está seguro que desea eliminar este comentario?",
"deleteSuccess": "El comentario fue eliminado con éxito.",
"addedSuccess": "El comentario fue añadido con éxito."
"addedSuccess": "El comentario fue añadido con éxito.",
"permalink": "Copy permalink to this comment"
},
"deferDueDate": {
"title": "Aplazar fecha de vencimiento",
@@ -1141,9 +1143,9 @@
"1012": "La dirección de correo electrónico del usuario no está confirmada.",
"1013": "La nueva contraseña está vacía.",
"1014": "La contraseña antigua está vacía.",
"1015": "Totp ya está habilitado para este usuario.",
"1016": "Totp no está habilitado para este usuario.",
"1017": "El código Totp es inválido.",
"1015": "TOTP is already enabled for this user.",
"1016": "TOTP is not enabled for this user.",
"1017": "The TOTP passcode is invalid.",
"1018": "La configuración del tipo de avatar de usuario es inválida.",
"2001": "El ID no puede estar vacío o ser 0.",
"2002": "Algunos datos de la solicitud fueron inválidos.",
@@ -1153,8 +1155,8 @@
"3006": "La compartición del proyecto no existe.",
"3007": "Ya existe un proyecto con este identificador.",
"3008": "El proyecto está archivado y por lo tanto sólo se puede acceder a él en modo lectura. Esto también se aplica a las tareas asociadas con este proyecto.",
"4001": "El texto de la tarea del proyecto no puede estar vacío.",
"4002": "La tarea del proyecto no existe.",
"4001": "The task title cannot be empty.",
"4002": "The task does not exist.",
"4003": "Todas las tareas de edición en masa deben pertenecer al mismo proyecto.",
"4004": "Se necesita al menos una tarea cuando se editan tareas masivamente.",
"4005": "No tiene permiso para ver la tarea.",
+22 -20
View File
@@ -43,7 +43,7 @@
"forgotPassword": "Mot de passe oublié ?",
"resetPassword": "Réinitialiser votre mot de passe",
"resetPasswordAction": "M’envoyer un lien de réinitialisation du mot de passe",
"resetPasswordSuccess": "Vérifiez votre boîte de réception ! Vous devriez y trouver un courriel contenant les instructions pour réinitialiser votre mot de passe.",
"resetPasswordSuccess": "Check your inbox! You should have an email with instructions on how to reset your password.",
"passwordsDontMatch": "Les mots de passe ne correspondent pas",
"confirmEmailSuccess": "Vous avez bien confirmé votre adresse ! Vous pouvez maintenant vous connecter.",
"totpTitle": "Code d’authentification à deux facteurs",
@@ -72,21 +72,21 @@
"settings": {
"title": "Paramètres",
"newPasswordTitle": "Mets à jour ton mot de passe",
"newPassword": "Nouveau mot de passe",
"newPasswordConfirm": "Confirmation du nouveau mot de passe",
"currentPassword": "Mot de passe actuel",
"newPassword": "New password",
"newPasswordConfirm": "New password confirmation",
"currentPassword": "Current password",
"currentPasswordPlaceholder": "Votre mot de passe actuel",
"passwordsDontMatch": "Le nouveau mot de passe et sa confirmation ne correspondent pas.",
"passwordUpdateSuccess": "Le mot de passe a bien été mis à jour.",
"updateEmailTitle": "Mettre à jour votre adresse électronique",
"updateEmailNew": "Nouvelle adresse électronique",
"updateEmailNew": "New email address",
"updateEmailSuccess": "L’adresse électronique a bien été mise à jour. Nous vous avons envoyé un lien pour confirmer ce changement.",
"general": {
"title": "Paramètres généraux",
"name": "Nom",
"newName": "Le nouveau nom",
"name": "My Name",
"newName": "The new name",
"savedSuccess": "Paramètres mis à jour.",
"emailReminders": "M’envoyer des rappels de tâches par courriel",
"emailReminders": "Send me reminders for tasks via email",
"overdueReminders": "Envoyez-moi un résumé de mes tâches en retard inachevées chaque jour",
"discoverableByName": "Autoriser d’autres utilisateurs ou utilisatrices à m’ajouter en tant que membre aux équipes ou aux projets lorsqu’ils recherchent mon nom",
"discoverableByEmail": "Autoriser d’autres utilisateurs ou utilisatrices à m’ajouter en tant que membre aux équipes ou aux projets lorsqu’ils recherchent mon adresse électronique complète",
@@ -95,24 +95,24 @@
"weekStartSunday": "dimanche",
"weekStartMonday": "lundi",
"language": "Langue",
"defaultProject": "Projet par défaut",
"defaultView": "Default View",
"timezone": "Fuseau horaire",
"defaultProject": "Default project",
"defaultView": "Default view",
"timezone": "Time zone",
"overdueTasksRemindersTime": "Heure du courriel de rappel des tâches en retard",
"filterUsedOnOverview": "Filtre enregistré utilisé sur la vue d’ensemble"
},
"totp": {
"title": "Authentification à deux facteurs",
"enroll": "S’inscrire",
"finishSetupPart1": "Pour terminer votre configuration, utilisez ce secret dans votre application de mot de passe à usage unique :",
"finishSetupPart1": "To finish your setup, use this secret in your TOTP app (Google Authenticator or similar):",
"finishSetupPart2": "Après cela, saisissez un code issu de votre application ci-dessous.",
"scanQR": "Vous pouvez également scanner ce code QR :",
"passcode": "Code",
"passcodePlaceholder": "Un code généré par votre application de mot de passe à usage unique",
"passcodePlaceholder": "A code generated by your TOTP application",
"setupSuccess": "Vous avez maintenant configuré l’authentification à deux facteurs !",
"enterPassword": "Saisissez votre mot de passe",
"disable": "Désactiver l’authentification à deux facteurs",
"confirmSuccess": "La configuration du mot de passe à usage unique est confirmée et vous pouvez l’utiliser dès à présent !",
"confirmSuccess": "You've successfully confirmed your TOTP setup and can use it from now on!",
"disableSuccess": "L’authentification à deux facteurs a bien été désactivée."
},
"caldav": {
@@ -163,6 +163,7 @@
"90d": "90 Days",
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
"titleRequired": "The title is required",
"permissionRequired": "Please select at least one permission from the list.",
"expired": "This token has expired {ago}.",
"tokenCreatedSuccess": "Here is your new api token: {token}",
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
@@ -847,7 +848,8 @@
"delete": "Supprimer ce commentaire",
"deleteText1": "Supprimer ce commentaire ?",
"deleteSuccess": "Le commentaire a bien été supprimé.",
"addedSuccess": "Commentaire ajouté."
"addedSuccess": "Commentaire ajouté.",
"permalink": "Copy permalink to this comment"
},
"deferDueDate": {
"title": "Reporter la date d’échéance",
@@ -1141,9 +1143,9 @@
"1012": "L’adresse électronique de l’utilisateur·rice n’est pas confirmée.",
"1013": "Le nouveau mot de passe est vide.",
"1014": "L’ancien mot de passe est vide.",
"1015": "Cette personne utilise déjà un mot de passe à usage unique.",
"1016": "Le mot de passe à usage unique n’est pas activé pour cet utilisateur ou cette utilisatrice.",
"1017": "Code à usage unique invalide.",
"1015": "TOTP is already enabled for this user.",
"1016": "TOTP is not enabled for this user.",
"1017": "The TOTP passcode is invalid.",
"1018": "Le paramètre du type d’avatar de l’utilisateur·rice est invalide.",
"2001": "L’identifiant ne peut pas être vide ou égal à 0.",
"2002": "Certaines des données de la requête étaient invalides.",
@@ -1153,8 +1155,8 @@
"3006": "Le partage de ce projet n’existe pas.",
"3007": "Un projet avec cet identifiant existe déjà.",
"3008": "Le projet est archivé et ne peut donc être consulté qu’en lecture seule. Ceci est également vrai pour toutes les tâches associées à ce projet.",
"4001": "Le texte de la tâche du projet ne peut pas être vide.",
"4002": "La tâche de projet n’existe pas.",
"4001": "The task title cannot be empty.",
"4002": "The task does not exist.",
"4003": "Toutes les tâches de modification en bloc doivent appartenir au même projet.",
"4004": "Besoin d’au moins une tâche lors de la modification en bloc de tâches.",
"4005": "Vous n’avez pas le droit de voir la tâche.",
+22 -20
View File
@@ -43,7 +43,7 @@
"forgotPassword": "Zaboravili ste lozinku?",
"resetPassword": "Ponovno postavite lozinku",
"resetPasswordAction": "Pošalji mi vezu za ponovno postavljanje lozinke",
"resetPasswordSuccess": "Provjerite svoj inbox! Trebali ste dobiti e-poštu s uputama kako poništiti lozinku.",
"resetPasswordSuccess": "Check your inbox! You should have an email with instructions on how to reset your password.",
"passwordsDontMatch": "Lozinke se ne podudaraju",
"confirmEmailSuccess": "Uspješno ste potvrdili svoju e-poštu! Sada se možete prijaviti.",
"totpTitle": "Dvostruki kod za provjeru autentifikacije",
@@ -72,21 +72,21 @@
"settings": {
"title": "Postavke",
"newPasswordTitle": "Ažurirajte svoju lozinku",
"newPassword": "Nova lozinka",
"newPasswordConfirm": "Potvrda nove lozinke",
"currentPassword": "Trenutna lozinka",
"newPassword": "New password",
"newPasswordConfirm": "New password confirmation",
"currentPassword": "Current password",
"currentPasswordPlaceholder": "Vaša trenutna lozinka",
"passwordsDontMatch": "Nova lozinka i njezina potvrda ne odgovaraju.",
"passwordUpdateSuccess": "Lozinka je uspješno ažurirana.",
"updateEmailTitle": "Ažurirajte e-mail adresu",
"updateEmailNew": "Nova adresa e-pošte",
"updateEmailNew": "New email address",
"updateEmailSuccess": "Vaša email adresa je uspješno ažurirana. Poslali smo vam poveznicu da ju potvrdite.",
"general": {
"title": "Opće postavke",
"name": "Ime",
"newName": "Novo ime",
"name": "My Name",
"newName": "The new name",
"savedSuccess": "Postavke su uspješno spremljene.",
"emailReminders": "Šalji mi podsjetnike za zadatke putem e-pošte",
"emailReminders": "Send me reminders for tasks via email",
"overdueReminders": "Šalji mi sažetak mojih neobavljenih zakašnjelih zadataka svaki dan",
"discoverableByName": "Dopusti drugim korisnicima da me dodaju kao člana u timove ili projekte kada traže moje ime",
"discoverableByEmail": "Dopusti drugim korisnicima da me dodaju kao člana u timove ili projekte kada traže moju punu e-poštu",
@@ -95,24 +95,24 @@
"weekStartSunday": "Nedjelju",
"weekStartMonday": "Ponedjeljak",
"language": "Jezik",
"defaultProject": "Zadani projekt",
"defaultView": "Default View",
"timezone": "Vremenska Zona",
"defaultProject": "Default project",
"defaultView": "Default view",
"timezone": "Time zone",
"overdueTasksRemindersTime": "Vrijeme podsjetnika za zakašnjele zadatke putem e-pošte",
"filterUsedOnOverview": "Spremljeni filtar korišten na stranici pregleda"
},
"totp": {
"title": "Dvofaktorska autentikacija",
"enroll": "Učlani se",
"finishSetupPart1": "Da biste dovršili svoje postavljanje, upotrijebite ovu tajnu u svojoj totp aplikaciji (Google Authenticator ili slično):",
"finishSetupPart1": "To finish your setup, use this secret in your TOTP app (Google Authenticator or similar):",
"finishSetupPart2": "Nakon toga unesite kod iz svoje aplikacije u nastavku.",
"scanQR": "Alternativno, možete skenirati ovaj QR kod:",
"passcode": "Lozinka",
"passcodePlaceholder": "Kod koji je generirala vaša totp aplikacija",
"passcodePlaceholder": "A code generated by your TOTP application",
"setupSuccess": "Uspješno ste postavili dvostruku provjeru autentičnosti!",
"enterPassword": "Molimo unesite vašu lozinku",
"disable": "Isključite dvofaktorsku provjeru autentičnosti",
"confirmSuccess": "Uspješno ste potvrdili svoju totp postavku i od sada je možete koristiti!",
"confirmSuccess": "You've successfully confirmed your TOTP setup and can use it from now on!",
"disableSuccess": "Dvostruka provjera autentičnosti uspješno je onemogućena."
},
"caldav": {
@@ -163,6 +163,7 @@
"90d": "90 dana",
"permissionExplanation": "Dopuštenja vam omogućuju da odredite što api token smije raditi.",
"titleRequired": "Naslov je obavezan",
"permissionRequired": "Please select at least one permission from the list.",
"expired": "Ovaj je token istekao {ago}.",
"tokenCreatedSuccess": "Evo vašeg novog api tokena: {token}",
"tokenCreatedNotSeeAgain": "Čuvajte ga na sigurnom mjestu, nećete ga više vidjeti!",
@@ -847,7 +848,8 @@
"delete": "Delete this comment",
"deleteText1": "Are you sure you want to delete this comment?",
"deleteSuccess": "The comment was deleted successfully.",
"addedSuccess": "The comment was added successfully."
"addedSuccess": "The comment was added successfully.",
"permalink": "Copy permalink to this comment"
},
"deferDueDate": {
"title": "Defer due date",
@@ -1141,9 +1143,9 @@
"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.",
"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.",
@@ -1153,8 +1155,8 @@
"3006": "The project share does not exist.",
"3007": "A project with this identifier already exists.",
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
"4001": "The project task text cannot be empty.",
"4002": "The project task does not exist.",
"4001": "The task title cannot be empty.",
"4002": "The task does not exist.",
"4003": "All bulk editing tasks must belong to the same project.",
"4004": "Need at least one task when bulk editing tasks.",
"4005": "You do not have the right to see the task.",
+22 -20
View File
@@ -43,7 +43,7 @@
"forgotPassword": "Elfelejtette a jelszavát?",
"resetPassword": "Jelszó visszaállítása",
"resetPasswordAction": "Jelszó-visszaállítási linket kérek",
"resetPasswordSuccess": "Ellenőrizze postaládáját! Kapnia kell egy e-mailt a jelszó visszaállításához szükséges utasításokkal.",
"resetPasswordSuccess": "Check your inbox! You should have an email with instructions on how to reset your password.",
"passwordsDontMatch": "Jelszavak nem egyeznek",
"confirmEmailSuccess": "Sikeresen megerősítette e-mail címét! Most már bejelentkezhet.",
"totpTitle": "Kétlépcsős hitelesítő kód",
@@ -72,21 +72,21 @@
"settings": {
"title": "Beállítások",
"newPasswordTitle": "Jelszó módosítása",
"newPassword": "Új jelszó",
"newPasswordConfirm": "Új jelszó megerősítése",
"currentPassword": "Jelenlegi jelszó",
"newPassword": "New password",
"newPasswordConfirm": "New password confirmation",
"currentPassword": "Current password",
"currentPasswordPlaceholder": "Az Ön aktuális jelszava",
"passwordsDontMatch": "Az új jelszó és a megerősítés nem egyezik.\nKérlek próbáld újra! Ügyelj a kis és nagy karakterekre!",
"passwordUpdateSuccess": "A jelszó módosítása megtörtént.",
"updateEmailTitle": "E-mail cím módosítása",
"updateEmailNew": "Új e-mail cím",
"updateEmailNew": "New email address",
"updateEmailSuccess": "E-mail címe sikeresen frissítve. Küldtünk egy linket a megerősítéshez.",
"general": {
"title": "Általános beállítások",
"name": "Név",
"newName": "Új név",
"name": "My Name",
"newName": "The new name",
"savedSuccess": "Beállítások sikeresen frissítve.",
"emailReminders": "Emlékeztetők küldése a feladatokról E-mailben",
"emailReminders": "Send me reminders for tasks via email",
"overdueReminders": "Minden nap küldjön nekem egy összefoglalót az elmaradt, késedelmes feladataimról",
"discoverableByName": "Engedélyezze a többi felhasználónak, hogy tagként vegyen fel csapatokba vagy projektekbe, amikor a nevemre keresnek",
"discoverableByEmail": "Engedélyezze a többi felhasználónak, hogy tagként vegyen fel csapatokhoz vagy projektekhez, amikor a teljes e-mailemet keresik",
@@ -95,24 +95,24 @@
"weekStartSunday": "Vasárnap",
"weekStartMonday": "Hétfő",
"language": "Nyelv",
"defaultProject": "Alapértelmezett projekt",
"defaultView": "Default View",
"timezone": "Időzóna",
"defaultProject": "Default project",
"defaultView": "Default view",
"timezone": "Time zone",
"overdueTasksRemindersTime": "Esedékes feladatokra emlékeztető e-mail küldésének ideje",
"filterUsedOnOverview": "Az áttekintő oldalon használt mentett szűrő"
},
"totp": {
"title": "Kétlépcsős azonosítás",
"enroll": "Csatlakozás",
"finishSetupPart1": "A beállítás befejezéséhez használja ezt a titkot a TOTP alkalmazásban (Google Authenticator vagy hasonló):",
"finishSetupPart1": "To finish your setup, use this secret in your TOTP app (Google Authenticator or similar):",
"finishSetupPart2": "Ezután írjon be egy kódot az alkalmazásból alább.",
"scanQR": "Alternatív megoldásként beolvashatja ezt a QR-kódot:",
"passcode": "Jelkód",
"passcodePlaceholder": "A TOTP alkalmazás által generált kód",
"passcodePlaceholder": "A code generated by your TOTP application",
"setupSuccess": "Két-faktoros azonosítás sikeresen bekapcsolva!",
"enterPassword": "Kérjük, írja be jelszavát",
"disable": "Kétlépcsős hitelesítés kikapcsolása",
"confirmSuccess": "Sikeresen megerősítette a TOTP beállítását és mostantól használhatja!",
"confirmSuccess": "You've successfully confirmed your TOTP setup and can use it from now on!",
"disableSuccess": "A kétlépcsős azonosítást sikeresen kikapcsoltuk."
},
"caldav": {
@@ -163,6 +163,7 @@
"90d": "90 nap",
"permissionExplanation": "Az engedélyek lehetővé teszik annak hatókörét, hogy egy API Token mire jogosult.",
"titleRequired": "A cím kötelező",
"permissionRequired": "Please select at least one permission from the list.",
"expired": "Ez a token lejárt {ago}.",
"tokenCreatedSuccess": "Íme az új API tokenje: {token}",
"tokenCreatedNotSeeAgain": "Tárolja el biztonságos helyen, többé nem fogja látni!",
@@ -847,7 +848,8 @@
"delete": "Hozzászólás törlése",
"deleteText1": "Biztos benne, hogy törölni akarja ezt a hozzászólást?",
"deleteSuccess": "A hozzászólás sikeresen törlődött.",
"addedSuccess": "A hozzászólás sikeresen hozzáadva."
"addedSuccess": "A hozzászólás sikeresen hozzáadva.",
"permalink": "Copy permalink to this comment"
},
"deferDueDate": {
"title": "A határidő elhalasztása",
@@ -1141,9 +1143,9 @@
"1012": "A felhasználó e-mail címe nincs megerősítve.",
"1013": "Az új jelszó üres.",
"1014": "A régi jelszó üres.",
"1015": "A 2FA már engedélyezve van ennél a felhasználónál.",
"1016": "A 2FA nincs engedélyezve ennél a felhasználónál.",
"1017": "A TOTP jelszó érvénytelen.",
"1015": "TOTP is already enabled for this user.",
"1016": "TOTP is not enabled for this user.",
"1017": "The TOTP passcode is invalid.",
"1018": "A felhasználói avatar típus beállítása érvénytelen.",
"2001": "Az azonosító nem lehet üres vagy 0.",
"2002": "A kérés egyes adatai érvénytelenek voltak.",
@@ -1153,8 +1155,8 @@
"3006": "A projektmegosztás nem létezik.",
"3007": "Ezzel az azonosítóval már létezik projekt.",
"3008": "A projekt archiválva van, ezért csak olvasható. Ez a projekthez kapcsolódó összes feladatra is igaz.",
"4001": "A projektfeladat szövege nem lehet üres.",
"4002": "A projektfeladat nem létezik.",
"4001": "The task title cannot be empty.",
"4002": "The task does not exist.",
"4003": "Minden tömeges szerkesztési feladatnak ugyanahhoz a projekthez kell tartoznia.",
"4004": "A feladatok tömeges szerkesztéséhez legalább egy feladatra van szükség.",
"4005": "Nincs joga a feladat megtekintéséhez.",
+22 -20
View File
@@ -43,7 +43,7 @@
"forgotPassword": "Password dimenticata?",
"resetPassword": "Reimposta la tua password",
"resetPasswordAction": "Inviami il link per reimpostare la password",
"resetPasswordSuccess": "Controlla la tua casella di posta! Dovresti avere un'e-mail con le istruzioni su come reimpostare la password.",
"resetPasswordSuccess": "Check your inbox! You should have an email with instructions on how to reset your password.",
"passwordsDontMatch": "Le password non corrispondono",
"confirmEmailSuccess": "Hai confermato con successo la tua e-mail! Ora puoi accedere.",
"totpTitle": "Codice di autenticazione a due fattori",
@@ -72,21 +72,21 @@
"settings": {
"title": "Impostazioni",
"newPasswordTitle": "Aggiorna la tua Password",
"newPassword": "Nuova Password",
"newPasswordConfirm": "Conferma della nuova password",
"currentPassword": "Password attuale",
"newPassword": "New password",
"newPasswordConfirm": "New password confirmation",
"currentPassword": "Current password",
"currentPasswordPlaceholder": "La tua password attuale",
"passwordsDontMatch": "La nuova password e la conferma non coincidono.",
"passwordUpdateSuccess": "Password aggiornata con successo.",
"updateEmailTitle": "Aggiorna l'indirizzo e-mail",
"updateEmailNew": "Nuovo indirizzo e-mail",
"updateEmailNew": "New email address",
"updateEmailSuccess": "Il tuo indirizzo e-mail è stato aggiornato correttamente. Ti abbiamo inviato un collegamento per confermarlo.",
"general": {
"title": "Impostazioni Generali",
"name": "Nome",
"newName": "Il nuovo nome",
"name": "My Name",
"newName": "The new name",
"savedSuccess": "Impostazioni salvate con successo.",
"emailReminders": "Inviami promemoria per le attività via e-mail",
"emailReminders": "Send me reminders for tasks via email",
"overdueReminders": "Inviami ogni giorno un riassunto delle attività in ritardo",
"discoverableByName": "Allow other users to add me as a member to teams or projects when they search for my name",
"discoverableByEmail": "Allow other users to add me as a member to teams or projects when they search for my full email",
@@ -95,24 +95,24 @@
"weekStartSunday": "Domenica",
"weekStartMonday": "Lunedì",
"language": "Lingua",
"defaultProject": "Progetto Predefinito",
"defaultView": "Default View",
"timezone": "Fuso Orario",
"defaultProject": "Default project",
"defaultView": "Default view",
"timezone": "Time zone",
"overdueTasksRemindersTime": "Orario email attività in scadute",
"filterUsedOnOverview": "Saved filter used on the overview page"
},
"totp": {
"title": "Autenticazione a due fattori",
"enroll": "Attiva",
"finishSetupPart1": "Per completare la configurazione, usa questo codice segreto nella tua app TOTP (Autenticatore Google o simile):",
"finishSetupPart1": "To finish your setup, use this secret in your TOTP app (Google Authenticator or similar):",
"finishSetupPart2": "Dopodiché, inserisci un codice dalla tua app di qui sotto.",
"scanQR": "In alternativa puoi scansionare questo codice QR:",
"passcode": "Codice di accesso",
"passcodePlaceholder": "Un codice generato dalla tua applicazione TOTP",
"passcodePlaceholder": "A code generated by your TOTP application",
"setupSuccess": "Autenticazione a due fattori abilitata!",
"enterPassword": "Inserisci La Tua Password",
"disable": "Disabilita l'autenticazione a due fattori",
"confirmSuccess": "Hai confermato con successo la tua configurazione totp e puoi usarla da ora in poi!",
"confirmSuccess": "You've successfully confirmed your TOTP setup and can use it from now on!",
"disableSuccess": "Autenticazione a due fattori disabilitata."
},
"caldav": {
@@ -163,6 +163,7 @@
"90d": "90 Days",
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
"titleRequired": "The title is required",
"permissionRequired": "Please select at least one permission from the list.",
"expired": "This token has expired {ago}.",
"tokenCreatedSuccess": "Here is your new api token: {token}",
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
@@ -847,7 +848,8 @@
"delete": "Elimina questo commento",
"deleteText1": "Sei sicuro di voler eliminare questo commento?",
"deleteSuccess": "Commento cancellato con successo.",
"addedSuccess": "Il commento è stato aggiunto correttamente."
"addedSuccess": "Il commento è stato aggiunto correttamente.",
"permalink": "Copy permalink to this comment"
},
"deferDueDate": {
"title": "Rinvia data di scadenza",
@@ -1141,9 +1143,9 @@
"1012": "Indirizzo e-mail dell'utente non confermato.",
"1013": "La nuova password è vuota.",
"1014": "La vecchia password è vuota.",
"1015": "Autenticazione TOTP già abilitata per questo utente.",
"1016": "Autenticazione TOTP non abilitata per questo utente.",
"1017": "Codice TOTP non valido.",
"1015": "TOTP is already enabled for this user.",
"1016": "TOTP is not enabled for this user.",
"1017": "The TOTP passcode is invalid.",
"1018": "L'impostazione del tipo di avatar utente non è valida.",
"2001": "L'ID non può essere vuoto o 0.",
"2002": "Alcuni dati della richiesta non erano validi.",
@@ -1153,8 +1155,8 @@
"3006": "The project share does not exist.",
"3007": "A project with this identifier already exists.",
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
"4001": "The project task text cannot be empty.",
"4002": "The project task does not exist.",
"4001": "The task title cannot be empty.",
"4002": "The task does not exist.",
"4003": "All bulk editing tasks must belong to the same project.",
"4004": "Hai bisogno di almeno un'attività quando si modificano in blocco le attività.",
"4005": "Non hai il permesso di vedere l'attività.",

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