Compare commits
82 Commits
renovate/l
...
main
Author | SHA1 | Date |
---|---|---|
Frederick [Bot] | 3925536a7c | |
kolaente | 2e298ffc11 | |
kolaente | 536047c4c4 | |
kolaente | 34780daab0 | |
kolaente | 76b3ac39b6 | |
Frederick [Bot] | 86b460d09c | |
renovate | b93e237899 | |
Frederick [Bot] | 9706ebb2fc | |
renovate | 1d2ee77e8a | |
renovate | 5098363e56 | |
renovate | 4c1dc6930d | |
renovate | db6d88fff5 | |
renovate | b021dd7237 | |
kolaente | e1dcf2e859 | |
kolaente | 6e759b3bee | |
kolaente | d3a7d79eb9 | |
kolaente | e0ce3e50bd | |
kolaente | 272f643955 | |
kolaente | cf46c76811 | |
kolaente | 31e502d711 | |
kolaente | fa628edc0c | |
kolaente | 053c4d5842 | |
kolaente | 8d1fc08de6 | |
kolaente | eee7b060b6 | |
renovate | 794fc4c1bb | |
renovate | e58d10bc72 | |
kolaente | 7837bcfaae | |
renovate | 615d40f4cd | |
renovate | fbf7037974 | |
renovate | 212c5506af | |
renovate | c861970f41 | |
kolaente | 37d3715eeb | |
kolaente | b0db3ce34c | |
renovate | 358e11c404 | |
renovate | 0fd7fc1452 | |
renovate | a7db576aad | |
renovate | 59281c39cf | |
renovate | f8b502f344 | |
Frederick [Bot] | ddf8db3b1f | |
renovate | 9260b3f1d3 | |
renovate | ab74b08314 | |
renovate | 9637db5a6b | |
renovate | 08645d38a0 | |
renovate | f155d6bb60 | |
renovate | 9a3d63a713 | |
renovate | 050f4313c8 | |
renovate | e917323d91 | |
renovate | 8ad7d00559 | |
renovate | fd126fa234 | |
renovate | 0c39a3dd38 | |
kolaente | 66e96322ea | |
kolaente | 00a96663ba | |
kolaente | 741370b613 | |
renovate | 70183dd7c6 | |
renovate | 760bec5e76 | |
renovate | 78f03373b8 | |
renovate | 09c6d095df | |
renovate | b102fe8188 | |
renovate | f7c367b5bb | |
renovate | b94053e42e | |
renovate | 6e98a6d7ff | |
renovate | 42bfe107ae | |
Frederick [Bot] | a1892ea10b | |
renovate | 899f8f9bc1 | |
renovate | 40f0ca6670 | |
renovate | f8d35396dc | |
kolaente | 409822442b | |
kolaente | aec60f3591 | |
renovate | 9b5ae38784 | |
renovate | 3e40a43d56 | |
kolaente | 15e0c716ad | |
kolaente | 26ada628a2 | |
kolaente | d86fdcb756 | |
kolaente | 84197dd9c1 | |
kolaente | 324df991ce | |
kolaente | 1f6a1f8ad4 | |
kolaente | ea7527a3cf | |
kolaente | 574c7f218e | |
kolaente | 1074a8d916 | |
kolaente | e88f95e501 | |
kolaente | 0962aa4262 | |
renovate | a48ad6c9e1 |
|
@ -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.
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
{ pkgs ? import <nixpkgs> {}
|
||||
}:
|
||||
pkgs.mkShell {
|
||||
name="electron-dev";
|
||||
buildInputs = [
|
||||
pkgs.electron
|
||||
];
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "29.3.1",
|
||||
"electron": "29.3.3",
|
||||
"electron-builder": "24.13.3"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
2028
desktop/yarn.lock
2028
desktop/yarn.lock
File diff suppressed because it is too large
Load Diff
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.**
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
nodePackages.pnpm cypress
|
||||
# API tools
|
||||
go golangci-lint mage
|
||||
# Desktop
|
||||
electron
|
||||
];
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1 +1 @@
|
|||
20.12.2
|
||||
20.13.0
|
|
@ -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')
|
||||
})
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -57,7 +57,7 @@ const showDocs = ref(false)
|
|||
<code>done = false && priority >= 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) && dueDate <= now</code>:
|
||||
{{ $t('filters.query.help.examples.priorityOneOrTwoPastDue') }}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -306,4 +306,12 @@ function prepareFiltersAndLoadTasks() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-view {
|
||||
padding-bottom: 1rem;
|
||||
|
||||
:deep(.card) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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": "إنشاء مشروع",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 l’arriè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
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "プロジェクトの作成",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "Создать проект",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "创建项目",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -160,7 +160,6 @@ export const useLabelStore = defineStore('label', () => {
|
|||
deleteLabel,
|
||||
updateLabel,
|
||||
createLabel,
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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}"
|
||||
/>
|
||||
|
|
|
@ -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
18
go.mod
|
@ -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
25
go.sum
|
@ -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=
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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"`)
|
||||
})
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue