Compare commits

...

82 Commits

Author SHA1 Message Date
Frederick [Bot] 3925536a7c chore(i18n): update translations via Crowdin
continuous-integration/drone/push Build is passing Details
2024-05-25 00:27:52 +00:00
kolaente 2e298ffc11
fix(i18n): adjust tests from 34780daab0
continuous-integration/drone/push Build is passing Details
2024-05-24 15:05:09 +02:00
kolaente 536047c4c4
fix(i18n): typo
continuous-integration/drone/push Build is failing Details
2024-05-24 13:17:53 +02:00
kolaente 34780daab0
chore(i18n): remove "new" from creation strings
continuous-integration/drone/push Build is failing Details
You can't create an "old" thing, therefore the "new" is redundant in that context. This change clarifies usage of this in all places.
2024-05-24 09:11:27 +02:00
kolaente 76b3ac39b6
fix(filter): clarify `in` filter syntax
continuous-integration/drone/push Build is passing Details
2024-05-22 22:17:50 +02:00
Frederick [Bot] 86b460d09c chore(i18n): update translations via Crowdin
continuous-integration/drone/push Build is passing Details
2024-05-11 00:06:24 +00:00
renovate b93e237899 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-05-09 06:06:20 +00:00
Frederick [Bot] 9706ebb2fc chore(i18n): update translations via Crowdin
continuous-integration/drone/push Build is passing Details
2024-05-09 00:06:59 +00:00
renovate 1d2ee77e8a fix(deps): update tiptap to v2.3.2
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-05-08 21:05:52 +00:00
renovate 5098363e56 fix(deps): update sentry-javascript monorepo to v7.114.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-05-08 09:05:57 +00:00
renovate 4c1dc6930d chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-05-08 05:05:55 +00:00
renovate db6d88fff5 chore(deps): update dependency go to v1.22.3
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-05-07 17:06:20 +00:00
renovate b021dd7237 chore(deps): update dependency node to v20.13.0
continuous-integration/drone/push Build is passing Details
2024-05-07 16:40:39 +00:00
kolaente e1dcf2e859
feat: do not save language on the server when in demo mode
continuous-integration/drone/push Build is failing Details
When the demo mode is enabled, people set the language to their own language - which is understandable. However, this is really confusing for other people when they log in and the language is something unexpected.
This change overrides the configured language when saving it while Vikunja is in demo mode.
2024-05-07 18:39:50 +02:00
kolaente 6e759b3bee
fix(i18n): clarify from current date string
continuous-integration/drone/push Build is failing Details
Resolves https://community.vikunja.io/t/suggestion-rename-from-current-date-to-after-completed/2344
2024-05-07 18:28:22 +02:00
kolaente d3a7d79eb9
fix: use correct project title in project card
continuous-integration/drone/push Build is failing Details
2024-05-07 18:18:19 +02:00
kolaente e0ce3e50bd
fix(attachment): correct spacing around creation date
continuous-integration/drone/push Build is failing Details
2024-05-07 18:11:40 +02:00
kolaente 272f643955
fix(project): show "remove background" button only when the project has a background set
continuous-integration/drone/push Build is passing Details
2024-05-07 17:17:06 +02:00
kolaente cf46c76811
fix(i18n): use correct title for background settings menu 2024-05-07 17:14:04 +02:00
kolaente 31e502d711
fix(project): do not remove project from navigation after removing background image 2024-05-07 17:13:22 +02:00
kolaente fa628edc0c
fix(project): make sure gantt and kanban views shared with link share are full width
continuous-integration/drone/push Build is passing Details
Resolves https://github.com/go-vikunja/vikunja/issues/258
2024-05-07 16:53:21 +02:00
kolaente 053c4d5842
fix(project): bottom spacing in list view
continuous-integration/drone/push Build is passing Details
2024-05-07 16:27:13 +02:00
kolaente 8d1fc08de6
docs: clarify where to file issues
continuous-integration/drone/push Build is failing Details
Resolves https://github.com/go-vikunja/vikunja/issues/262
2024-05-07 16:13:25 +02:00
kolaente eee7b060b6
fix(docs): typos
continuous-integration/drone/push Build is failing Details
Apply patch from https://github.com/go-vikunja/vikunja/issues/263
2024-05-07 16:06:17 +02:00
renovate 794fc4c1bb fix(deps): update dependency vue to v3.4.27
continuous-integration/drone/pr Build is failing Details
continuous-integration/drone/push Build is passing Details
2024-05-07 09:05:51 +00:00
renovate e58d10bc72 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-05-07 03:05:41 +00:00
kolaente 7837bcfaae
fix(task): only count unique tasks in a bucket when checking bucket limit
continuous-integration/drone/push Build is passing Details
This fixes a bug where the current number of tasks in a bucket was computed wrong when moving tasks into a bucket with a limit. Sometimes the bug would prevent adding a task to a bucket which seemed to have space left but ultimately failed when moving the task.
2024-05-06 20:07:06 +02:00
renovate 615d40f4cd fix(deps): update module golang.org/x/crypto to v0.23.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-05-06 15:05:56 +00:00
renovate fbf7037974 chore(deps): update pnpm to v9.1.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is failing Details
2024-05-06 11:05:37 +00:00
renovate 212c5506af fix(deps): update module golang.org/x/image to v0.16.0
continuous-integration/drone/push Build is failing Details
2024-05-05 14:57:30 +00:00
renovate c861970f41 fix(deps): update module golang.org/x/term to v0.20.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is failing Details
2024-05-05 14:06:28 +00:00
kolaente 37d3715eeb
fix(task): show repeating indicator in task list for monthly repeating tasks
continuous-integration/drone/push Build is failing Details
Resolves #2319
2024-05-05 15:03:43 +02:00
kolaente b0db3ce34c
fix(quick add magic): parse full month name as month, do not replace only the abbreviation
continuous-integration/drone/push Build is failing Details
Resolves #2320
2024-05-05 14:14:30 +02:00
renovate 358e11c404 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is failing Details
2024-05-05 00:06:06 +00:00
renovate 0fd7fc1452 fix(deps): update module golang.org/x/text to v0.15.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is failing Details
2024-05-04 21:05:53 +00:00
renovate a7db576aad fix(deps): update module golang.org/x/sys to v0.20.0
continuous-integration/drone/push Build is failing Details
2024-05-04 20:13:44 +00:00
renovate 59281c39cf fix(deps): update module golang.org/x/oauth2 to v0.20.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is failing Details
2024-05-04 16:05:40 +00:00
renovate f8b502f344 chore(deps): update dev-dependencies
continuous-integration/drone/push Build is failing Details
2024-05-04 06:56:59 +00:00
Frederick [Bot] ddf8db3b1f chore(i18n): update translations via Crowdin
continuous-integration/drone/push Build is failing Details
2024-05-04 00:07:32 +00:00
renovate 9260b3f1d3 chore(deps): update dependency vite to v5.2.11
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-05-03 00:06:00 +00:00
renovate ab74b08314 fix(deps): update sentry-javascript monorepo to v7.113.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-05-02 12:05:42 +00:00
renovate 9637db5a6b chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-05-02 05:05:36 +00:00
renovate 08645d38a0 fix(deps): update dependency dompurify to v3.1.2
continuous-integration/drone/push Build is failing Details
2024-05-01 18:47:25 +00:00
renovate f155d6bb60 fix(deps): update tiptap to v2.3.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is failing Details
2024-04-30 16:05:36 +00:00
renovate 9a3d63a713 fix(deps): update dependency vue to v3.4.26
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-04-29 13:05:49 +00:00
renovate 050f4313c8 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-04-29 05:05:40 +00:00
renovate e917323d91 fix(deps): update dependency dayjs to v1.11.11
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-04-28 12:05:38 +00:00
renovate 8ad7d00559 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-04-28 00:06:00 +00:00
renovate fd126fa234 fix(deps): update dependency dompurify to v3.1.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-04-26 12:06:39 +00:00
renovate 0c39a3dd38 chore(deps): update dependency vitest to v1.5.2
continuous-integration/drone/pr Build is passing Details
2024-04-26 00:12:26 +00:00
kolaente 66e96322ea
fix: do not remove empty openid teams when none are present
continuous-integration/drone/push Build is passing Details
Maybe resolves https://community.vikunja.io/t/empty-openid-team-cleanup-cron-error-removing-empty-openid-team-database-is-locked-error-when-exporting-data/2306/3
2024-04-25 14:21:31 +02:00
kolaente 00a96663ba
fix(caldav): check if vtodo contains any components
continuous-integration/drone/push Build is passing Details
Resolves https://vikunja.sentry.io/share/issue/1ae2fd1601aa40dea4aee41927cfcf78/
2024-04-25 13:40:23 +02:00
kolaente 741370b613
fix(caldav): return more than 1000 tasks
continuous-integration/drone/push Build is failing Details
Resolves #2302
2024-04-25 13:37:04 +02:00
renovate 70183dd7c6 chore(deps): update pnpm to v9.0.6
continuous-integration/drone/push Build is passing Details
2024-04-25 06:59:45 +00:00
renovate 760bec5e76 chore(deps): update dependency vitest to v1.5.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is failing Details
2024-04-25 01:10:55 +00:00
renovate 78f03373b8 fix(deps): update dependency vue to v3.4.25
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-04-24 13:09:36 +00:00
renovate 09c6d095df fix(deps): update sentry-javascript monorepo to v7.112.2
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build encountered an error Details
2024-04-24 12:06:21 +00:00
renovate b102fe8188 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is failing Details
continuous-integration/drone/push Build is passing Details
2024-04-24 00:11:24 +00:00
renovate f7c367b5bb fix(deps): update dependency workbox-precaching to v7.1.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-04-23 21:08:15 +00:00
renovate b94053e42e fix(deps): update sentry-javascript monorepo to v7.112.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-04-23 14:06:37 +00:00
renovate 6e98a6d7ff fix(deps): update sentry-javascript monorepo to v7.112.0
continuous-integration/drone/push Build is passing Details
2024-04-23 10:18:38 +00:00
renovate 42bfe107ae chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is failing Details
2024-04-23 06:10:30 +00:00
Frederick [Bot] a1892ea10b chore(i18n): update translations via Crowdin
continuous-integration/drone/push Build is passing Details
2024-04-23 00:07:19 +00:00
renovate 899f8f9bc1 fix(deps): update github.com/dustinkirkland/golang-petname digest to 76c06c4
continuous-integration/drone/push Build is passing Details
2024-04-22 22:11:33 +00:00
renovate 40f0ca6670 fix(deps): update dependency vue-i18n to v9.13.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is failing Details
2024-04-22 17:06:27 +00:00
renovate f8d35396dc fix(deps): update dependency vue to v3.4.24
continuous-integration/drone/push Build is passing Details
2024-04-22 16:36:18 +00:00
kolaente 409822442b
fix(backgrounds): return full project after uploading image
continuous-integration/drone/push Build is failing Details
2024-04-22 18:33:43 +02:00
kolaente aec60f3591
feat(backgrounds): resize images to a maximum of 4K
continuous-integration/drone/push Build is failing Details
Resolves #1373 (comment)
2024-04-22 18:29:58 +02:00
renovate 9b5ae38784 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-04-22 02:08:31 +00:00
renovate 3e40a43d56 chore(deps): update pnpm to v9.0.5
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-04-21 22:07:37 +00:00
kolaente 15e0c716ad
fix(reminders): do not show relative reminders as minutes when they round to hours
continuous-integration/drone/push Build was killed Details
Regression from fd520dab0a
2024-04-22 00:05:12 +02:00
kolaente 26ada628a2
fix(editor): use colors from color scheme to render table cells
continuous-integration/drone/push Build is failing Details
Resolves https://github.com/go-vikunja/vikunja/issues/253
2024-04-21 23:57:07 +02:00
kolaente d86fdcb756
fix(table view): do not sort table column fields when the field in question is hidden
continuous-integration/drone/push Build was killed Details
Resolves #2272
2024-04-21 23:48:40 +02:00
kolaente 84197dd9c1
fix: correctly return error and bubble up when the api could not be reached
continuous-integration/drone/push Build was killed Details
2024-04-21 23:33:50 +02:00
kolaente 324df991ce
chore(desktop): switch from yarn to pnpm
continuous-integration/drone/push Build is passing Details
2024-04-21 21:04:07 +02:00
kolaente 1f6a1f8ad4
fix(kanban): fetch project and view when checking permissions
continuous-integration/drone/push Build is passing Details
2024-04-21 19:44:47 +02:00
kolaente ea7527a3cf
fix(test): cast result before comparing 2024-04-21 19:43:57 +02:00
kolaente 574c7f218e
fix(labels): allow link shares to add existing labels to a task
continuous-integration/drone/push Build is failing Details
Resolves https://github.com/go-vikunja/vikunja/issues/252
2024-04-21 15:12:27 +02:00
kolaente 1074a8d916
fix(views): only allow project admins to manage views
continuous-integration/drone/push Build is failing Details
Resolves https://community.vikunja.io/t/manage-views-only-for-project-admins/2279
2024-04-21 14:36:09 +02:00
kolaente e88f95e501
fix(migration): remove buckets table name when dropping index
continuous-integration/drone/push Build is passing Details
Related to #2243
2024-04-21 13:50:03 +02:00
kolaente 0962aa4262
fix(restore): transform json fields during restore
continuous-integration/drone/push Build is failing Details
Resolves https://community.vikunja.io/t/unable-to-restore-after-dump-and-export-also-not-working/2263/5
2024-04-21 13:45:49 +02:00
renovate a48ad6c9e1 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-04-21 05:09:05 +00:00
96 changed files with 7165 additions and 4033 deletions

View File

@ -156,7 +156,7 @@ mailer:
username: "user"
# SMTP password
password: ""
# Wether to skip verification of the tls certificate on the server
# Whether to skip verification of the tls certificate on the server
skiptlsverify: false
# The default from address when sending emails
fromemail: "mail@vikunja"
@ -306,7 +306,7 @@ auth:
# The provider needs to support the `openid`, `profile` and `email` scopes.<br/>
# **Note:** Some openid providers (like Gitlab) only make the email of the user available through OpenID if they have set it to be publicly visible.
# If the email is not public in those cases, authenticating will fail.
# **Note 2:** The frontend expects the third party to rediect the user <frontend-url>/auth/openid/<auth key> after authentication. Please make sure to configure the redirect url in your third party auth service accordingly if you're using the default vikunja frontend.
# +**Note 2:** The frontend expects the third party to redirect the user <frontend-url>/auth/openid/<auth key> after authentication. Please make sure to configure the redirect url in your third party auth service accordingly if you're using the default vikunja frontend.
# The frontend will automatically provide the API with the redirect url, composed from the current url where it's hosted.
# If you want to use the desktop client with OpenID, make sure to allow redirects to `127.0.0.1`.
# Take a look at the [default config file](https://kolaente.dev/vikunja/vikunja/src/branch/main/config.yml.sample) for more information about how to configure openid authentication.
@ -368,8 +368,8 @@ defaultsettings:
webhooks:
# Whether to enable support for webhooks
enabled: true
# The timout in seconds until a webhook request fails when no response has been received.
timoutseconds: 30
# The timeout in seconds until a webhook request fails when no response has been received.
timeoutseconds: 30
# The URL of [a mole instance](https://github.com/frain-dev/mole) to use to proxy outgoing webhook requests. You should use this and configure appropriately if you're not the only one using your Vikunja instance. More info about why: https://webhooks.fyi/best-practices/webhook-providers#implement-security-on-egress-communication. Must be used in combination with `webhooks.password` (see below).
proxyurl:
# The proxy password to use when authenticating against the proxy.

View File

@ -1,9 +0,0 @@
{ pkgs ? import <nixpkgs> {}
}:
pkgs.mkShell {
name="electron-dev";
buildInputs = [
pkgs.electron
];
}

View File

@ -51,7 +51,7 @@
}
},
"devDependencies": {
"electron": "29.3.1",
"electron": "29.3.3",
"electron-builder": "24.13.3"
},
"dependencies": {

2325
desktop/pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,7 @@ In other packages, use the `db.NewSession()` method to get a new database sessio
To add a new table to the database, create the struct and [add a migration for it]({{< ref "db-migrations.md" >}}).
To learn more about how to configure your struct to create "good" tables, refer to [the xorm documentaion](https://xorm.io/docs/).
To learn more about how to configure your struct to create "good" tables, refer to [the xorm documentation](https://xorm.io/docs/).
In most cases you will also need to implement the `TableName() string` method on the new struct to make sure the table name matches the rest of the tables - plural.

View File

@ -26,6 +26,15 @@ If you plan to do a bigger change, it is better to open an issue for discussion
The main repo is [`vikunja/vikunja`](https://kolaente.dev/vikunja/vikunja), it contains all code for the api, frontend and desktop applications.
## Where to file issues
You can file issues on [the Gitea repo](https://kolaente.dev/vikunja/vikunja) or [on the GitHub mirror](https://github.com/go-vikunja/vikunja), when you don't want to create an account on the Gitea instance.
Please note that due to a spam problem, we need to manually enable accounts on Gitea after you've registered there.
To get that started, please reach out on another channel with your username.
Another option is [the community forum](https://community.vikunja.io), especially if you want to discuss a feature in more detail.
## API
You'll need at least Go 1.21 to build Vikunja's api.

View File

@ -101,7 +101,7 @@ You should also document the routes with [swagger annotations]({{< ref "swagger-
There is a method available in the `migration` package which takes a fully nested Vikunja structure and creates it with all relations.
This means you start by adding a project, then add projects inside that project, then tasks in the lists and so on.
In general, it is reccommended to have one root project with all projects of the other service as child projects.
In general, it is recommended to have one root project with all projects of the other service as child projects.
The root structure must be present as `[]*models.ProjectWithTasksAndBuckets`. It allows to represent all of Vikunja's hierarchy as a single data structure.

View File

@ -16,7 +16,7 @@ menu:
The following parts are about the kinds of tests in the API package and how to run them.
### Prerequesites
### Prerequisites
To run any kind of test, you need to specify Vikunja's [root path](https://vikunja.io/docs/config-options/#rootpath).
This is required to make sure all test fixtures are correctly loaded.
@ -39,7 +39,7 @@ This definition is a bit blurry, but we haven't found a better one yet.
All integration tests live in `pkg/integrations`.
You can run them by executing `mage test:integration`.
The integration tests use the same config and fixtures as the unit tests and therefor have the same options available,
The integration tests use the same config and fixtures as the unit tests and therefore have the same options available,
see at the beginning of this document.
To run integration tests, use `mage test:integration`.

View File

@ -348,6 +348,7 @@ Environment path: `VIKUNJA_SERVICE_CUSTOMLOGOURL`
### enablepublicteams
Enables the public team feature. If enabled, it is possible to configure teams to be public, which makes them
discoverable when sharing a project, therefore not only showing teams the user is member of.
Default: `false`
@ -779,7 +780,7 @@ Environment path: `VIKUNJA_MAILER_PASSWORD`
### skiptlsverify
Wether to skip verification of the tls certificate on the server
Whether to skip verification of the tls certificate on the server
Default: `false`
@ -1222,7 +1223,7 @@ OpenID configuration will allow users to authenticate through a third-party Open
The provider needs to support the `openid`, `profile` and `email` scopes.<br/>
**Note:** Some openid providers (like Gitlab) only make the email of the user available through OpenID if they have set it to be publicly visible.
If the email is not public in those cases, authenticating will fail.
**Note 2:** The frontend expects the third party to rediect the user <frontend-url>/auth/openid/<auth key> after authentication. Please make sure to configure the redirect url in your third party auth service accordingly if you're using the default vikunja frontend.
+**Note 2:** The frontend expects the third party to redirect the user <frontend-url>/auth/openid/<auth key> after authentication. Please make sure to configure the redirect url in your third party auth service accordingly if you're using the default vikunja frontend.
The frontend will automatically provide the API with the redirect url, composed from the current url where it's hosted.
If you want to use the desktop client with OpenID, make sure to allow redirects to `127.0.0.1`.
Take a look at the [default config file](https://kolaente.dev/vikunja/vikunja/src/branch/main/config.yml.sample) for more information about how to configure openid authentication.
@ -1421,15 +1422,15 @@ Full path: `webhooks.enabled`
Environment path: `VIKUNJA_WEBHOOKS_ENABLED`
### timoutseconds
### timeoutseconds
The timout in seconds until a webhook request fails when no response has been received.
The timeout in seconds until a webhook request fails when no response has been received.
Default: `30`
Full path: `webhooks.timoutseconds`
Full path: `webhooks.timeoutseconds`
Environment path: `VIKUNJA_WEBHOOKS_TIMOUTSECONDS`
Environment path: `VIKUNJA_WEBHOOKS_TIMEOUTSECONDS`
### proxyurl

View File

@ -40,7 +40,7 @@ chown 1000 $PWD/files
You'll need to do this before running any of the examples on this page.
Vikunja will not try to aquire ownership of the files folder, as that would mean it had to run as root.
Vikunja will not try to acquire ownership of the files folder, as that would mean it had to run as root.
## PostgreSQL
@ -312,7 +312,7 @@ To do that, you can
* Either activate SSH and paste the adapted compose file in a terminal (using Putty or similar)
* Without activating SSH as a "custom script" (go to Control Panel / Task Scheduler / Create / Scheduled Task / User-defined script)
* Without activating SSH, by using Portainer (you have to install first, check out [this tutorial](https://www.portainer.io/blog/how-to-install-portainer-on-a-synology-nas) for exmple):
* Without activating SSH, by using Portainer (you have to install first, check out [this tutorial](https://www.portainer.io/blog/how-to-install-portainer-on-a-synology-nas) for example):
1. Go to **Dashboard / Stacks** click the button **"Add Stack"**
2. Give it the name Vikunja and paste the adapted docker compose file
3. Deploy the Stack with the "Deploy Stack" button:

View File

@ -37,7 +37,7 @@ This document provides an overview and instructions for the different methods:
* [FreeBSD](#freebsd--freenas)
* [Kubernetes]({{< ref "k8s.md" >}})
And after you installed Vikunja, you may want to check out these other ressources:
And after you installed Vikunja, you may want to check out these other resources:
* [Configuration]({{< ref "config.md">}})
* [UTF-8 Settings]({{< ref "utf-8.md">}})
@ -229,7 +229,7 @@ git clone https://code.vikunja.io/vikunja
cd vikunja
```
**Note:** Ceck out the version you want to build with `git checkout VERSION` - replace `VERSION` with the version want to use.
**Note:** Check out the version you want to build with `git checkout VERSION` - replace `VERSION` with the version want to use.
If you don't do this, you'll build the [latest unstable build]({{< ref "versions.md">}}), which might contain bugs.
### Compile binaries

View File

@ -17,7 +17,7 @@ Vikunja allows for authentication with an external identity source such as Authe
## OpenID Connect Overview
OpenID Connect is a standardized identity layer built on top of the more generic OAuth 2.0 specification, simplying interaction between the involved parties significantly.
OpenID Connect is a standardized identity layer built on top of the more generic OAuth 2.0 specification, simplifying interaction between the involved parties significantly.
While the [OpenID specification](https://openid.net/specs/openid-connect-core-1_0.html#Overview) is worth a read, we summarize the most important basics here.
The involved parties are:

View File

@ -17,7 +17,7 @@ However, you can still run it in a subdirectory but need to build the frontend y
First, make sure you're able to build the frontend from source.
Check [the guide about building from source]({{< ref "build-from-source.md">}}#frontend) about that.
### Dynamicly set with build command
### Dynamically set with build command
Run the build with the `VIKUNJA_FRONTEND_BASE` variable specified.

View File

@ -147,7 +147,7 @@ Flags:
#### `user delete`
Start the user deletion process.
If called without the `--now` flag, this command will only trigger an email to the user in order for them to confirm and start the deletion process (this is the same behavoir as if the user requested their deletion via the web interface).
If called without the `--now` flag, this command will only trigger an email to the user in order for them to confirm and start the deletion process (this is the same behavior as if the user requested their deletion via the web interface).
With the flag the user is deleted **immediately**.
**USE WITH CAUTION.**

View File

@ -51,7 +51,7 @@ This document describes the different errors Vikunja can return.
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 2001 | 400 | ID cannot be empty or 0. |
| 2002 | 400 | Some of the request data was invalid. The response contains an aditional array with all invalid fields. |
| 2002 | 400 | Some of the request data was invalid. The response contains an additional array with all invalid fields. |
## Project

View File

@ -61,7 +61,7 @@ Here are some examples of filter queries:
* `priority = 4`: Matches tasks with priority level 4
* `dueDate < now`: Matches tasks with a due date in the past
* `done = false && priority >= 3`: Matches undone tasks with priority level 3 or higher
* `assignees in [user1, user2]`: Matches tasks assigned to either "user1" or "user2
* `assignees in user1, user2`: Matches tasks assigned to either "user1" or "user2
* `(priority = 1 || priority = 2) && dueDate <= now`: Matches tasks with priority level 1 or 2 and a due date in the past

View File

@ -18,7 +18,7 @@ Starting with version 0.22.0, Vikunja allows you to define webhooks to notify ot
To create a webhook, in the project options select "Webhooks". The form will allow you to create and modify webhooks.
Check out [the api docs](https://try.vikunja.io/api/v1/docs#tag/webhooks) for information about how to create webhooks programatically.
Check out [the api docs](https://try.vikunja.io/api/v1/docs#tag/webhooks) for information about how to create webhooks programmatically.
## Available events and their payload

View File

@ -12,6 +12,8 @@
nodePackages.pnpm cypress
# API tools
go golangci-lint mage
# Desktop
electron
];
};
};

View File

@ -1 +1 @@
20.12.2
20.13.0

View File

@ -68,7 +68,7 @@ describe('Project View List', () => {
cy.get('.project-title-wrapper .icon')
.should('not.exist')
cy.get('input.input[placeholder="Add a new task..."')
cy.get('input.input[placeholder="Add a task..."')
.should('not.exist')
})

View File

@ -27,7 +27,7 @@ describe('Link shares', () => {
cy.get('h1.title')
.should('contain', project.title)
cy.get('input.input[placeholder="Add a new task..."')
cy.get('input.input[placeholder="Add a task..."')
.should('not.exist')
cy.get('.tasks')
.should('contain', tasks[0].title)
@ -42,7 +42,7 @@ describe('Link shares', () => {
cy.get('h1.title')
.should('contain', project.title)
cy.get('input.input[placeholder="Add a new task..."')
cy.get('input.input[placeholder="Add a task..."')
.should('not.exist')
cy.get('.tasks')
.should('contain', tasks[0].title)

View File

@ -14,12 +14,12 @@ describe('Team', () => {
const newTeamName = 'New Team'
cy.get('a.button')
.contains('Create a new team')
.contains('Create a team')
.click()
cy.url()
.should('contain', '/teams/new')
cy.get('.card-header-title')
.contains('Create a new team')
.contains('Create a team')
cy.get('input.input')
.type(newTeamName)
cy.get('.button')

View File

@ -65,7 +65,7 @@ describe('Task', () => {
it('Should be created new', () => {
cy.visit('/projects/1/1')
cy.get('.input[placeholder="Add a new task…"')
cy.get('.input[placeholder="Add a task…"')
.type('New Task')
cy.get('.button')
.contains('Add')
@ -81,7 +81,7 @@ describe('Task', () => {
cy.visit('/projects/1/1')
cy.get('.project-is-empty-notice')
.should('not.exist')
cy.get('.input[placeholder="Add a new task…"')
cy.get('.input[placeholder="Add a task…"')
.type('New Task')
cy.get('.button')
.contains('Add')
@ -640,7 +640,7 @@ describe('Task', () => {
.contains('Set Reminders')
.click()
cy.get('.task-view .columns.details .column button')
.contains('Add a new reminder')
.contains('Add a reminder')
.click()
cy.get('.datepicker__quick-select-date')
.contains('Tomorrow')
@ -665,7 +665,7 @@ describe('Task', () => {
.contains('Set Reminders')
.click()
cy.get('.task-view .columns.details .column button')
.contains('Add a new reminder')
.contains('Add a reminder')
.click()
cy.get('.datepicker__quick-select-date')
.should('not.exist')
@ -694,7 +694,7 @@ describe('Task', () => {
.contains('Set Reminders')
.click()
cy.get('.task-view .columns.details .column button')
.contains('Add a new reminder')
.contains('Add a reminder')
.click()
cy.get('.datepicker__quick-select-date')
.should('not.exist')
@ -723,7 +723,7 @@ describe('Task', () => {
.contains('Set Reminders')
.click()
cy.get('.task-view .columns.details .column button')
.contains('Add a new reminder')
.contains('Add a reminder')
.click()
cy.get('.datepicker__quick-select-date')
.should('not.exist')
@ -759,7 +759,7 @@ describe('Task', () => {
.contains('Set Reminders')
.click()
cy.get('.task-view .columns.details .column button')
.contains('Add a new reminder')
.contains('Add a reminder')
.click()
cy.get('.datepicker__quick-select-date')
.should('not.exist')

View File

@ -13,7 +13,7 @@
},
"homepage": "https://vikunja.io/",
"funding": "https://opencollective.com/vikunja",
"packageManager": "pnpm@9.0.4",
"packageManager": "pnpm@9.1.0",
"keywords": [
"todo",
"productivity",
@ -58,41 +58,41 @@
"@infectoone/vue-ganttastic": "2.3.2",
"@intlify/unplugin-vue-i18n": "4.0.0",
"@kyvg/vue3-notification": "3.2.1",
"@sentry/tracing": "7.111.0",
"@sentry/vue": "7.111.0",
"@tiptap/core": "2.3.0",
"@tiptap/extension-blockquote": "2.3.0",
"@tiptap/extension-bold": "2.3.0",
"@tiptap/extension-bullet-list": "2.3.0",
"@tiptap/extension-code": "2.3.0",
"@tiptap/extension-code-block-lowlight": "2.3.0",
"@tiptap/extension-document": "2.3.0",
"@tiptap/extension-dropcursor": "2.3.0",
"@tiptap/extension-gapcursor": "2.3.0",
"@tiptap/extension-hard-break": "2.3.0",
"@tiptap/extension-heading": "2.3.0",
"@tiptap/extension-history": "2.3.0",
"@tiptap/extension-horizontal-rule": "2.3.0",
"@tiptap/extension-image": "2.3.0",
"@tiptap/extension-italic": "2.3.0",
"@tiptap/extension-link": "2.3.0",
"@tiptap/extension-list-item": "2.3.0",
"@tiptap/extension-ordered-list": "2.3.0",
"@tiptap/extension-paragraph": "2.3.0",
"@tiptap/extension-placeholder": "2.3.0",
"@tiptap/extension-strike": "2.3.0",
"@tiptap/extension-table": "2.3.0",
"@tiptap/extension-table-cell": "2.3.0",
"@tiptap/extension-table-header": "2.3.0",
"@tiptap/extension-table-row": "2.3.0",
"@tiptap/extension-task-item": "2.3.0",
"@tiptap/extension-task-list": "2.3.0",
"@tiptap/extension-text": "2.3.0",
"@tiptap/extension-typography": "2.3.0",
"@tiptap/extension-underline": "2.3.0",
"@tiptap/pm": "2.3.0",
"@tiptap/suggestion": "2.3.0",
"@tiptap/vue-3": "2.3.0",
"@sentry/tracing": "7.114.0",
"@sentry/vue": "7.114.0",
"@tiptap/core": "2.3.2",
"@tiptap/extension-blockquote": "2.3.2",
"@tiptap/extension-bold": "2.3.2",
"@tiptap/extension-bullet-list": "2.3.2",
"@tiptap/extension-code": "2.3.2",
"@tiptap/extension-code-block-lowlight": "2.3.2",
"@tiptap/extension-document": "2.3.2",
"@tiptap/extension-dropcursor": "2.3.2",
"@tiptap/extension-gapcursor": "2.3.2",
"@tiptap/extension-hard-break": "2.3.2",
"@tiptap/extension-heading": "2.3.2",
"@tiptap/extension-history": "2.3.2",
"@tiptap/extension-horizontal-rule": "2.3.2",
"@tiptap/extension-image": "2.3.2",
"@tiptap/extension-italic": "2.3.2",
"@tiptap/extension-link": "2.3.2",
"@tiptap/extension-list-item": "2.3.2",
"@tiptap/extension-ordered-list": "2.3.2",
"@tiptap/extension-paragraph": "2.3.2",
"@tiptap/extension-placeholder": "2.3.2",
"@tiptap/extension-strike": "2.3.2",
"@tiptap/extension-table": "2.3.2",
"@tiptap/extension-table-cell": "2.3.2",
"@tiptap/extension-table-header": "2.3.2",
"@tiptap/extension-table-row": "2.3.2",
"@tiptap/extension-task-item": "2.3.2",
"@tiptap/extension-task-list": "2.3.2",
"@tiptap/extension-text": "2.3.2",
"@tiptap/extension-typography": "2.3.2",
"@tiptap/extension-underline": "2.3.2",
"@tiptap/pm": "2.3.2",
"@tiptap/suggestion": "2.3.2",
"@tiptap/vue-3": "2.3.2",
"@types/is-touch-device": "1.0.2",
"@types/lodash.clonedeep": "4.5.9",
"@vueuse/core": "10.9.0",
@ -102,8 +102,8 @@
"bulma-css-variables": "0.9.33",
"camel-case": "4.1.2",
"date-fns": "3.6.0",
"dayjs": "1.11.10",
"dompurify": "3.1.0",
"dayjs": "1.11.11",
"dompurify": "3.1.2",
"fast-deep-equal": "3.1.3",
"flatpickr": "4.6.13",
"flexsearch": "0.7.31",
@ -118,13 +118,13 @@
"sortablejs": "1.15.2",
"tippy.js": "6.3.7",
"ufo": "1.5.3",
"vue": "3.4.23",
"vue": "3.4.27",
"vue-advanced-cropper": "2.8.8",
"vue-flatpickr-component": "11.0.5",
"vue-i18n": "9.13.0",
"vue-i18n": "9.13.1",
"vue-router": "4.3.2",
"vuemoji-picker": "0.2.1",
"workbox-precaching": "7.0.0",
"workbox-precaching": "7.1.0",
"zhyswan-vuedraggable": "4.1.3"
},
"devDependencies": {
@ -142,46 +142,46 @@
"@types/is-touch-device": "1.0.2",
"@types/lodash.debounce": "4.0.9",
"@types/marked": "5.0.2",
"@types/node": "20.12.7",
"@types/node": "20.12.11",
"@types/postcss-preset-env": "7.7.0",
"@types/sortablejs": "1.15.8",
"@typescript-eslint/eslint-plugin": "7.7.0",
"@typescript-eslint/parser": "7.7.0",
"@vitejs/plugin-legacy": "5.3.2",
"@typescript-eslint/eslint-plugin": "7.8.0",
"@typescript-eslint/parser": "7.8.0",
"@vitejs/plugin-legacy": "5.4.0",
"@vitejs/plugin-vue": "5.0.4",
"@vue/eslint-config-typescript": "13.0.0",
"@vue/test-utils": "2.4.5",
"@vue/test-utils": "2.4.6",
"@vue/tsconfig": "0.5.1",
"autoprefixer": "10.4.19",
"browserslist": "4.23.0",
"caniuse-lite": "1.0.30001611",
"caniuse-lite": "1.0.30001617",
"css-has-pseudo": "6.0.3",
"csstype": "3.1.3",
"cypress": "13.8.0",
"esbuild": "0.20.2",
"cypress": "13.9.0",
"esbuild": "0.21.1",
"eslint": "8.57.0",
"eslint-plugin-vue": "9.25.0",
"happy-dom": "14.7.1",
"eslint-plugin-vue": "9.26.0",
"happy-dom": "14.10.1",
"histoire": "0.17.17",
"postcss": "8.4.38",
"postcss-easing-gradients": "3.0.1",
"postcss-easings": "4.0.0",
"postcss-focus-within": "8.0.1",
"postcss-preset-env": "9.5.6",
"rollup": "4.14.3",
"postcss-preset-env": "9.5.11",
"rollup": "4.17.2",
"rollup-plugin-visualizer": "5.12.0",
"sass": "1.75.0",
"sass": "1.77.0",
"start-server-and-test": "2.0.3",
"typescript": "5.4.5",
"vite": "5.2.9",
"vite": "5.2.11",
"vite-plugin-inject-preload": "1.3.3",
"vite-plugin-pwa": "0.19.8",
"vite-plugin-pwa": "0.20.0",
"vite-plugin-sentry": "1.4.0",
"vite-svg-loader": "5.1.0",
"vitest": "1.5.0",
"vue-tsc": "2.0.13",
"vitest": "1.6.0",
"vue-tsc": "2.0.16",
"wait-on": "7.2.0",
"workbox-cli": "7.0.0"
"workbox-cli": "7.1.0"
},
"pnpm": {
"patchedDependencies": {

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,27 @@
<template>
<div
:class="[background ? 'has-background' : '', $route.name as string +'-view']"
:class="{
'has-background': background,
'link-share-is-fullwidth': isFullWidth,
}"
:style="{'background-image': `url(${background})`}"
class="link-share-container"
>
<div class="container has-text-centered link-share-view">
<div class="column is-10 is-offset-1">
<Logo
v-if="logoVisible"
class="logo"
/>
<h1
:class="{'m-0': !logoVisible}"
:style="{ 'opacity': currentProject?.title === '' ? '0': '1' }"
class="title"
>
{{ currentProject?.title === '' ? $t('misc.loading') : currentProject?.title }}
</h1>
<div class="box has-text-left view">
<router-view />
<PoweredByLink />
</div>
<div class="has-text-centered link-share-view">
<Logo
v-if="logoVisible"
class="logo"
/>
<h1
:class="{'m-0': !logoVisible}"
:style="{ 'opacity': currentProject?.title === '' ? '0': '1' }"
class="title"
>
{{ currentProject?.title === '' ? $t('misc.loading') : currentProject?.title }}
</h1>
<div class="box has-text-left view">
<router-view />
<PoweredByLink />
</div>
</div>
</div>
@ -30,10 +31,13 @@
import {computed} from 'vue'
import {useBaseStore} from '@/stores/base'
import {useRoute} from 'vue-router'
import Logo from '@/components/home/Logo.vue'
import PoweredByLink from './PoweredByLink.vue'
import {useProjectStore} from '@/stores/projects'
import {useLabelStore} from '@/stores/labels'
import {PROJECT_VIEW_KINDS} from '@/modelTypes/IProjectView'
const baseStore = useBaseStore()
const currentProject = computed(() => baseStore.currentProject)
@ -42,6 +46,23 @@ const logoVisible = computed(() => baseStore.logoVisible)
const projectStore = useProjectStore()
projectStore.loadAllProjects()
const labelStore = useLabelStore()
labelStore.loadAllLabels()
const route = useRoute()
const isFullWidth = computed(() => {
const viewId = route.params?.viewId ?? null
const projectId = route.params?.projectId ?? null
if (!viewId || !projectId) {
return false
}
const view = projectStore.projects[Number(projectId)]?.views.find(v => v.id === Number(viewId))
return view?.viewKind === PROJECT_VIEW_KINDS.KANBAN ||
view?.viewKind === PROJECT_VIEW_KINDS.GANTT
})
</script>
<style lang="scss" scoped>
@ -53,20 +74,34 @@ projectStore.loadAllProjects()
.logo {
max-width: 300px;
width: 90%;
margin: 2rem 0 1.5rem;
margin: 1rem auto 2rem;
height: 100px;
}
.column {
max-width: 100%;
}
.title {
text-shadow: 0 0 1rem var(--white);
}
// FIXME: this should be defined somewhere deep
.link-share-view .card {
background-color: var(--white);
.link-share-view {
width: 100%;
max-width: $desktop;
margin: 0 auto;
}
.link-share-container.link-share-is-fullwidth {
.link-share-view {
max-width: 100vw;
}
}
.link-share-container:not(.has-background) {
:deep(.loader-container, .gantt-chart-container > .card) {
box-shadow: none !important;
border: none;
.task-add {
padding: 1rem 0 0;
}
}
}
</style>

View File

@ -818,7 +818,7 @@ watch(
td,
th {
min-width: 1em;
border: 2px solid #ced4da;
border: 2px solid var(--grey-300) !important;
padding: 3px 5px;
vertical-align: top;
box-sizing: border-box;
@ -832,7 +832,7 @@ watch(
th {
font-weight: bold;
text-align: left;
background-color: #f1f3f5;
background-color: var(--grey-200);
}
.selectedCell:after {

View File

@ -57,7 +57,7 @@ const showDocs = ref(false)
<code>done = false &amp;&amp; priority &gt;= 3</code>:
{{ $t('filters.query.help.examples.undoneHighPriority') }}
</li>
<li><code>assignees in [user1, user2]</code>: {{ $t('filters.query.help.examples.assigneesIn') }}</li>
<li><code>assignees in user1, user2</code>: {{ $t('filters.query.help.examples.assigneesIn') }}</li>
<li>
<code>(priority = 1 || priority = 2) &amp;&amp; dueDate &lt;= now</code>:
{{ $t('filters.query.help.examples.priorityOneOrTwoPastDue') }}

View File

@ -30,7 +30,7 @@
>
<icon icon="filter" />
</span>
{{ project.title }}
{{ getProjectTitle(project) }}
</div>
<BaseButton
class="project-button"
@ -59,6 +59,7 @@ import BaseButton from '@/components/base/BaseButton.vue'
import {useProjectBackground} from './useProjectBackground'
import {useProjectStore} from '@/stores/projects'
import {getProjectTitle} from '@/helpers/getProjectTitle'
const {
project,

View File

@ -306,4 +306,12 @@ function prepareFiltersAndLoadTasks() {
}
}
}
.list-view {
padding-bottom: 1rem;
:deep(.card) {
margin-bottom: 0;
}
}
</style>

View File

@ -267,7 +267,7 @@
</template>
<script setup lang="ts">
import {computed, type Ref} from 'vue'
import {computed, type Ref, watch} from 'vue'
import {useStorage} from '@vueuse/core'
@ -337,6 +337,12 @@ Object.assign(params.value, {
filter: '',
})
watch(
() => activeColumns.value,
() => setActiveColumnsSortParam(),
{deep: true},
)
// FIXME: by doing this we can have multiple sort orders
function sort(property: keyof SortBy) {
const order = sortBy.value[property]
@ -347,7 +353,16 @@ function sort(property: keyof SortBy) {
} else {
delete sortBy.value[property]
}
sortByParam.value = sortBy.value
setActiveColumnsSortParam()
}
function setActiveColumnsSortParam() {
sortByParam.value = Object.keys(sortBy.value)
.filter(prop => activeColumns.value[prop])
.reduce((obj, key) => {
obj[key] = sortBy.value[key]
return obj
}, {})
}
// TODO: re-enable opening task detail in modal

View File

@ -323,9 +323,8 @@ async function setCoverImage(attachment: IAttachment | null) {
margin-bottom: 0;
display: flex;
> span:not(:last-child):after,
> span,
> button:not(:last-child):after {
content: '·';
padding: 0 .25rem;
}
}

View File

@ -111,7 +111,7 @@
<icon icon="align-left" />
</span>
<span
v-if="task.repeatAfter.amount > 0"
v-if="task.repeatAfter.amount > 0 || (task.repeatAfter.amount === 0 && task.repeatMode === TASK_REPEAT_MODES.REPEAT_MODE_MONTH)"
class="project-task-icon"
>
<icon icon="history" />
@ -207,6 +207,7 @@ 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,

View File

@ -2,7 +2,7 @@ import {calculateDayInterval} from './calculateDayInterval'
import {calculateNearestHours} from './calculateNearestHours'
import {replaceAll} from '../replaceAll'
interface dateParseResult {
export interface dateParseResult {
newText: string,
date: Date | null,
}
@ -12,7 +12,7 @@ interface dateFoundResult {
date: Date | null,
}
const monthsRegexGroup = '(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)'
const monthsRegexGroup = '(january|february|march|april|june|july|august|september|october|november|december|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)'
function matchesDateExpr(text: string, dateExpr: string): boolean {
return text.match(new RegExp('(^| )' + dateExpr, 'gi')) !== null
@ -60,12 +60,12 @@ export const parseDate = (text: string, now: Date = new Date()): dateParseResult
return addTimeToDate(text, date, 'end of month')
}
let parsed = getDateFromWeekday(text)
let parsed = getDateFromWeekday(text, now)
if (parsed.date !== null) {
return addTimeToDate(text, parsed.date, parsed.foundText)
}
parsed = getDayFromText(text)
parsed = getDayFromText(text, now)
if (parsed.date !== null) {
const month = getMonthFromText(text, parsed.date)
return addTimeToDate(month.newText, month.date, parsed.foundText)
@ -76,7 +76,7 @@ export const parseDate = (text: string, now: Date = new Date()): dateParseResult
return addTimeToDate(text, parsed.date, parsed.foundText)
}
parsed = getDateFromText(text)
parsed = getDateFromText(text, now)
if (parsed.date === null) {
return {
@ -230,7 +230,7 @@ export const getDateFromTextIn = (text: string, now: Date = new Date()) => {
}
}
const getDateFromWeekday = (text: string): dateFoundResult => {
const getDateFromWeekday = (text: string, date: Date = new Date()): dateFoundResult => {
const matcher = /(^| )(next )?(monday|mon|tuesday|tue|wednesday|wed|thursday|thu|friday|fri|saturday|sat|sunday|sun)($| )/g
const results: string[] | null = matcher.exec(text.toLowerCase()) // The i modifier does not seem to work.
if (results === null) {
@ -240,7 +240,6 @@ const getDateFromWeekday = (text: string): dateFoundResult => {
}
}
const date: Date = new Date()
const currentDay: number = date.getDay()
let day = 0
@ -296,7 +295,7 @@ const getDateFromWeekday = (text: string): dateFoundResult => {
}
}
const getDayFromText = (text: string) => {
const getDayFromText = (text: string, now: Date = new Date()) => {
const matcher = /(^| )(([1-2][0-9])|(3[01])|(0?[1-9]))(st|nd|rd|th|\.)($| )/ig
const results = matcher.exec(text)
if (results === null) {
@ -306,7 +305,6 @@ const getDayFromText = (text: string) => {
}
}
const now = new Date()
const date = new Date(now)
const day = parseInt(results[0])
date.setDate(day)

View File

@ -19,16 +19,16 @@ export function secondsToPeriod(seconds: number): { unit: PeriodUnit, amount: nu
}
}
if (seconds % SECONDS_A_MINUTE === 0) {
if (seconds % SECONDS_A_HOUR === 0) {
return {
unit: 'minutes',
amount: seconds / SECONDS_A_MINUTE,
unit: 'hours',
amount: seconds / SECONDS_A_HOUR,
}
}
return {
unit: 'hours',
amount: seconds / SECONDS_A_HOUR,
unit: 'minutes',
amount: seconds / SECONDS_A_MINUTE,
}
}

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "كل 30 يوماً",
"mode": "وضع التكرار",
"monthly": "شهرياً",
"fromCurrentDate": "من التاريخ الحالي",
"fromCurrentDate": "From completion date",
"each": "كل",
"specifyAmount": "حدد قيمة…",
"hours": "الساعات",
@ -1059,7 +1060,7 @@
"duplicate": "تكرار",
"delete": "حذف",
"unarchive": "إلغاء الأرشفة",
"setBackground": "تعيين خلفية",
"setBackground": "Background settings",
"share": "مشاركة",
"newProject": "مشروع جديد",
"createProject": "إنشاء مشروع",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Repeat mode",
"monthly": "Monthly",
"fromCurrentDate": "From Current Date",
"fromCurrentDate": "From completion date",
"each": "Each",
"specifyAmount": "Specify an amount…",
"hours": "Hours",
@ -1059,7 +1060,7 @@
"duplicate": "Duplicate",
"delete": "Delete",
"unarchive": "Un-Archive",
"setBackground": "Set background",
"setBackground": "Background settings",
"share": "Share",
"newProject": "New project",
"createProject": "Create project",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Každých 30 dní",
"mode": "Režim opakování",
"monthly": "Měsíčně",
"fromCurrentDate": "Od aktuálního data",
"fromCurrentDate": "From completion date",
"each": "Každý",
"specifyAmount": "Zadejte množství…",
"hours": "Hodin",
@ -1059,7 +1060,7 @@
"duplicate": "Duplikovat",
"delete": "Smazat",
"unarchive": "Zrušit archivaci",
"setBackground": "Nastavit pozadí",
"setBackground": "Background settings",
"share": "Sdílet",
"newProject": "Nový projekt",
"createProject": "Vytvořit projekt",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Gentagelsestilstand",
"monthly": "Månedligt",
"fromCurrentDate": "Fra Nuværende Dato",
"fromCurrentDate": "From completion date",
"each": "Hver",
"specifyAmount": "Angiv et beløb…",
"hours": "Timer",
@ -1059,7 +1060,7 @@
"duplicate": "Dupliker",
"delete": "Slet",
"unarchive": "Tilbagekald",
"setBackground": "Indstil baggrund",
"setBackground": "Background settings",
"share": "Del",
"newProject": "New project",
"createProject": "Create project",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Nur Projektadministrator:innen können Ansichten bearbeiten."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Alle 30 Tage",
"mode": "Wiederholungsmodus",
"monthly": "Monatlich",
"fromCurrentDate": "Ab dem aktuellen Datum",
"fromCurrentDate": "Ab Fertigstellungsdatum",
"each": "Alle",
"specifyAmount": "Gib einen Anzahl an …",
"hours": "Stunden",
@ -1059,7 +1060,7 @@
"duplicate": "Duplizieren",
"delete": "Löschen",
"unarchive": "Archivierung aufheben",
"setBackground": "Hintergrund einstellen",
"setBackground": "Hintergrundeinstellungen",
"share": "Teilen",
"newProject": "Neues Projekt",
"createProject": "Projekt erstellen",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Nur Projektadministrator:innen können Ansichten bearbeiten."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Alle 30 Tage",
"mode": "Widerholigs Modus",
"monthly": "Monatlich",
"fromCurrentDate": "Vom Hüttige Datum",
"fromCurrentDate": "Ab Fertigstellungsdatum",
"each": "Jedä",
"specifyAmount": "Gib e Ahzahl ah…",
"hours": "Stundä",
@ -1059,7 +1060,7 @@
"duplicate": "Dublizierä",
"delete": "Chüble",
"unarchive": "Ent-archiviere",
"setBackground": "Hintergrund iihstelle",
"setBackground": "Hintergrundeinstellungen",
"share": "Teilä",
"newProject": "Neues Projekt",
"createProject": "Projekt erstellen",

View File

@ -53,7 +53,7 @@
"loginWith": "Log in with {provider}",
"authenticating": "Authenticating…",
"openIdStateError": "State does not match, refusing to continue!",
"openIdGeneralError": "An error occured while authenticating against the third party.",
"openIdGeneralError": "An error occurred while authenticating against the third party.",
"logout": "Logout",
"emailInvalid": "Please enter a valid email address.",
"usernameRequired": "Please provide a username.",
@ -227,8 +227,8 @@
"title": "Archive \"{project}\"",
"archive": "Archive this project",
"unarchive": "Un-Archive this project",
"unarchiveText": "You will be able to create new tasks or edit it.",
"archiveText": "You won't be able to edit this project or create new tasks until you un-archive it.",
"unarchiveText": "You will be able to create tasks or edit it.",
"archiveText": "You won't be able to edit this project or create tasks until you un-archive it.",
"success": "The project was successfully archived."
},
"background": {
@ -277,7 +277,7 @@
"title": "Share Links",
"what": "What is a share link?",
"explanation": "Share Links allow you to easily share a project with other users who don't have an account on Vikunja.",
"create": "Create a new link share",
"create": "Create a link share",
"name": "Name (optional)",
"namePlaceholder": "e.g. Lorem Ipsum",
"nameExplanation": "All actions done by this link share will show up with the name.",
@ -317,9 +317,9 @@
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"addPlaceholder": "Add a task…",
"empty": "This project is currently empty.",
"newTaskCta": "Create a new task.",
"newTaskCta": "Create a task.",
"editTask": "Edit Task"
},
"gantt": {
@ -352,7 +352,7 @@
"addTaskPlaceholder": "Enter the new task title…",
"addTask": "Add a task",
"addAnotherTask": "Add another task",
"addBucket": "Create a new bucket",
"addBucket": "Create a bucket",
"addBucketPlaceholder": "Enter the new bucket title…",
"deleteHeaderBucket": "Delete the bucket",
"deleteBucketText1": "Are you sure you want to delete this bucket?",
@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -422,7 +423,7 @@
"create": {
"title": "New Saved Filter",
"description": "A saved filter is a virtual project which is computed from a set of filters each time it is accessed.",
"action": "Create new saved filter",
"action": "Create saved filter",
"titleRequired": "Please provide a title for the filter."
},
"delete": {
@ -506,7 +507,7 @@
"search": "Type to search for a label…",
"create": {
"header": "New label",
"title": "Create a new label",
"title": "Create a label",
"titleRequired": "Please specify a title.",
"success": "The label was successfully created."
},
@ -527,7 +528,7 @@
"sharing": {
"authenticating": "Authenticating…",
"passwordRequired": "This shared project requires a password. Please enter it below:",
"error": "An error occured.",
"error": "An error occurred.",
"invalidPassword": "The password is invalid."
},
"navigation": {
@ -642,7 +643,7 @@
}
},
"multiselect": {
"createPlaceholder": "Create new",
"createPlaceholder": "Create",
"selectPlaceholder": "Click or press enter to select"
},
"datepickerRange": {
@ -721,10 +722,10 @@
},
"task": {
"task": "Task",
"new": "Create a new task",
"new": "Create a task",
"delete": "Delete this task",
"createSuccess": "The task was successfully created.",
"addReminder": "Add a new reminder…",
"addReminder": "Add a reminder…",
"doneSuccess": "The task was successfully marked as done.",
"undoneSuccess": "The task was successfully un-marked as done.",
"undo": "Undo",
@ -860,7 +861,7 @@
"unassignSuccess": "The user has been unassigned successfully."
},
"label": {
"placeholder": "Type to add a new label…",
"placeholder": "Type to add a label…",
"createPlaceholder": "Add this as new label",
"addSuccess": "The label has been added successfully.",
"createSuccess": "The label has been created successfully.",
@ -883,8 +884,8 @@
"relation": {
"add": "Add a New Task Relation",
"new": "New Task Relation",
"searchPlaceholder": "Type search for a new task to add as related…",
"createPlaceholder": "Add this as new related task",
"searchPlaceholder": "Type search for a task to add as related…",
"createPlaceholder": "Add this as related task",
"differentProject": "This task belongs to a different project.",
"noneYet": "No task relations yet.",
"delete": "Delete Task Relation",
@ -922,7 +923,7 @@
"every30d": "Every 30 Days",
"mode": "Repeat mode",
"monthly": "Monthly",
"fromCurrentDate": "From Current Date",
"fromCurrentDate": "From completion date",
"each": "Each",
"specifyAmount": "Specify an amount…",
"hours": "Hours",
@ -962,7 +963,7 @@
"title": "Teams",
"noTeams": "You are currently not part of any teams.",
"create": {
"title": "Create a new team",
"title": "Create a team",
"success": "The team was successfully created."
},
"edit": {
@ -1060,7 +1061,7 @@
"duplicate": "Duplicate",
"delete": "Delete",
"unarchive": "Un-Archive",
"setBackground": "Set background",
"setBackground": "Background settings",
"share": "Share",
"newProject": "New project",
"createProject": "Create project",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Repeat mode",
"monthly": "Monthly",
"fromCurrentDate": "From Current Date",
"fromCurrentDate": "From completion date",
"each": "Each",
"specifyAmount": "Specify an amount…",
"hours": "Hours",
@ -1059,7 +1060,7 @@
"duplicate": "Duplicate",
"delete": "Delete",
"unarchive": "Un-Archive",
"setBackground": "Set background",
"setBackground": "Background settings",
"share": "Share",
"newProject": "New project",
"createProject": "Create project",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Modo de repetición",
"monthly": "Mensualmente",
"fromCurrentDate": "Desde la Fecha Actual",
"fromCurrentDate": "From completion date",
"each": "Cada",
"specifyAmount": "Especifique una cantidad…",
"hours": "Horas",
@ -1059,7 +1060,7 @@
"duplicate": "Duplicar",
"delete": "Eliminar",
"unarchive": "Desarchivar",
"setBackground": "Establecer fondo",
"setBackground": "Background settings",
"share": "Compartir",
"newProject": "Nuevo proyecto",
"createProject": "Crear proyecto",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Mode de répétition",
"monthly": "Mensuel",
"fromCurrentDate": "À partir de la date actuelle",
"fromCurrentDate": "From completion date",
"each": "Tous ou toutes les",
"specifyAmount": "Indiquer un nombre…",
"hours": "Heures",
@ -1059,7 +1060,7 @@
"duplicate": "Dupliquer",
"delete": "Supprimer",
"unarchive": "Désarchiver",
"setBackground": "Définir larrière-plan",
"setBackground": "Background settings",
"share": "Partager",
"newProject": "Nouveau projet",
"createProject": "Créer un projet",

File diff suppressed because it is too large Load Diff

View File

@ -396,7 +396,8 @@
"titleRequired": "Kérjük, adjon meg egy címet.",
"delete": "Törölje ezt a nézetet",
"deleteText": "Biztosan eltávolítja ezt a nézetet? A továbbiakban nem lesz használható a projektben szereplő feladatok megtekintésére. Ez a művelet nem töröl semmilyen feladatot. Ezt nem lehet visszacsinálni!",
"deleteSuccess": "A nézet sikeresen törölve"
"deleteSuccess": "A nézet sikeresen törölve",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "30 naponta",
"mode": "Ismétlés típusa",
"monthly": "Havi",
"fromCurrentDate": "Aktuális dátumtól",
"fromCurrentDate": "From completion date",
"each": "Minden egyes",
"specifyAmount": "Adja meg a mennyiséget…",
"hours": "Órák",
@ -1059,7 +1060,7 @@
"duplicate": "Duplikálás",
"delete": "Törlés",
"unarchive": "Archiválás visszavonása",
"setBackground": "Háttérkép beállítása",
"setBackground": "Background settings",
"share": "Megosztás",
"newProject": "Új projekt",
"createProject": "Projekt létrehozása",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Modalità Ripetizione",
"monthly": "Mensilmente",
"fromCurrentDate": "Dalla Data Attuale",
"fromCurrentDate": "From completion date",
"each": "Ogni",
"specifyAmount": "Specifica una quantità…",
"hours": "Ore",
@ -1059,7 +1060,7 @@
"duplicate": "Duplica",
"delete": "Elimina",
"unarchive": "Disarchivia",
"setBackground": "Imposta sfondo",
"setBackground": "Background settings",
"share": "Condividi",
"newProject": "New project",
"createProject": "Create project",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "30日ごと",
"mode": "繰り返しモード",
"monthly": "毎月",
"fromCurrentDate": "現在時刻からの間隔",
"fromCurrentDate": "From completion date",
"each": "隔",
"specifyAmount": "数字を入力…",
"hours": "時間ごと",
@ -1059,7 +1060,7 @@
"duplicate": "複製",
"delete": "削除",
"unarchive": "アーカイブの取り消し",
"setBackground": "背景画像の設定",
"setBackground": "Background settings",
"share": "共有",
"newProject": "新しいプロジェクトの作成",
"createProject": "プロジェクトの作成",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Repeat mode",
"monthly": "Monthly",
"fromCurrentDate": "From Current Date",
"fromCurrentDate": "From completion date",
"each": "Each",
"specifyAmount": "Specify an amount…",
"hours": "Hours",
@ -1059,7 +1060,7 @@
"duplicate": "Duplicate",
"delete": "Delete",
"unarchive": "Un-Archive",
"setBackground": "Set background",
"setBackground": "Background settings",
"share": "Share",
"newProject": "New project",
"createProject": "Create project",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Herhaalmodus",
"monthly": "Maandelijks",
"fromCurrentDate": "Vanaf huidige datum",
"fromCurrentDate": "From completion date",
"each": "Each",
"specifyAmount": "Specify an amount…",
"hours": "Uren",
@ -1059,7 +1060,7 @@
"duplicate": "Dupliceer",
"delete": "Verwijderen",
"unarchive": "Archivering opheffen",
"setBackground": "Achtergrond instellen",
"setBackground": "Background settings",
"share": "Delen",
"newProject": "New project",
"createProject": "Create project",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Repeter modus",
"monthly": "Månedlig",
"fromCurrentDate": "Fra gjeldende dato",
"fromCurrentDate": "From completion date",
"each": "Hver",
"specifyAmount": "Angi beløp…",
"hours": "Timer",
@ -1059,7 +1060,7 @@
"duplicate": "Dupliser",
"delete": "Slett",
"unarchive": "Av-arkiver",
"setBackground": "Bruk som bakgrunn",
"setBackground": "Background settings",
"share": "Del",
"newProject": "Nytt prosjekt",
"createProject": "Create project",

View File

@ -396,7 +396,8 @@
"titleRequired": "Proszę podać tytuł.",
"delete": "Usuń ten widok",
"deleteText": "Czy na pewno chcesz usunąć ten widok? Nie będzie już możliwe wyświetlanie zadań w tym projekcie. Ta akcja nie usunie żadnych zadań. Tej operacji nie można cofnąć!",
"deleteSuccess": "Widok został pomyślnie usunięty"
"deleteSuccess": "Widok został pomyślnie usunięty",
"onlyAdminsCanEdit": "Tylko administratorzy projektu mogą edytować widoki."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Co 30 dni",
"mode": "Tryb powtarzania",
"monthly": "Miesięczny",
"fromCurrentDate": "Od bieżącej daty",
"fromCurrentDate": "From completion date",
"each": "Co",
"specifyAmount": "Określ ilość…",
"hours": "Godziny",
@ -1059,7 +1060,7 @@
"duplicate": "Duplikuj",
"delete": "Usuń",
"unarchive": "Cofnij archiwizację",
"setBackground": "Ustaw tło",
"setBackground": "Background settings",
"share": "Udostępnij",
"newProject": "Nowy projekt",
"createProject": "Utwórz projekt",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "A cada 30 dias",
"mode": "Modo repetição",
"monthly": "Mensalmente",
"fromCurrentDate": "Data atual",
"fromCurrentDate": "From completion date",
"each": "Cada",
"specifyAmount": "Especifique uma quantidade…",
"hours": "Horas",
@ -1059,7 +1060,7 @@
"duplicate": "Duplicar",
"delete": "Excluir",
"unarchive": "Desarquivar",
"setBackground": "Definir plano de fundo",
"setBackground": "Background settings",
"share": "Compartilhar",
"newProject": "Novo projeto",
"createProject": "Criar projeto",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "A cada 30 Dias",
"mode": "Modo de repetição",
"monthly": "Mensal",
"fromCurrentDate": "Da Data Atual",
"fromCurrentDate": "From completion date",
"each": "Cada",
"specifyAmount": "Especifica uma quantidade…",
"hours": "Horas",
@ -1059,7 +1060,7 @@
"duplicate": "Duplicar",
"delete": "Eliminar",
"unarchive": "Desarquivar",
"setBackground": "Definir Fundo",
"setBackground": "Background settings",
"share": "Partilhar",
"newProject": "Novo projeto",
"createProject": "Criar projeto",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Repeat mode",
"monthly": "Monthly",
"fromCurrentDate": "From Current Date",
"fromCurrentDate": "From completion date",
"each": "Each",
"specifyAmount": "Specify an amount…",
"hours": "Hours",
@ -1059,7 +1060,7 @@
"duplicate": "Duplicate",
"delete": "Delete",
"unarchive": "Un-Archive",
"setBackground": "Set background",
"setBackground": "Background settings",
"share": "Share",
"newProject": "New project",
"createProject": "Create project",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Каждые 30 дней",
"mode": "Режим повтора",
"monthly": "Ежемесячно",
"fromCurrentDate": "От сегодняшей даты",
"fromCurrentDate": "From completion date",
"each": "Каждые",
"specifyAmount": "Укажите количество…",
"hours": "Часов",
@ -1059,7 +1060,7 @@
"duplicate": "Создать копию",
"delete": "Удалить",
"unarchive": "Вернуть из архива",
"setBackground": "Задать фон",
"setBackground": "Background settings",
"share": "Поделиться",
"newProject": "Создать проект",
"createProject": "Создать проект",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Repeat mode",
"monthly": "Monthly",
"fromCurrentDate": "From Current Date",
"fromCurrentDate": "From completion date",
"each": "Each",
"specifyAmount": "Specify an amount…",
"hours": "Hours",
@ -1059,7 +1060,7 @@
"duplicate": "Duplicate",
"delete": "Delete",
"unarchive": "Un-Archive",
"setBackground": "Set background",
"setBackground": "Background settings",
"share": "Share",
"newProject": "New project",
"createProject": "Create project",

View File

@ -396,7 +396,8 @@
"titleRequired": "Prosim navedite naslov.",
"delete": "Izbriši pogled",
"deleteText": "Ali ste prepričani, da želite odstraniti ta pogled? Ne bo ga več mogoče uporabljati za ogled nalog v tem projektu. To dejanje ne bo izbrisalo nobenih opravil. Tega ni mogoče razveljaviti!",
"deleteSuccess": "Pogled je bil uspešno izbrisan"
"deleteSuccess": "Pogled je bil uspešno izbrisan",
"onlyAdminsCanEdit": "Samo skrbniki projekta lahko urejajo poglede."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Vsakih 30 dni",
"mode": "Način ponavljanja",
"monthly": "Mesečno",
"fromCurrentDate": "Od trenutnega datuma",
"fromCurrentDate": "Od datuma dokončanja",
"each": "Vsak",
"specifyAmount": "Določi znesek…",
"hours": "Ur",
@ -1059,7 +1060,7 @@
"duplicate": "Podvoji",
"delete": "Izbriši",
"unarchive": "Odstrani iz arhiva",
"setBackground": "Nastavi ozadje",
"setBackground": "Nastavitve ozadja",
"share": "Skupna raba",
"newProject": "Nov projekt",
"createProject": "Ustvari projekt",

View File

@ -53,7 +53,7 @@
"loginWith": "Log in with {provider}",
"authenticating": "Authenticating…",
"openIdStateError": "State does not match, refusing to continue!",
"openIdGeneralError": "An error occured while authenticating against the third party.",
"openIdGeneralError": "An error occurred while authenticating against the third party.",
"logout": "Logout",
"emailInvalid": "Please enter a valid email address.",
"usernameRequired": "Please provide a username.",
@ -227,8 +227,8 @@
"title": "Archive \"{project}\"",
"archive": "Archive this project",
"unarchive": "Un-Archive this project",
"unarchiveText": "You will be able to create new tasks or edit it.",
"archiveText": "You won't be able to edit this project or create new tasks until you un-archive it.",
"unarchiveText": "You will be able to create tasks or edit it.",
"archiveText": "You won't be able to edit this project or create tasks until you un-archive it.",
"success": "The project was successfully archived."
},
"background": {
@ -277,7 +277,7 @@
"title": "Share Links",
"what": "What is a share link?",
"explanation": "Share Links allow you to easily share a project with other users who don't have an account on Vikunja.",
"create": "Create a new link share",
"create": "Create a link share",
"name": "Name (optional)",
"namePlaceholder": "e.g. Lorem Ipsum",
"nameExplanation": "All actions done by this link share will show up with the name.",
@ -317,9 +317,9 @@
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"addPlaceholder": "Add a task…",
"empty": "This project is currently empty.",
"newTaskCta": "Create a new task.",
"newTaskCta": "Create a task.",
"editTask": "Edit Task"
},
"gantt": {
@ -352,7 +352,7 @@
"addTaskPlaceholder": "Enter the new task title…",
"addTask": "Add a task",
"addAnotherTask": "Add another task",
"addBucket": "Create a new bucket",
"addBucket": "Create a bucket",
"addBucketPlaceholder": "Enter the new bucket title…",
"deleteHeaderBucket": "Delete the bucket",
"deleteBucketText1": "Are you sure you want to delete this bucket?",
@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -422,7 +423,7 @@
"create": {
"title": "New Saved Filter",
"description": "A saved filter is a virtual project which is computed from a set of filters each time it is accessed.",
"action": "Create new saved filter",
"action": "Create saved filter",
"titleRequired": "Please provide a title for the filter."
},
"delete": {
@ -506,7 +507,7 @@
"search": "Type to search for a label…",
"create": {
"header": "New label",
"title": "Create a new label",
"title": "Create a label",
"titleRequired": "Please specify a title.",
"success": "The label was successfully created."
},
@ -527,7 +528,7 @@
"sharing": {
"authenticating": "Authenticating…",
"passwordRequired": "This shared project requires a password. Please enter it below:",
"error": "An error occured.",
"error": "An error occurred.",
"invalidPassword": "The password is invalid."
},
"navigation": {
@ -641,7 +642,7 @@
"placeholder": "Type some text or hit '/' to see more options…"
},
"multiselect": {
"createPlaceholder": "Create new",
"createPlaceholder": "Create",
"selectPlaceholder": "Click or press enter to select"
},
"datepickerRange": {
@ -720,10 +721,10 @@
},
"task": {
"task": "Task",
"new": "Create a new task",
"new": "Create a task",
"delete": "Delete this task",
"createSuccess": "The task was successfully created.",
"addReminder": "Add a new reminder…",
"addReminder": "Add a reminder…",
"doneSuccess": "The task was successfully marked as done.",
"undoneSuccess": "The task was successfully un-marked as done.",
"undo": "Undo",
@ -859,7 +860,7 @@
"unassignSuccess": "The user has been unassigned successfully."
},
"label": {
"placeholder": "Type to add a new label…",
"placeholder": "Type to add a label…",
"createPlaceholder": "Add this as new label",
"addSuccess": "The label has been added successfully.",
"createSuccess": "The label has been created successfully.",
@ -882,8 +883,8 @@
"relation": {
"add": "Add a New Task Relation",
"new": "New Task Relation",
"searchPlaceholder": "Type search for a new task to add as related…",
"createPlaceholder": "Add this as new related task",
"searchPlaceholder": "Type search for a task to add as related…",
"createPlaceholder": "Add this as related task",
"differentProject": "This task belongs to a different project.",
"noneYet": "No task relations yet.",
"delete": "Delete Task Relation",
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Repeat mode",
"monthly": "Monthly",
"fromCurrentDate": "From Current Date",
"fromCurrentDate": "From completion date",
"each": "Each",
"specifyAmount": "Specify an amount…",
"hours": "Hours",
@ -961,7 +962,7 @@
"title": "Teams",
"noTeams": "You are currently not part of any teams.",
"create": {
"title": "Create a new team",
"title": "Create a team",
"success": "The team was successfully created."
},
"edit": {
@ -1059,7 +1060,7 @@
"duplicate": "Duplicate",
"delete": "Delete",
"unarchive": "Un-Archive",
"setBackground": "Set background",
"setBackground": "Background settings",
"share": "Share",
"newProject": "New project",
"createProject": "Create project",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Repeat mode",
"monthly": "Monthly",
"fromCurrentDate": "From Current Date",
"fromCurrentDate": "From completion date",
"each": "Each",
"specifyAmount": "Specify an amount…",
"hours": "Timmar",
@ -1059,7 +1060,7 @@
"duplicate": "Duplicera",
"delete": "Radera",
"unarchive": "Un-Archive",
"setBackground": "Set background",
"setBackground": "Background settings",
"share": "Dela",
"newProject": "Nytt projekt",
"createProject": "Skapa projekt",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Repeat mode",
"monthly": "Monthly",
"fromCurrentDate": "From Current Date",
"fromCurrentDate": "From completion date",
"each": "Each",
"specifyAmount": "Specify an amount…",
"hours": "Hours",
@ -1059,7 +1060,7 @@
"duplicate": "Duplicate",
"delete": "Delete",
"unarchive": "Un-Archive",
"setBackground": "Set background",
"setBackground": "Background settings",
"share": "Share",
"newProject": "New project",
"createProject": "Create project",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Chế độ lặp lại",
"monthly": "Hàng tháng",
"fromCurrentDate": "Từ ngày hiện tại",
"fromCurrentDate": "From completion date",
"each": "Mỗi",
"specifyAmount": "Chỉ định một số lượng…",
"hours": "Giờ",
@ -1059,7 +1060,7 @@
"duplicate": "Nhân bản",
"delete": "Xóa",
"unarchive": "Bỏ lưu trữ",
"setBackground": "Cài hình nền",
"setBackground": "Background settings",
"share": "Chia sẻ",
"newProject": "New project",
"createProject": "Create project",

View File

@ -396,7 +396,8 @@
"titleRequired": "请提供标题。",
"delete": "删除此视图",
"deleteText": "您确定要删除此视图吗?它将不再可能使用它来查看此项目中的任务。 此操作不会删除任何任务。此操作不能撤销!",
"deleteSuccess": "视图已成功删除"
"deleteSuccess": "视图已成功删除",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "每 30 天",
"mode": "重复模式",
"monthly": "每月",
"fromCurrentDate": "从当前日期",
"fromCurrentDate": "From completion date",
"each": "每个",
"specifyAmount": "指定数量…",
"hours": "小时",
@ -1059,7 +1060,7 @@
"duplicate": "复制",
"delete": "删除",
"unarchive": "取消存档",
"setBackground": "设置背景",
"setBackground": "Background settings",
"share": "共享",
"newProject": "新项目",
"createProject": "创建项目",

View File

@ -396,7 +396,8 @@
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"deleteSuccess": "The view was successfully deleted",
"onlyAdminsCanEdit": "Only project admins can edit views."
}
},
"filters": {
@ -921,7 +922,7 @@
"every30d": "Every 30 Days",
"mode": "Repeat mode",
"monthly": "Monthly",
"fromCurrentDate": "From Current Date",
"fromCurrentDate": "From completion date",
"each": "Each",
"specifyAmount": "Specify an amount…",
"hours": "Hours",
@ -1059,7 +1060,7 @@
"duplicate": "Duplicate",
"delete": "Delete",
"unarchive": "Un-Archive",
"setBackground": "Set background",
"setBackground": "Background settings",
"share": "Share",
"newProject": "New project",
"createProject": "Create project",

View File

@ -1,8 +1,13 @@
import type {IAbstract} from './IAbstract'
import type {IProject} from '@/modelTypes/IProject'
export const PROJECT_VIEW_KINDS = ['list', 'gantt', 'table', 'kanban']
export type ProjectViewKind = typeof PROJECT_VIEW_KINDS[number]
export const PROJECT_VIEW_KINDS = {
LIST: 'list',
GANTT: 'gantt',
TABLE: 'table',
KANBAN: 'kanban',
} as const
export type ProjectViewKind = typeof PROJECT_VIEW_KINDS[keyof typeof PROJECT_VIEW_KINDS]
export const PROJECT_VIEW_BUCKET_CONFIGURATION_MODES = ['none', 'manual', 'filter']
export type ProjectViewBucketConfigurationMode = typeof PROJECT_VIEW_BUCKET_CONFIGURATION_MODES[number]

View File

@ -22,6 +22,6 @@ export interface IUserSettings extends IAbstract {
defaultProjectId: undefined | IProject['id']
weekStart: 0 | 1 | 2 | 3 | 4 | 5 | 6
timezone: string
language: SupportedLocale
language: SupportedLocale | null
frontendSettings: IFrontendSettings
}

View File

@ -1,7 +1,7 @@
import {beforeEach, afterEach, describe, it, expect, vi} from 'vitest'
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
import {parseTaskText, PrefixMode} from './parseTaskText'
import {getDateFromText, parseDate} from '../helpers/time/parseDate'
import {ParsedTaskText, parseTaskText, PrefixMode} from './parseTaskText'
import {parseDate} from '../helpers/time/parseDate'
import {calculateDayInterval} from '../helpers/time/calculateDayInterval'
import {PRIORITIES} from '@/constants/priorities'
import {MILLISECONDS_A_DAY} from '@/constants/date'
@ -461,50 +461,69 @@ describe('Parse Task Text', () => {
'1/27': '2022-1-27',
'jan 27': '2022-1-27',
'Jan 27': '2022-1-27',
'january 27': '2022-1-27',
'January 27': '2022-1-27',
'feb 21': '2022-2-21',
'Feb 21': '2022-2-21',
'february 21': '2022-2-21',
'February 21': '2022-2-21',
'mar 21': '2022-3-21',
'Mar 21': '2022-3-21',
'march 21': '2022-3-21',
'March 21': '2022-3-21',
'apr 21': '2022-4-21',
'Apr 21': '2022-4-21',
'april 21': '2022-4-21',
'April 21': '2022-4-21',
'may 21': '2022-5-21',
'May 21': '2022-5-21',
'jun 21': '2022-6-21',
'Jun 21': '2022-6-21',
'june 21': '2022-6-21',
'June 21': '2022-6-21',
'21st June': '2021-6-21',
'jul 21': '2021-7-21',
'Jul 21': '2021-7-21',
'july 21': '2021-7-21',
'July 21': '2021-7-21',
'aug 21': '2021-8-21',
'Aug 21': '2021-8-21',
'august 21': '2021-8-21',
'August 21': '2021-8-21',
'sep 21': '2021-9-21',
'Sep 21': '2021-9-21',
'september 21': '2021-9-21',
'September 21': '2021-9-21',
'oct 21': '2021-10-21',
'Oct 21': '2021-10-21',
'october 21': '2021-10-21',
'October 21': '2021-10-21',
'nov 21': '2021-11-21',
'Nov 21': '2021-11-21',
'november 21': '2021-11-21',
'November 21': '2021-11-21',
'dec 21': '2021-12-21',
'Dec 21': '2021-12-21',
'december 21': '2021-12-21',
'December 21': '2021-12-21',
} as Record<string, string | null>
for (const c in cases) {
it(`should parse '${c}' as '${cases[c]}' with the date at the end`, () => {
const {date, foundText} = getDateFromText(`Lorem Ipsum ${c}`, now)
const assertResult = ({date, text}: ParsedTaskText) => {
if (date === null && cases[c] === null) {
expect(date).toBeNull()
return
}
expect(`${date?.getFullYear()}-${date?.getMonth() + 1}-${date?.getDate()}`).toBe(cases[c])
expect(foundText.trim()).toBe(c)
expect(text.trim()).toBe('Lorem Ipsum')
}
it(`should parse '${c}' as '${cases[c]}' with the date at the end`, () => {
assertResult(parseTaskText(`Lorem Ipsum ${c}`, PrefixMode.Default, now))
})
it(`should parse '${c}' as '${cases[c]}' with the date at the beginning`, () => {
const {date, foundText} = getDateFromText(`${c} Lorem Ipsum`, now)
if (date === null && cases[c] === null) {
expect(date).toBeNull()
return
}
expect(`${date?.getFullYear()}-${date?.getMonth() + 1}-${date?.getDate()}`).toBe(cases[c])
expect(foundText.trim()).toBe(c)
assertResult(parseTaskText(`${c} Lorem Ipsum`, PrefixMode.Default, now))
})
}
})

View File

@ -55,7 +55,7 @@ interface Prefixes {
*
* @param text
*/
export const parseTaskText = (text: string, prefixesMode: PrefixMode = PrefixMode.Default): ParsedTaskText => {
export const parseTaskText = (text: string, prefixesMode: PrefixMode = PrefixMode.Default, now: Date = new Date()): ParsedTaskText => {
const result: ParsedTaskText = {
text: text,
date: null,
@ -86,7 +86,7 @@ export const parseTaskText = (text: string, prefixesMode: PrefixMode = PrefixMod
result.text = textWithoutMatched
result.repeats = repeats
const {newText, date} = parseDate(result.text)
const {newText, date} = parseDate(result.text, now)
result.text = newText
result.date = date

View File

@ -52,12 +52,16 @@ export default class ProjectService extends AbstractService<IProject> {
return window.URL.createObjectURL(new Blob([response.data]))
}
async removeBackground(project: Pick<IProject, 'id'>) {
async removeBackground(project: IProject) {
const cancel = this.setLoading()
try {
const response = await this.http.delete(`/projects/${project.id}/background`, project)
return response.data
await this.http.delete(`/projects/${project.id}/background`)
return {
...project,
backgroundInformation: null,
backgroundBlurHash: '',
}
} finally {
cancel()
}

View File

@ -37,6 +37,8 @@ function redirectToProviderIfNothingElseIsEnabled() {
}
export const useAuthStore = defineStore('auth', () => {
const configStore = useConfigStore()
const authenticated = ref(false)
const isLinkShareAuth = ref(false)
const needsTotpPasscode = ref(false)
@ -185,8 +187,7 @@ export const useAuthStore = defineStore('auth', () => {
const HTTP = HTTPFactory()
setIsLoading(true)
const {auth} = useConfigStore()
const fullProvider: IProvider = auth.openidConnect.providers.find((p: IProvider) => p.key === provider)
const fullProvider: IProvider = configStore.auth.openidConnect.providers.find((p: IProvider) => p.key === provider)
const data = {
code: code,
@ -357,8 +358,15 @@ export const useAuthStore = defineStore('auth', () => {
const cancel = setModuleLoading(setIsLoadingGeneralSettings)
try {
const updateSettingsPromise = userSettingsService.update(settings)
setUserSettings({...settings})
let settingsUpdate = {...settings}
if (configStore.demoModeEnabled) {
settingsUpdate = {
...settingsUpdate,
language: null,
}
}
const updateSettingsPromise = userSettingsService.update(settingsUpdate)
setUserSettings(settingsUpdate)
await setLanguage(settings.language)
await updateSettingsPromise
if (showMessage) {
@ -403,13 +411,12 @@ export const useAuthStore = defineStore('auth', () => {
await checkAuth()
// if configured, redirect to OIDC Provider on logout
const {auth} = useConfigStore()
if (
auth.local.enabled === false &&
auth.openidConnect.enabled &&
auth.openidConnect.providers?.length === 1)
configStore.auth.local.enabled === false &&
configStore.auth.openidConnect.enabled &&
configStore.auth.openidConnect.providers?.length === 1)
{
redirectToProviderOnLogout(auth.openidConnect.providers[0])
redirectToProviderOnLogout(configStore.auth.openidConnect.providers[0])
}
}

View File

@ -1,5 +1,5 @@
import {computed, reactive, toRefs} from 'vue'
import {defineStore, acceptHMRUpdate} from 'pinia'
import {acceptHMRUpdate, defineStore} from 'pinia'
import {parseURL} from 'ufo'
import {HTTPFactory} from '@/helpers/fetcher'
@ -7,6 +7,7 @@ import {objectToCamelCase} from '@/helpers/case'
import type {IProvider} from '@/types/IProvider'
import type {MIGRATORS} from '@/views/migrate/migrators'
import {InvalidApiUrlProvidedError} from '@/helpers/checkAndSetApiUrl'
export interface ConfigState {
version: string,
@ -83,15 +84,17 @@ export const useConfigStore = defineStore('config', () => {
function setConfig(config: ConfigState) {
Object.assign(state, config)
}
async function update(): Promise<boolean> {
const HTTP = HTTPFactory()
const {data: config} = await HTTP.get('info')
if (typeof config.version === 'undefined') {
return false
throw new InvalidApiUrlProvidedError()
}
setConfig(objectToCamelCase(config))
const success = !!config
return success
return !!config
}
return {

View File

@ -160,7 +160,6 @@ export const useLabelStore = defineStore('label', () => {
deleteLabel,
updateLabel,
createLabel,
}
})

View File

@ -14,33 +14,4 @@
border-radius: $radius !important;
}
}
}
.link-share-container {
&.project\.gantt-view,
&.project\.kanban-view {
.container {
max-width: 100vw;
.column {
width: 100%;
margin: 0;
}
}
}
}
.link-share-container:not(.has-background) {
.list-view {
max-width: 100%;
}
.loader-container, .gantt-chart-container > .card {
box-shadow: none !important;
border: none;
.task-add {
padding: 1rem 0 0;
}
}
}

View File

@ -162,7 +162,7 @@ const configStore = useConfigStore()
const unsplashBackgroundEnabled = computed(() => configStore.enabledBackgroundProviders.includes('unsplash'))
const uploadBackgroundEnabled = computed(() => configStore.enabledBackgroundProviders.includes('upload'))
const currentProject = computed(() => baseStore.currentProject)
const hasBackground = computed(() => baseStore.background !== null)
const hasBackground = computed(() => !!currentProject.value.backgroundInformation)
// Show the default collection of backgrounds
newBackgroundSearch()

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import CreateEdit from '@/components/misc/create-edit.vue'
import {computed, ref} from 'vue'
import {watch, ref, computed} from 'vue'
import {useProjectStore} from '@/stores/projects'
import ProjectViewModel from '@/models/projectView'
import type {IProjectView} from '@/modelTypes/IProjectView'
@ -9,6 +9,10 @@ import ProjectViewService from '@/services/projectViews'
import XButton from '@/components/input/button.vue'
import {error, success} from '@/message'
import {useI18n} from 'vue-i18n'
import ProjectService from '@/services/project'
import {RIGHTS} from '@/constants/rights'
import ProjectModel from '@/models/project'
import Message from '@/components/misc/message.vue'
const {
projectId,
@ -28,6 +32,17 @@ const viewIdToDelete = ref<number | null>(null)
const showDeleteModal = ref(false)
const viewToEdit = ref<IProjectView | null>(null)
const isAdmin = ref<boolean>(false)
watch(
() => projectId,
async () => {
const projectService = new ProjectService()
const project = await projectService.get(new ProjectModel({id: projectId}))
isAdmin.value = project.maxRight === RIGHTS.ADMIN
},
{immediate: true},
)
async function createView() {
if (!showCreateForm.value) {
showCreateForm.value = true
@ -83,13 +98,17 @@ async function saveView() {
<CreateEdit
:title="$t('project.views.header')"
:primary-label="$t('misc.save')"
:has-primary-action="false"
>
<ViewEditForm
v-if="showCreateForm"
v-model="newView"
class="mb-4"
/>
<div class="is-flex is-justify-content-end mb-4">
<div
v-if="isAdmin"
class="is-flex is-justify-content-end mb-4"
>
<XButton
:loading="projectViewService.loading"
@click="createView"
@ -97,6 +116,10 @@ async function saveView() {
{{ $t('project.views.create') }}
</XButton>
</div>
<Message v-if="!isAdmin">
{{ $t('project.views.onlyAdminsCanEdit') }}
</Message>
<table
v-if="views?.length > 0"
@ -144,6 +167,7 @@ async function saveView() {
<td>{{ v.viewKind }}</td>
<td class="has-text-right">
<XButton
v-if="isAdmin"
class="is-danger mr-2"
icon="trash-alt"
@click="() => {
@ -152,6 +176,7 @@ async function saveView() {
}"
/>
<XButton
v-if="isAdmin"
icon="pen"
@click="viewToEdit = {...v}"
/>

View File

@ -309,6 +309,7 @@
v-model="task.labels"
:disabled="!canWrite"
:task-id="taskId"
:creatable="!authStore.isLinkShareAuth"
/>
</div>
@ -653,6 +654,7 @@ const projectStore = useProjectStore()
const attachmentStore = useAttachmentStore()
const taskStore = useTaskStore()
const kanbanStore = useKanbanStore()
const authStore = useAuthStore()
const task = ref<ITask>(new TaskModel())
const taskTitle = computed(() => task.value.title)
@ -877,7 +879,7 @@ function toggleTaskDone() {
done: !task.value.done,
}
if (newTask.done && useAuthStore().settings.frontendSettings.playSoundWhenDone) {
if (newTask.done && authStore.settings.frontendSettings.playSoundWhenDone) {
playPopSound()
}

18
go.mod
View File

@ -29,7 +29,7 @@ require (
github.com/cweill/gotests v1.6.0
github.com/d4l3k/messagediff v1.2.1
github.com/disintegration/imaging v1.6.2
github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2
github.com/dustinkirkland/golang-petname v0.0.0-20240422154211-76c06c4bde6b
github.com/gabriel-vasile/mimetype v1.4.3
github.com/ganigeorgiev/fexpr v0.4.0
github.com/getsentry/sentry-go v0.27.0
@ -68,13 +68,13 @@ require (
github.com/ulule/limiter/v3 v3.11.2
github.com/wneessen/go-mail v0.4.0
github.com/yuin/goldmark v1.7.1
golang.org/x/crypto v0.22.0
golang.org/x/image v0.15.0
golang.org/x/oauth2 v0.19.0
golang.org/x/crypto v0.23.0
golang.org/x/image v0.16.0
golang.org/x/oauth2 v0.20.0
golang.org/x/sync v0.7.0
golang.org/x/sys v0.19.0
golang.org/x/term v0.19.0
golang.org/x/text v0.14.0
golang.org/x/sys v0.20.0
golang.org/x/term v0.20.0
golang.org/x/text v0.15.0
gopkg.in/d4l3k/messagediff.v1 v1.2.1
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/xurls/v2 v2.5.0
@ -120,7 +120,6 @@ require (
github.com/go-playground/validator/v10 v10.15.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
@ -181,7 +180,6 @@ require (
golang.org/x/net v0.24.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
@ -192,4 +190,4 @@ replace github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20
go 1.21
toolchain go1.21.2
toolchain go1.22.3

25
go.sum
View File

@ -103,6 +103,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2 h1:S6Dco8FtAhEI/qkg/00H6RdEGC+MCy5GPiQ+xweNRFE=
github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2/go.mod h1:8AuBTZBRSFqEYBPYULd+NN474/zZBLP+6WeT5S9xlAc=
github.com/dustinkirkland/golang-petname v0.0.0-20240422154211-76c06c4bde6b h1:+0Xqob+onh+4l9TSWmFyZ4JHqGUiCy5P1muyH8Evfpw=
github.com/dustinkirkland/golang-petname v0.0.0-20240422154211-76c06c4bde6b/go.mod h1:8AuBTZBRSFqEYBPYULd+NN474/zZBLP+6WeT5S9xlAc=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@ -189,8 +191,6 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
@ -318,8 +318,6 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo-jwt/v4 v4.2.0 h1:odSISV9JgcSCuhgQSV/6Io3i7nUmfM/QkBeR5GVJj5c=
github.com/labstack/echo-jwt/v4 v4.2.0/go.mod h1:MA2RqdXdEn4/uEglx0HcUOgQSyBaTh5JcaHIan3biwU=
github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI=
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
@ -503,8 +501,6 @@ github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpP
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
github.com/tkuchiki/go-timezone v0.2.2 h1:MdHR65KwgVTwWFQrota4SKzc4L5EfuH5SdZZGtk/P2Q=
github.com/tkuchiki/go-timezone v0.2.2/go.mod h1:oFweWxYl35C/s7HMVZXiA19Jr9Y0qJHMaG/J2TES4LY=
github.com/tkuchiki/go-timezone v0.2.3 h1:D3TVdIPrFsu9lxGxqNX2wsZwn1MZtTqTW0mdevMozHc=
github.com/tkuchiki/go-timezone v0.2.3/go.mod h1:oFweWxYl35C/s7HMVZXiA19Jr9Y0qJHMaG/J2TES4LY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@ -578,11 +574,15 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@ -612,10 +612,10 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -654,22 +654,27 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
@ -696,8 +701,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View File

@ -95,6 +95,9 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
if err != nil {
return nil, err
}
if len(parsed.Components) == 0 {
return nil, errors.New("VTODO element does seem not contain any components")
}
vTodo, ok := parsed.Components[0].(*ics.VTodo)
if !ok {
return nil, errors.New("VTODO element not found")

View File

@ -349,82 +349,135 @@ func TestBucket(t *testing.T) {
})
t.Run("Create", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "1",
"view": "3",
}, `{"title":"Lorem Ipsum"}`)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9999"}, `{"title":"Lorem Ipsum"}`)
t.Run("Nonexistent project", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "9999",
"view": "1",
}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
assertHandlerErrorCode(t, err, models.ErrCodeProjectViewDoesNotExist)
})
t.Run("Nonexistent view", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "1",
"view": "9999",
}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectViewDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user13
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"title":"Lorem Ipsum"}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "20",
"view": "80",
}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "6"}, `{"title":"Lorem Ipsum"}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "6",
"view": "24",
}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "7"}, `{"title":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "7",
"view": "28",
}, `{"title":"Lorem Ipsum"}`)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "8"}, `{"title":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "8",
"view": "32",
}, `{"title":"Lorem Ipsum"}`)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"title":"Lorem Ipsum"}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "9",
"view": "36",
}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"title":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "10",
"view": "40",
}, `{"title":"Lorem Ipsum"}`)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "11"}, `{"title":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "11",
"view": "44",
}, `{"title":"Lorem Ipsum"}`)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "12"}, `{"title":"Lorem Ipsum"}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "12",
"view": "48",
}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "13"}, `{"title":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "13",
"view": "52",
}, `{"title":"Lorem Ipsum"}`)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "14"}, `{"title":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "14",
"view": "56",
}, `{"title":"Lorem Ipsum"}`)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "15"}, `{"title":"Lorem Ipsum"}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "15",
"view": "60",
}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "16"}, `{"title":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "16",
"view": "64",
}, `{"title":"Lorem Ipsum"}`)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "17"}, `{"title":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
"project": "17",
"view": "68",
}, `{"title":"Lorem Ipsum"}`)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})

View File

@ -99,7 +99,7 @@ insert into buckets_dg_tmp(id, title, "limit", position, created, updated, creat
select id, title, "limit", position, created, updated, created_by_id, project_view_id
from buckets;
drop index if exists buckets.UQE_buckets_id;
drop index if exists UQE_buckets_id;
drop table buckets;

View File

@ -23,11 +23,13 @@ import (
// CanCreate checks if a user can create a new bucket
func (b *Bucket) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
pv := &ProjectView{
ID: b.ProjectViewID,
ProjectID: b.ProjectID,
pv, err := GetProjectViewByIDAndProject(s, b.ProjectViewID, b.ProjectID)
if err != nil {
return false, err
}
return pv.CanUpdate(s, a)
p := &Project{ID: pv.ProjectID}
return p.CanUpdate(s, a)
}
// CanUpdate checks if a user can update an existing bucket
@ -46,9 +48,11 @@ func (b *Bucket) canDoBucket(s *xorm.Session, a web.Auth) (bool, error) {
if err != nil {
return false, err
}
pv := &ProjectView{
ID: bb.ProjectViewID,
ProjectID: b.ProjectID,
pv, err := GetProjectViewByIDAndProject(s, bb.ProjectViewID, b.ProjectID)
if err != nil {
return false, err
}
return pv.CanUpdate(s, a)
p := &Project{ID: pv.ProjectID}
return p.CanUpdate(s, a)
}

View File

@ -148,23 +148,14 @@ func (l *Label) Delete(s *xorm.Session, _ web.Auth) (err error) {
// @Failure 500 {object} models.Message "Internal error"
// @Router /labels [get]
func (l *Label) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (ls interface{}, resultCount int, numberOfEntries int64, err error) {
if _, is := a.(*LinkSharing); is {
return nil, 0, 0, ErrGenericForbidden{}
}
u, err := user.GetUserByID(s, a.GetID())
if err != nil {
return nil, 0, 0, err
}
return GetLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
Search: []string{search},
User: u,
GetForUser: u.ID,
User: a,
Page: page,
PerPage: perPage,
GetUnusedLabels: true,
GroupByLabelIDsOnly: true,
GetForUser: true,
})
}

View File

@ -17,7 +17,6 @@
package models
import (
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/web"
"xorm.io/builder"
"xorm.io/xorm"
@ -64,27 +63,29 @@ func (l *Label) isLabelOwner(s *xorm.Session, a web.Auth) (bool, error) {
// Helper method to check if a user can see a specific label
func (l *Label) hasAccessToLabel(s *xorm.Session, a web.Auth) (has bool, maxRight int, err error) {
if _, is := a.(*LinkSharing); is {
return false, 0, nil
}
linkShare, isLinkShare := a.(*LinkSharing)
u, err := user.GetUserByID(s, a.GetID())
if err != nil {
return false, 0, err
var where builder.Cond
var createdByID int64
if isLinkShare {
where = builder.Eq{"project_id": linkShare.ProjectID}
} else {
where = builder.In("project_id", getUserProjectsStatement(a.GetID(), "", false).Select("l.id"))
createdByID = a.GetID()
}
cond := builder.In("label_tasks.task_id",
builder.
Select("id").
From("tasks").
Where(builder.In("project_id", getUserProjectsStatement(u.ID, "", false).Select("l.id"))),
Where(where),
)
ll := &LabelTask{}
has, err = s.Table("labels").
Select("label_tasks.*").
Join("LEFT", "label_tasks", "label_tasks.label_id = labels.id").
Where("label_tasks.label_id is not null OR labels.created_by_id = ?", u.ID).
Where("label_tasks.label_id is not null OR labels.created_by_id = ?", createdByID).
Or(cond).
And("labels.id = ?", l.ID).
Exist(ll)

View File

@ -144,7 +144,7 @@ func (lt *LabelTask) ReadAll(s *xorm.Session, a web.Auth, search string, page in
}
return GetLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
User: &user.User{ID: a.GetID()},
User: a,
Search: []string{search},
Page: page,
TaskIDs: []int64{lt.TaskID},
@ -159,23 +159,26 @@ type LabelWithTaskID struct {
// LabelByTaskIDsOptions is a struct to not clutter the function with too many optional parameters.
type LabelByTaskIDsOptions struct {
User *user.User
User web.Auth
Search []string
Page int
PerPage int
TaskIDs []int64
GetUnusedLabels bool
GroupByLabelIDsOnly bool
GetForUser int64
GetForUser bool
}
// GetLabelsByTaskIDs is a helper function to get all labels for a set of tasks
// Used when getting all labels for one task as well when getting all lables
func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*LabelWithTaskID, resultCount int, totalEntries int64, err error) {
linkShare, isLinkShareAuth := opts.User.(*LinkSharing)
// We still need the task ID when we want to get all labels for a task, but because of this, we get the same label
// multiple times when it is associated to more than one task.
// Because of this whole thing, we need this extra switch here to only group by Task IDs if needed.
// Probably not the most ideal solution.
// Probably not the most ideataskdetaill solution.
var groupBy = "labels.id,label_tasks.task_id"
var selectStmt = "labels.*, label_tasks.task_id"
if opts.GroupByLabelIDsOnly {
@ -186,20 +189,25 @@ func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*Lab
// Get all labels associated with these tasks
var labels []*LabelWithTaskID
cond := builder.And(builder.NotNull{"label_tasks.label_id"})
if len(opts.TaskIDs) > 0 && opts.GetForUser == 0 {
if len(opts.TaskIDs) > 0 && !opts.GetForUser {
cond = builder.And(builder.In("label_tasks.task_id", opts.TaskIDs), cond)
}
if opts.GetForUser != 0 {
if opts.GetForUser {
projects, _, _, err := getRawProjectsForUser(s, &projectOptions{
user: &user.User{ID: opts.GetForUser},
})
if err != nil {
return nil, 0, 0, err
}
projectIDs := make([]int64, 0, len(projects))
for _, project := range projects {
projectIDs = append(projectIDs, project.ID)
var projectIDs []int64
if isLinkShareAuth {
projectIDs = []int64{linkShare.ProjectID}
} else {
projects, _, _, err := getRawProjectsForUser(s, &projectOptions{
user: &user.User{ID: opts.User.GetID()},
})
if err != nil {
return nil, 0, 0, err
}
projectIDs = make([]int64, 0, len(projects))
for _, project := range projects {
projectIDs = append(projectIDs, project.ID)
}
}
cond = builder.And(builder.In("label_tasks.task_id",
@ -209,8 +217,8 @@ func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*Lab
Where(builder.In("project_id", projectIDs)),
), cond)
}
if opts.GetUnusedLabels {
cond = builder.Or(cond, builder.Eq{"labels.created_by_id": opts.User.ID})
if opts.GetUnusedLabels && !isLinkShareAuth {
cond = builder.Or(cond, builder.Eq{"labels.created_by_id": opts.User.GetID()})
}
ids := []int64{}

View File

@ -62,7 +62,7 @@ func TestLabel_ReadAll(t *testing.T) {
name string
fields fields
args args
wantLs interface{}
wantLs []*LabelWithTaskID
wantErr bool
}{
{
@ -143,7 +143,10 @@ func TestLabel_ReadAll(t *testing.T) {
t.Errorf("Label.ReadAll() error = %v, wantErr %v", err, tt.wantErr)
return
}
if diff, equal := messagediff.PrettyDiff(gotLs, tt.wantLs); !equal {
got := gotLs.([]*LabelWithTaskID)
if diff, equal := messagediff.PrettyDiff(got, tt.wantLs); !equal {
t.Errorf("Label.ReadAll() = %v, want %v, diff: %v", gotLs, tt.wantLs, diff)
}
s.Close()

View File

@ -28,17 +28,17 @@ func (p *ProjectView) CanRead(s *xorm.Session, a web.Auth) (bool, int, error) {
func (p *ProjectView) CanDelete(s *xorm.Session, a web.Auth) (bool, error) {
pp := p.getProject()
return pp.CanUpdate(s, a)
return pp.IsAdmin(s, a)
}
func (p *ProjectView) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
pp := p.getProject()
return pp.CanUpdate(s, a)
return pp.IsAdmin(s, a)
}
func (p *ProjectView) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
pp := p.getProject()
return pp.CanUpdate(s, a)
return pp.IsAdmin(s, a)
}
func (p *ProjectView) getProject() (pp *Project) {

View File

@ -640,6 +640,7 @@ func checkBucketLimit(s *xorm.Session, t *Task, bucket *Bucket) (err error) {
if bucket.Limit > 0 {
taskCount, err := s.
Where("bucket_id = ?", bucket.ID).
GroupBy("task_id").
Count(&TaskBucket{})
if err != nil {
return err

View File

@ -38,6 +38,10 @@ func RemoveEmptySSOTeams(s *xorm.Session) (err error) {
return err
}
if len(teams) == 0 {
return nil
}
teamIDs := make([]int64, 0, len(teams))
for _, team := range teams {
teamIDs = append(teamIDs, team.ID)
@ -63,6 +67,6 @@ func RegisterEmptyOpenIDTeamCleanupCron() {
}
})
if err != nil {
log.Fatalf("Could not empty openid teams cleanup cron: %s", err)
log.Fatalf("Could not register empty openid teams cleanup cron: %s", err)
}
}

View File

@ -32,6 +32,8 @@ type Image struct {
Info interface{} `json:"info,omitempty"`
}
const MaxBackgroundImageHeight = 3840
// Provider represents something that is able to get a project of images and set one of them as background
type Provider interface {
// Search is used to either return a pre-defined project of Image or let the user search for an image

View File

@ -17,10 +17,13 @@
package handler
import (
"bytes"
_ "image/gif" // To make sure the decoder used for generating blurHashes recognizes gifs
_ "image/jpeg" // To make sure the decoder used for generating blurHashes recognizes jpgs
_ "image/png" // To make sure the decoder used for generating blurHashes recognizes pngs
"github.com/disintegration/imaging"
_ "golang.org/x/image/bmp" // To make sure the decoder used for generating blurHashes recognizes bmps
_ "golang.org/x/image/tiff" // To make sure the decoder used for generating blurHashes recognizes tiffs
_ "golang.org/x/image/webp" // To make sure the decoder used for generating blurHashes recognizes tiffs
@ -210,6 +213,12 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
return handler.HandleHTTPError(err, c)
}
err = project.ReadOne(s, auth)
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
@ -220,7 +229,30 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
func SaveBackgroundFile(s *xorm.Session, auth web.Auth, project *models.Project, srcf io.ReadSeeker, filename string, filesize uint64) (err error) {
_, _ = srcf.Seek(0, io.SeekStart)
f, err := files.Create(srcf, filename, filesize, auth)
src, err := imaging.Decode(srcf)
if err != nil {
return err
}
_, _ = srcf.Seek(0, io.SeekStart)
imgConfig, _, err := image.DecodeConfig(srcf)
if err != nil {
return err
}
height := imgConfig.Height
if imgConfig.Height > background.MaxBackgroundImageHeight {
height = background.MaxBackgroundImageHeight
}
buf := bytes.Buffer{}
dst := imaging.Resize(src, 0, height, imaging.Lanczos)
err = imaging.Encode(&buf, dst, imaging.JPEG, imaging.JPEGQuality(80))
if err != nil {
return err
}
f, err := files.Create(&buf, filename, filesize, auth)
if err != nil {
return err
}

View File

@ -256,7 +256,7 @@ func (p *Provider) Set(s *xorm.Session, image *background.Image, project *models
// Download the photo from unsplash
// The parameters crop the image to a max width of 2560 and a max height of 2048 to save bandwidth and storage.
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, photo.Urls.Raw+"&w=2560&h=2048&q=90", nil)
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, photo.Urls.Raw+"&fm=jpg&h="+strconv.FormatInt(background.MaxBackgroundImageHeight, 10)+"&q=80", nil)
if err != nil {
return
}

View File

@ -150,36 +150,10 @@ func Restore(filename string) error {
delete(dbfiles, "migration")
// Restore all db data
for table, d := range dbfiles {
content, err := unmarshalFileToJSON(d)
if err != nil {
return fmt.Errorf("could not read table %s: %w", table, err)
}
// FIXME: There has to be a general way to do this but this works for now.
if table == "notifications" {
for i := range content {
var decoded []byte
decoded, err = base64.StdEncoding.DecodeString(content[i]["notification"].(string))
if err != nil && !errors.Is(err, base64.CorruptInputError(0)) {
return fmt.Errorf("could not decode notification %s: %w", content[i]["notification"], err)
}
if err != nil && errors.Is(err, base64.CorruptInputError(0)) {
decoded = []byte(content[i]["notification"].(string))
}
content[i]["notification"] = string(decoded)
}
}
if err := db.Restore(table, content); err != nil {
return fmt.Errorf("could not restore table data for table %s: %w", table, err)
}
log.Infof("Restored table %s", table)
err = restoreTableData(dbfiles)
if err != nil {
return err
}
log.Infof("Restored %d tables", len(dbfiles))
// Run migrations again to migrate a potentially outdated dump
migration.Migrate(nil)
@ -216,6 +190,53 @@ func Restore(filename string) error {
return nil
}
func restoreTableData(tables map[string]*zip.File) error {
jsonFields := map[string][]string{
"notifications": {"notification"},
"users": {"frontend_settings"},
}
// Restore all db data
for table, d := range tables {
content, err := unmarshalFileToJSON(d)
if err != nil {
return fmt.Errorf("could not read table %s: %w", table, err)
}
fields, hasJSONFields := jsonFields[table]
if hasJSONFields {
for i := range content {
for _, f := range fields {
if _, hasField := content[i][f]; !hasField {
continue
}
var decoded []byte
decoded, err = base64.StdEncoding.DecodeString(content[i][f].(string))
if err != nil && !errors.Is(err, base64.CorruptInputError(0)) {
return fmt.Errorf("could not decode field '%s' %s: %w", f, content[i][f], err)
}
if err != nil && errors.Is(err, base64.CorruptInputError(0)) {
decoded = []byte(content[i][f].(string))
}
content[i][f] = string(decoded)
}
}
}
if err := db.Restore(table, content); err != nil {
return fmt.Errorf("could not restore table data for table %s: %w", table, err)
}
log.Infof("Restored table %s", table)
}
log.Infof("Restored %d tables", len(tables))
return nil
}
func unmarshalFileToJSON(file *zip.File) (contents []map[string]interface{}, err error) {
rc, err := file.Open()
if err != nil {

View File

@ -419,7 +419,7 @@ func persistLabels(s *xorm.Session, a web.Auth, task *models.Task, labels []*mod
existingLabels, _, _, err := models.GetLabelsByTaskIDs(s, &models.LabelByTaskIDsOptions{
Search: labelTitles,
User: u,
GetForUser: u.ID,
GetForUser: true,
GetUnusedLabels: true,
GroupByLabelIDsOnly: true,
})
@ -636,7 +636,7 @@ func (vcls *VikunjaCaldavProjectStorage) getProjectRessource(isCollection bool)
tk := models.TaskCollection{
ProjectID: vcls.project.ID,
}
iface, _, _, err := tk.ReadAll(s, vcls.user, "", 1, 1000)
iface, _, _, err := tk.ReadAll(s, vcls.user, "", 0, -1)
if err != nil {
_ = s.Rollback()
return rr, err