Compare commits
1 Commits
main
...
renovate/l
Author | SHA1 | Date |
---|---|---|
renovate | 813a1b8eb2 |
|
@ -156,7 +156,7 @@ mailer:
|
|||
username: "user"
|
||||
# SMTP password
|
||||
password: ""
|
||||
# Whether to skip verification of the tls certificate on the server
|
||||
# Wether 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 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.
|
||||
# **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.
|
||||
# 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 timeout in seconds until a webhook request fails when no response has been received.
|
||||
timeoutseconds: 30
|
||||
# The timout in seconds until a webhook request fails when no response has been received.
|
||||
timoutseconds: 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.
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{ pkgs ? import <nixpkgs> {}
|
||||
}:
|
||||
pkgs.mkShell {
|
||||
name="electron-dev";
|
||||
buildInputs = [
|
||||
pkgs.electron
|
||||
];
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "29.3.3",
|
||||
"electron": "29.3.1",
|
||||
"electron-builder": "24.13.3"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
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 documentation](https://xorm.io/docs/).
|
||||
To learn more about how to configure your struct to create "good" tables, refer to [the xorm documentaion](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,15 +26,6 @@ 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 recommended to have one root project with all projects of the other service as child projects.
|
||||
In general, it is reccommended 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.
|
||||
|
||||
### Prerequisites
|
||||
### Prerequesites
|
||||
|
||||
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 therefore have the same options available,
|
||||
The integration tests use the same config and fixtures as the unit tests and therefor have the same options available,
|
||||
see at the beginning of this document.
|
||||
|
||||
To run integration tests, use `mage test:integration`.
|
||||
|
|
|
@ -348,7 +348,6 @@ 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`
|
||||
|
@ -780,7 +779,7 @@ Environment path: `VIKUNJA_MAILER_PASSWORD`
|
|||
|
||||
### skiptlsverify
|
||||
|
||||
Whether to skip verification of the tls certificate on the server
|
||||
Wether to skip verification of the tls certificate on the server
|
||||
|
||||
Default: `false`
|
||||
|
||||
|
@ -1223,7 +1222,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 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.
|
||||
**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.
|
||||
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.
|
||||
|
@ -1422,15 +1421,15 @@ Full path: `webhooks.enabled`
|
|||
Environment path: `VIKUNJA_WEBHOOKS_ENABLED`
|
||||
|
||||
|
||||
### timeoutseconds
|
||||
### timoutseconds
|
||||
|
||||
The timeout in seconds until a webhook request fails when no response has been received.
|
||||
The timout in seconds until a webhook request fails when no response has been received.
|
||||
|
||||
Default: `30`
|
||||
|
||||
Full path: `webhooks.timeoutseconds`
|
||||
Full path: `webhooks.timoutseconds`
|
||||
|
||||
Environment path: `VIKUNJA_WEBHOOKS_TIMEOUTSECONDS`
|
||||
Environment path: `VIKUNJA_WEBHOOKS_TIMOUTSECONDS`
|
||||
|
||||
|
||||
### 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 acquire ownership of the files folder, as that would mean it had to run as root.
|
||||
Vikunja will not try to aquire 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 example):
|
||||
* 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):
|
||||
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 resources:
|
||||
And after you installed Vikunja, you may want to check out these other ressources:
|
||||
|
||||
* [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:** Check out the version you want to build with `git checkout VERSION` - replace `VERSION` with the version want to use.
|
||||
**Note:** Ceck 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, simplifying interaction between the involved parties significantly.
|
||||
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.
|
||||
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.
|
||||
|
||||
### Dynamically set with build command
|
||||
### Dynamicly 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 behavior 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 behavoir 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 additional array with all invalid fields. |
|
||||
| 2002 | 400 | Some of the request data was invalid. The response contains an aditional 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 programmatically.
|
||||
Check out [the api docs](https://try.vikunja.io/api/v1/docs#tag/webhooks) for information about how to create webhooks programatically.
|
||||
|
||||
## Available events and their payload
|
||||
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
nodePackages.pnpm cypress
|
||||
# API tools
|
||||
go golangci-lint mage
|
||||
# Desktop
|
||||
electron
|
||||
];
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1 +1 @@
|
|||
20.13.0
|
||||
20.12.2
|
|
@ -68,7 +68,7 @@ describe('Project View List', () => {
|
|||
|
||||
cy.get('.project-title-wrapper .icon')
|
||||
.should('not.exist')
|
||||
cy.get('input.input[placeholder="Add a task..."')
|
||||
cy.get('input.input[placeholder="Add a new 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 task..."')
|
||||
cy.get('input.input[placeholder="Add a new 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 task..."')
|
||||
cy.get('input.input[placeholder="Add a new 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 team')
|
||||
.contains('Create a new team')
|
||||
.click()
|
||||
cy.url()
|
||||
.should('contain', '/teams/new')
|
||||
cy.get('.card-header-title')
|
||||
.contains('Create a team')
|
||||
.contains('Create a new 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 task…"')
|
||||
cy.get('.input[placeholder="Add a new 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 task…"')
|
||||
cy.get('.input[placeholder="Add a new 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 reminder')
|
||||
.contains('Add a new 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 reminder')
|
||||
.contains('Add a new 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 reminder')
|
||||
.contains('Add a new 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 reminder')
|
||||
.contains('Add a new 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 reminder')
|
||||
.contains('Add a new 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.1.0",
|
||||
"packageManager": "pnpm@9.0.4",
|
||||
"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.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",
|
||||
"@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",
|
||||
"@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.11",
|
||||
"dompurify": "3.1.2",
|
||||
"dayjs": "1.11.10",
|
||||
"dompurify": "3.1.0",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"flatpickr": "4.6.13",
|
||||
"flexsearch": "0.7.31",
|
||||
|
@ -111,20 +111,20 @@
|
|||
"is-touch-device": "1.0.1",
|
||||
"klona": "2.0.6",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"lowlight": "2.9.0",
|
||||
"lowlight": "3.1.0",
|
||||
"pinia": "2.1.7",
|
||||
"register-service-worker": "1.7.2",
|
||||
"snake-case": "3.0.4",
|
||||
"sortablejs": "1.15.2",
|
||||
"tippy.js": "6.3.7",
|
||||
"ufo": "1.5.3",
|
||||
"vue": "3.4.27",
|
||||
"vue": "3.4.23",
|
||||
"vue-advanced-cropper": "2.8.8",
|
||||
"vue-flatpickr-component": "11.0.5",
|
||||
"vue-i18n": "9.13.1",
|
||||
"vue-i18n": "9.13.0",
|
||||
"vue-router": "4.3.2",
|
||||
"vuemoji-picker": "0.2.1",
|
||||
"workbox-precaching": "7.1.0",
|
||||
"workbox-precaching": "7.0.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.11",
|
||||
"@types/node": "20.12.7",
|
||||
"@types/postcss-preset-env": "7.7.0",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "7.8.0",
|
||||
"@typescript-eslint/parser": "7.8.0",
|
||||
"@vitejs/plugin-legacy": "5.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "7.7.0",
|
||||
"@typescript-eslint/parser": "7.7.0",
|
||||
"@vitejs/plugin-legacy": "5.3.2",
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"@vue/eslint-config-typescript": "13.0.0",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"@vue/test-utils": "2.4.5",
|
||||
"@vue/tsconfig": "0.5.1",
|
||||
"autoprefixer": "10.4.19",
|
||||
"browserslist": "4.23.0",
|
||||
"caniuse-lite": "1.0.30001617",
|
||||
"caniuse-lite": "1.0.30001611",
|
||||
"css-has-pseudo": "6.0.3",
|
||||
"csstype": "3.1.3",
|
||||
"cypress": "13.9.0",
|
||||
"esbuild": "0.21.1",
|
||||
"cypress": "13.8.0",
|
||||
"esbuild": "0.20.2",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-vue": "9.26.0",
|
||||
"happy-dom": "14.10.1",
|
||||
"eslint-plugin-vue": "9.25.0",
|
||||
"happy-dom": "14.7.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.11",
|
||||
"rollup": "4.17.2",
|
||||
"postcss-preset-env": "9.5.6",
|
||||
"rollup": "4.14.3",
|
||||
"rollup-plugin-visualizer": "5.12.0",
|
||||
"sass": "1.77.0",
|
||||
"sass": "1.75.0",
|
||||
"start-server-and-test": "2.0.3",
|
||||
"typescript": "5.4.5",
|
||||
"vite": "5.2.11",
|
||||
"vite": "5.2.9",
|
||||
"vite-plugin-inject-preload": "1.3.3",
|
||||
"vite-plugin-pwa": "0.20.0",
|
||||
"vite-plugin-pwa": "0.19.8",
|
||||
"vite-plugin-sentry": "1.4.0",
|
||||
"vite-svg-loader": "5.1.0",
|
||||
"vitest": "1.6.0",
|
||||
"vue-tsc": "2.0.16",
|
||||
"vitest": "1.5.0",
|
||||
"vue-tsc": "2.0.13",
|
||||
"wait-on": "7.2.0",
|
||||
"workbox-cli": "7.1.0"
|
||||
"workbox-cli": "7.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,27 +1,26 @@
|
|||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'has-background': background,
|
||||
'link-share-is-fullwidth': isFullWidth,
|
||||
}"
|
||||
:class="[background ? 'has-background' : '', $route.name as string +'-view']"
|
||||
:style="{'background-image': `url(${background})`}"
|
||||
class="link-share-container"
|
||||
>
|
||||
<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 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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,13 +30,10 @@
|
|||
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)
|
||||
|
@ -46,23 +42,6 @@ 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>
|
||||
|
@ -74,34 +53,20 @@ const isFullWidth = computed(() => {
|
|||
.logo {
|
||||
max-width: 300px;
|
||||
width: 90%;
|
||||
margin: 1rem auto 2rem;
|
||||
margin: 2rem 0 1.5rem;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.column {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-shadow: 0 0 1rem 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;
|
||||
}
|
||||
}
|
||||
// FIXME: this should be defined somewhere deep
|
||||
.link-share-view .card {
|
||||
background-color: var(--white);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -818,7 +818,7 @@ watch(
|
|||
td,
|
||||
th {
|
||||
min-width: 1em;
|
||||
border: 2px solid var(--grey-300) !important;
|
||||
border: 2px solid #ced4da;
|
||||
padding: 3px 5px;
|
||||
vertical-align: top;
|
||||
box-sizing: border-box;
|
||||
|
@ -832,7 +832,7 @@ watch(
|
|||
th {
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
background-color: var(--grey-200);
|
||||
background-color: #f1f3f5;
|
||||
}
|
||||
|
||||
.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>
|
||||
{{ getProjectTitle(project) }}
|
||||
{{ project.title }}
|
||||
</div>
|
||||
<BaseButton
|
||||
class="project-button"
|
||||
|
@ -59,7 +59,6 @@ import BaseButton from '@/components/base/BaseButton.vue'
|
|||
|
||||
import {useProjectBackground} from './useProjectBackground'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
import {getProjectTitle} from '@/helpers/getProjectTitle'
|
||||
|
||||
const {
|
||||
project,
|
||||
|
|
|
@ -306,12 +306,4 @@ 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, watch} from 'vue'
|
||||
import {computed, type Ref} from 'vue'
|
||||
|
||||
import {useStorage} from '@vueuse/core'
|
||||
|
||||
|
@ -337,12 +337,6 @@ 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]
|
||||
|
@ -353,16 +347,7 @@ function sort(property: keyof SortBy) {
|
|||
} else {
|
||||
delete sortBy.value[property]
|
||||
}
|
||||
setActiveColumnsSortParam()
|
||||
}
|
||||
|
||||
function setActiveColumnsSortParam() {
|
||||
sortByParam.value = Object.keys(sortBy.value)
|
||||
.filter(prop => activeColumns.value[prop])
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = sortBy.value[key]
|
||||
return obj
|
||||
}, {})
|
||||
sortByParam.value = sortBy.value
|
||||
}
|
||||
|
||||
// TODO: re-enable opening task detail in modal
|
||||
|
|
|
@ -323,8 +323,9 @@ async function setCoverImage(attachment: IAttachment | null) {
|
|||
margin-bottom: 0;
|
||||
display: flex;
|
||||
|
||||
> span,
|
||||
> span:not(:last-child):after,
|
||||
> 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 || (task.repeatAfter.amount === 0 && task.repeatMode === TASK_REPEAT_MODES.REPEAT_MODE_MONTH)"
|
||||
v-if="task.repeatAfter.amount > 0"
|
||||
class="project-task-icon"
|
||||
>
|
||||
<icon icon="history" />
|
||||
|
@ -207,7 +207,6 @@ 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'
|
||||
|
||||
export interface dateParseResult {
|
||||
interface dateParseResult {
|
||||
newText: string,
|
||||
date: Date | null,
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ interface dateFoundResult {
|
|||
date: Date | null,
|
||||
}
|
||||
|
||||
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)'
|
||||
const monthsRegexGroup = '(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, now)
|
||||
let parsed = getDateFromWeekday(text)
|
||||
if (parsed.date !== null) {
|
||||
return addTimeToDate(text, parsed.date, parsed.foundText)
|
||||
}
|
||||
|
||||
parsed = getDayFromText(text, now)
|
||||
parsed = getDayFromText(text)
|
||||
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, now)
|
||||
parsed = getDateFromText(text)
|
||||
|
||||
if (parsed.date === null) {
|
||||
return {
|
||||
|
@ -230,7 +230,7 @@ export const getDateFromTextIn = (text: string, now: Date = new Date()) => {
|
|||
}
|
||||
}
|
||||
|
||||
const getDateFromWeekday = (text: string, date: Date = new Date()): dateFoundResult => {
|
||||
const getDateFromWeekday = (text: string): 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,6 +240,7 @@ const getDateFromWeekday = (text: string, date: Date = new Date()): dateFoundRes
|
|||
}
|
||||
}
|
||||
|
||||
const date: Date = new Date()
|
||||
const currentDay: number = date.getDay()
|
||||
let day = 0
|
||||
|
||||
|
@ -295,7 +296,7 @@ const getDateFromWeekday = (text: string, date: Date = new Date()): dateFoundRes
|
|||
}
|
||||
}
|
||||
|
||||
const getDayFromText = (text: string, now: Date = new Date()) => {
|
||||
const getDayFromText = (text: string) => {
|
||||
const matcher = /(^| )(([1-2][0-9])|(3[01])|(0?[1-9]))(st|nd|rd|th|\.)($| )/ig
|
||||
const results = matcher.exec(text)
|
||||
if (results === null) {
|
||||
|
@ -305,6 +306,7 @@ const getDayFromText = (text: string, now: Date = new Date()) => {
|
|||
}
|
||||
}
|
||||
|
||||
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_HOUR === 0) {
|
||||
if (seconds % SECONDS_A_MINUTE === 0) {
|
||||
return {
|
||||
unit: 'hours',
|
||||
amount: seconds / SECONDS_A_HOUR,
|
||||
unit: 'minutes',
|
||||
amount: seconds / SECONDS_A_MINUTE,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
unit: 'minutes',
|
||||
amount: seconds / SECONDS_A_MINUTE,
|
||||
unit: 'hours',
|
||||
amount: seconds / SECONDS_A_HOUR,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "كل 30 يوماً",
|
||||
"mode": "وضع التكرار",
|
||||
"monthly": "شهرياً",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "من التاريخ الحالي",
|
||||
"each": "كل",
|
||||
"specifyAmount": "حدد قيمة…",
|
||||
"hours": "الساعات",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "تكرار",
|
||||
"delete": "حذف",
|
||||
"unarchive": "إلغاء الأرشفة",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "تعيين خلفية",
|
||||
"share": "مشاركة",
|
||||
"newProject": "مشروع جديد",
|
||||
"createProject": "إنشاء مشروع",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Repeat mode",
|
||||
"monthly": "Monthly",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "From Current Date",
|
||||
"each": "Each",
|
||||
"specifyAmount": "Specify an amount…",
|
||||
"hours": "Hours",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplicate",
|
||||
"delete": "Delete",
|
||||
"unarchive": "Un-Archive",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Set background",
|
||||
"share": "Share",
|
||||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Každých 30 dní",
|
||||
"mode": "Režim opakování",
|
||||
"monthly": "Měsíčně",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "Od aktuálního data",
|
||||
"each": "Každý",
|
||||
"specifyAmount": "Zadejte množství…",
|
||||
"hours": "Hodin",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplikovat",
|
||||
"delete": "Smazat",
|
||||
"unarchive": "Zrušit archivaci",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Nastavit pozadí",
|
||||
"share": "Sdílet",
|
||||
"newProject": "Nový projekt",
|
||||
"createProject": "Vytvořit projekt",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Gentagelsestilstand",
|
||||
"monthly": "Månedligt",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "Fra Nuværende Dato",
|
||||
"each": "Hver",
|
||||
"specifyAmount": "Angiv et beløb…",
|
||||
"hours": "Timer",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Dupliker",
|
||||
"delete": "Slet",
|
||||
"unarchive": "Tilbagekald",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Indstil baggrund",
|
||||
"share": "Del",
|
||||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Nur Projektadministrator:innen können Ansichten bearbeiten."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Alle 30 Tage",
|
||||
"mode": "Wiederholungsmodus",
|
||||
"monthly": "Monatlich",
|
||||
"fromCurrentDate": "Ab Fertigstellungsdatum",
|
||||
"fromCurrentDate": "Ab dem aktuellen Datum",
|
||||
"each": "Alle",
|
||||
"specifyAmount": "Gib einen Anzahl an …",
|
||||
"hours": "Stunden",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplizieren",
|
||||
"delete": "Löschen",
|
||||
"unarchive": "Archivierung aufheben",
|
||||
"setBackground": "Hintergrundeinstellungen",
|
||||
"setBackground": "Hintergrund einstellen",
|
||||
"share": "Teilen",
|
||||
"newProject": "Neues Projekt",
|
||||
"createProject": "Projekt erstellen",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Nur Projektadministrator:innen können Ansichten bearbeiten."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Alle 30 Tage",
|
||||
"mode": "Widerholigs Modus",
|
||||
"monthly": "Monatlich",
|
||||
"fromCurrentDate": "Ab Fertigstellungsdatum",
|
||||
"fromCurrentDate": "Vom Hüttige Datum",
|
||||
"each": "Jedä",
|
||||
"specifyAmount": "Gib e Ahzahl ah…",
|
||||
"hours": "Stundä",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Dublizierä",
|
||||
"delete": "Chüble",
|
||||
"unarchive": "Ent-archiviere",
|
||||
"setBackground": "Hintergrundeinstellungen",
|
||||
"setBackground": "Hintergrund iihstelle",
|
||||
"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 occurred while authenticating against the third party.",
|
||||
"openIdGeneralError": "An error occured 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 tasks or edit it.",
|
||||
"archiveText": "You won't be able to edit this project or create tasks until you un-archive it.",
|
||||
"unarchiveText": "You will be able to create new tasks or edit it.",
|
||||
"archiveText": "You won't be able to edit this project or create new tasks until you un-archive it.",
|
||||
"success": "The project was successfully archived."
|
||||
},
|
||||
"background": {
|
||||
|
@ -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 link share",
|
||||
"create": "Create a new link share",
|
||||
"name": "Name (optional)",
|
||||
"namePlaceholder": "e.g. Lorem Ipsum",
|
||||
"nameExplanation": "All actions done by this link share will show up with the name.",
|
||||
|
@ -317,9 +317,9 @@
|
|||
"list": {
|
||||
"title": "List",
|
||||
"add": "Add",
|
||||
"addPlaceholder": "Add a task…",
|
||||
"addPlaceholder": "Add a new task…",
|
||||
"empty": "This project is currently empty.",
|
||||
"newTaskCta": "Create a task.",
|
||||
"newTaskCta": "Create a new 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 bucket",
|
||||
"addBucket": "Create a new bucket",
|
||||
"addBucketPlaceholder": "Enter the new bucket title…",
|
||||
"deleteHeaderBucket": "Delete the bucket",
|
||||
"deleteBucketText1": "Are you sure you want to delete this bucket?",
|
||||
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -423,7 +422,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 saved filter",
|
||||
"action": "Create new saved filter",
|
||||
"titleRequired": "Please provide a title for the filter."
|
||||
},
|
||||
"delete": {
|
||||
|
@ -507,7 +506,7 @@
|
|||
"search": "Type to search for a label…",
|
||||
"create": {
|
||||
"header": "New label",
|
||||
"title": "Create a label",
|
||||
"title": "Create a new label",
|
||||
"titleRequired": "Please specify a title.",
|
||||
"success": "The label was successfully created."
|
||||
},
|
||||
|
@ -528,7 +527,7 @@
|
|||
"sharing": {
|
||||
"authenticating": "Authenticating…",
|
||||
"passwordRequired": "This shared project requires a password. Please enter it below:",
|
||||
"error": "An error occurred.",
|
||||
"error": "An error occured.",
|
||||
"invalidPassword": "The password is invalid."
|
||||
},
|
||||
"navigation": {
|
||||
|
@ -643,7 +642,7 @@
|
|||
}
|
||||
},
|
||||
"multiselect": {
|
||||
"createPlaceholder": "Create",
|
||||
"createPlaceholder": "Create new",
|
||||
"selectPlaceholder": "Click or press enter to select"
|
||||
},
|
||||
"datepickerRange": {
|
||||
|
@ -722,10 +721,10 @@
|
|||
},
|
||||
"task": {
|
||||
"task": "Task",
|
||||
"new": "Create a task",
|
||||
"new": "Create a new task",
|
||||
"delete": "Delete this task",
|
||||
"createSuccess": "The task was successfully created.",
|
||||
"addReminder": "Add a reminder…",
|
||||
"addReminder": "Add a new reminder…",
|
||||
"doneSuccess": "The task was successfully marked as done.",
|
||||
"undoneSuccess": "The task was successfully un-marked as done.",
|
||||
"undo": "Undo",
|
||||
|
@ -861,7 +860,7 @@
|
|||
"unassignSuccess": "The user has been unassigned successfully."
|
||||
},
|
||||
"label": {
|
||||
"placeholder": "Type to add a label…",
|
||||
"placeholder": "Type to add a new label…",
|
||||
"createPlaceholder": "Add this as new label",
|
||||
"addSuccess": "The label has been added successfully.",
|
||||
"createSuccess": "The label has been created successfully.",
|
||||
|
@ -884,8 +883,8 @@
|
|||
"relation": {
|
||||
"add": "Add a New Task Relation",
|
||||
"new": "New Task Relation",
|
||||
"searchPlaceholder": "Type search for a task to add as related…",
|
||||
"createPlaceholder": "Add this as related task",
|
||||
"searchPlaceholder": "Type search for a new task to add as related…",
|
||||
"createPlaceholder": "Add this as new related task",
|
||||
"differentProject": "This task belongs to a different project.",
|
||||
"noneYet": "No task relations yet.",
|
||||
"delete": "Delete Task Relation",
|
||||
|
@ -923,7 +922,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Repeat mode",
|
||||
"monthly": "Monthly",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "From Current Date",
|
||||
"each": "Each",
|
||||
"specifyAmount": "Specify an amount…",
|
||||
"hours": "Hours",
|
||||
|
@ -963,7 +962,7 @@
|
|||
"title": "Teams",
|
||||
"noTeams": "You are currently not part of any teams.",
|
||||
"create": {
|
||||
"title": "Create a team",
|
||||
"title": "Create a new team",
|
||||
"success": "The team was successfully created."
|
||||
},
|
||||
"edit": {
|
||||
|
@ -1061,7 +1060,7 @@
|
|||
"duplicate": "Duplicate",
|
||||
"delete": "Delete",
|
||||
"unarchive": "Un-Archive",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Set background",
|
||||
"share": "Share",
|
||||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Repeat mode",
|
||||
"monthly": "Monthly",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "From Current Date",
|
||||
"each": "Each",
|
||||
"specifyAmount": "Specify an amount…",
|
||||
"hours": "Hours",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplicate",
|
||||
"delete": "Delete",
|
||||
"unarchive": "Un-Archive",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Set background",
|
||||
"share": "Share",
|
||||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Modo de repetición",
|
||||
"monthly": "Mensualmente",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "Desde la Fecha Actual",
|
||||
"each": "Cada",
|
||||
"specifyAmount": "Especifique una cantidad…",
|
||||
"hours": "Horas",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplicar",
|
||||
"delete": "Eliminar",
|
||||
"unarchive": "Desarchivar",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Establecer fondo",
|
||||
"share": "Compartir",
|
||||
"newProject": "Nuevo proyecto",
|
||||
"createProject": "Crear proyecto",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Mode de répétition",
|
||||
"monthly": "Mensuel",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "À partir de la date actuelle",
|
||||
"each": "Tous ou toutes les",
|
||||
"specifyAmount": "Indiquer un nombre…",
|
||||
"hours": "Heures",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Dupliquer",
|
||||
"delete": "Supprimer",
|
||||
"unarchive": "Désarchiver",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Définir l’arrière-plan",
|
||||
"share": "Partager",
|
||||
"newProject": "Nouveau projet",
|
||||
"createProject": "Créer un projet",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "A nézet sikeresen törölve"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "30 naponta",
|
||||
"mode": "Ismétlés típusa",
|
||||
"monthly": "Havi",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "Aktuális dátumtól",
|
||||
"each": "Minden egyes",
|
||||
"specifyAmount": "Adja meg a mennyiséget…",
|
||||
"hours": "Órák",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplikálás",
|
||||
"delete": "Törlés",
|
||||
"unarchive": "Archiválás visszavonása",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Háttérkép beállítása",
|
||||
"share": "Megosztás",
|
||||
"newProject": "Új projekt",
|
||||
"createProject": "Projekt létrehozása",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Modalità Ripetizione",
|
||||
"monthly": "Mensilmente",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "Dalla Data Attuale",
|
||||
"each": "Ogni",
|
||||
"specifyAmount": "Specifica una quantità…",
|
||||
"hours": "Ore",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplica",
|
||||
"delete": "Elimina",
|
||||
"unarchive": "Disarchivia",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Imposta sfondo",
|
||||
"share": "Condividi",
|
||||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "30日ごと",
|
||||
"mode": "繰り返しモード",
|
||||
"monthly": "毎月",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "現在時刻からの間隔",
|
||||
"each": "隔",
|
||||
"specifyAmount": "数字を入力…",
|
||||
"hours": "時間ごと",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "複製",
|
||||
"delete": "削除",
|
||||
"unarchive": "アーカイブの取り消し",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "背景画像の設定",
|
||||
"share": "共有",
|
||||
"newProject": "新しいプロジェクトの作成",
|
||||
"createProject": "プロジェクトの作成",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Repeat mode",
|
||||
"monthly": "Monthly",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "From Current Date",
|
||||
"each": "Each",
|
||||
"specifyAmount": "Specify an amount…",
|
||||
"hours": "Hours",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplicate",
|
||||
"delete": "Delete",
|
||||
"unarchive": "Un-Archive",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Set background",
|
||||
"share": "Share",
|
||||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Herhaalmodus",
|
||||
"monthly": "Maandelijks",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "Vanaf huidige datum",
|
||||
"each": "Each",
|
||||
"specifyAmount": "Specify an amount…",
|
||||
"hours": "Uren",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Dupliceer",
|
||||
"delete": "Verwijderen",
|
||||
"unarchive": "Archivering opheffen",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Achtergrond instellen",
|
||||
"share": "Delen",
|
||||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Repeter modus",
|
||||
"monthly": "Månedlig",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "Fra gjeldende dato",
|
||||
"each": "Hver",
|
||||
"specifyAmount": "Angi beløp…",
|
||||
"hours": "Timer",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Dupliser",
|
||||
"delete": "Slett",
|
||||
"unarchive": "Av-arkiver",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Bruk som bakgrunn",
|
||||
"share": "Del",
|
||||
"newProject": "Nytt prosjekt",
|
||||
"createProject": "Create project",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Tylko administratorzy projektu mogą edytować widoki."
|
||||
"deleteSuccess": "Widok został pomyślnie usunięty"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Co 30 dni",
|
||||
"mode": "Tryb powtarzania",
|
||||
"monthly": "Miesięczny",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "Od bieżącej daty",
|
||||
"each": "Co",
|
||||
"specifyAmount": "Określ ilość…",
|
||||
"hours": "Godziny",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplikuj",
|
||||
"delete": "Usuń",
|
||||
"unarchive": "Cofnij archiwizację",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Ustaw tło",
|
||||
"share": "Udostępnij",
|
||||
"newProject": "Nowy projekt",
|
||||
"createProject": "Utwórz projekt",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "A cada 30 dias",
|
||||
"mode": "Modo repetição",
|
||||
"monthly": "Mensalmente",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "Data atual",
|
||||
"each": "Cada",
|
||||
"specifyAmount": "Especifique uma quantidade…",
|
||||
"hours": "Horas",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplicar",
|
||||
"delete": "Excluir",
|
||||
"unarchive": "Desarquivar",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Definir plano de fundo",
|
||||
"share": "Compartilhar",
|
||||
"newProject": "Novo projeto",
|
||||
"createProject": "Criar projeto",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "A cada 30 Dias",
|
||||
"mode": "Modo de repetição",
|
||||
"monthly": "Mensal",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "Da Data Atual",
|
||||
"each": "Cada",
|
||||
"specifyAmount": "Especifica uma quantidade…",
|
||||
"hours": "Horas",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplicar",
|
||||
"delete": "Eliminar",
|
||||
"unarchive": "Desarquivar",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Definir Fundo",
|
||||
"share": "Partilhar",
|
||||
"newProject": "Novo projeto",
|
||||
"createProject": "Criar projeto",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Repeat mode",
|
||||
"monthly": "Monthly",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "From Current Date",
|
||||
"each": "Each",
|
||||
"specifyAmount": "Specify an amount…",
|
||||
"hours": "Hours",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplicate",
|
||||
"delete": "Delete",
|
||||
"unarchive": "Un-Archive",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Set background",
|
||||
"share": "Share",
|
||||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Каждые 30 дней",
|
||||
"mode": "Режим повтора",
|
||||
"monthly": "Ежемесячно",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "От сегодняшей даты",
|
||||
"each": "Каждые",
|
||||
"specifyAmount": "Укажите количество…",
|
||||
"hours": "Часов",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Создать копию",
|
||||
"delete": "Удалить",
|
||||
"unarchive": "Вернуть из архива",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Задать фон",
|
||||
"share": "Поделиться",
|
||||
"newProject": "Создать проект",
|
||||
"createProject": "Создать проект",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Repeat mode",
|
||||
"monthly": "Monthly",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "From Current Date",
|
||||
"each": "Each",
|
||||
"specifyAmount": "Specify an amount…",
|
||||
"hours": "Hours",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplicate",
|
||||
"delete": "Delete",
|
||||
"unarchive": "Un-Archive",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Set background",
|
||||
"share": "Share",
|
||||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Samo skrbniki projekta lahko urejajo poglede."
|
||||
"deleteSuccess": "Pogled je bil uspešno izbrisan"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Vsakih 30 dni",
|
||||
"mode": "Način ponavljanja",
|
||||
"monthly": "Mesečno",
|
||||
"fromCurrentDate": "Od datuma dokončanja",
|
||||
"fromCurrentDate": "Od trenutnega datuma",
|
||||
"each": "Vsak",
|
||||
"specifyAmount": "Določi znesek…",
|
||||
"hours": "Ur",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Podvoji",
|
||||
"delete": "Izbriši",
|
||||
"unarchive": "Odstrani iz arhiva",
|
||||
"setBackground": "Nastavitve ozadja",
|
||||
"setBackground": "Nastavi ozadje",
|
||||
"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 occurred while authenticating against the third party.",
|
||||
"openIdGeneralError": "An error occured 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 tasks or edit it.",
|
||||
"archiveText": "You won't be able to edit this project or create tasks until you un-archive it.",
|
||||
"unarchiveText": "You will be able to create new tasks or edit it.",
|
||||
"archiveText": "You won't be able to edit this project or create new tasks until you un-archive it.",
|
||||
"success": "The project was successfully archived."
|
||||
},
|
||||
"background": {
|
||||
|
@ -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 link share",
|
||||
"create": "Create a new link share",
|
||||
"name": "Name (optional)",
|
||||
"namePlaceholder": "e.g. Lorem Ipsum",
|
||||
"nameExplanation": "All actions done by this link share will show up with the name.",
|
||||
|
@ -317,9 +317,9 @@
|
|||
"list": {
|
||||
"title": "List",
|
||||
"add": "Add",
|
||||
"addPlaceholder": "Add a task…",
|
||||
"addPlaceholder": "Add a new task…",
|
||||
"empty": "This project is currently empty.",
|
||||
"newTaskCta": "Create a task.",
|
||||
"newTaskCta": "Create a new 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 bucket",
|
||||
"addBucket": "Create a new bucket",
|
||||
"addBucketPlaceholder": "Enter the new bucket title…",
|
||||
"deleteHeaderBucket": "Delete the bucket",
|
||||
"deleteBucketText1": "Are you sure you want to delete this bucket?",
|
||||
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -423,7 +422,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 saved filter",
|
||||
"action": "Create new saved filter",
|
||||
"titleRequired": "Please provide a title for the filter."
|
||||
},
|
||||
"delete": {
|
||||
|
@ -507,7 +506,7 @@
|
|||
"search": "Type to search for a label…",
|
||||
"create": {
|
||||
"header": "New label",
|
||||
"title": "Create a label",
|
||||
"title": "Create a new label",
|
||||
"titleRequired": "Please specify a title.",
|
||||
"success": "The label was successfully created."
|
||||
},
|
||||
|
@ -528,7 +527,7 @@
|
|||
"sharing": {
|
||||
"authenticating": "Authenticating…",
|
||||
"passwordRequired": "This shared project requires a password. Please enter it below:",
|
||||
"error": "An error occurred.",
|
||||
"error": "An error occured.",
|
||||
"invalidPassword": "The password is invalid."
|
||||
},
|
||||
"navigation": {
|
||||
|
@ -642,7 +641,7 @@
|
|||
"placeholder": "Type some text or hit '/' to see more options…"
|
||||
},
|
||||
"multiselect": {
|
||||
"createPlaceholder": "Create",
|
||||
"createPlaceholder": "Create new",
|
||||
"selectPlaceholder": "Click or press enter to select"
|
||||
},
|
||||
"datepickerRange": {
|
||||
|
@ -721,10 +720,10 @@
|
|||
},
|
||||
"task": {
|
||||
"task": "Task",
|
||||
"new": "Create a task",
|
||||
"new": "Create a new task",
|
||||
"delete": "Delete this task",
|
||||
"createSuccess": "The task was successfully created.",
|
||||
"addReminder": "Add a reminder…",
|
||||
"addReminder": "Add a new reminder…",
|
||||
"doneSuccess": "The task was successfully marked as done.",
|
||||
"undoneSuccess": "The task was successfully un-marked as done.",
|
||||
"undo": "Undo",
|
||||
|
@ -860,7 +859,7 @@
|
|||
"unassignSuccess": "The user has been unassigned successfully."
|
||||
},
|
||||
"label": {
|
||||
"placeholder": "Type to add a label…",
|
||||
"placeholder": "Type to add a new label…",
|
||||
"createPlaceholder": "Add this as new label",
|
||||
"addSuccess": "The label has been added successfully.",
|
||||
"createSuccess": "The label has been created successfully.",
|
||||
|
@ -883,8 +882,8 @@
|
|||
"relation": {
|
||||
"add": "Add a New Task Relation",
|
||||
"new": "New Task Relation",
|
||||
"searchPlaceholder": "Type search for a task to add as related…",
|
||||
"createPlaceholder": "Add this as related task",
|
||||
"searchPlaceholder": "Type search for a new task to add as related…",
|
||||
"createPlaceholder": "Add this as new related task",
|
||||
"differentProject": "This task belongs to a different project.",
|
||||
"noneYet": "No task relations yet.",
|
||||
"delete": "Delete Task Relation",
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Repeat mode",
|
||||
"monthly": "Monthly",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "From Current Date",
|
||||
"each": "Each",
|
||||
"specifyAmount": "Specify an amount…",
|
||||
"hours": "Hours",
|
||||
|
@ -962,7 +961,7 @@
|
|||
"title": "Teams",
|
||||
"noTeams": "You are currently not part of any teams.",
|
||||
"create": {
|
||||
"title": "Create a team",
|
||||
"title": "Create a new team",
|
||||
"success": "The team was successfully created."
|
||||
},
|
||||
"edit": {
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplicate",
|
||||
"delete": "Delete",
|
||||
"unarchive": "Un-Archive",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Set background",
|
||||
"share": "Share",
|
||||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Repeat mode",
|
||||
"monthly": "Monthly",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "From Current Date",
|
||||
"each": "Each",
|
||||
"specifyAmount": "Specify an amount…",
|
||||
"hours": "Timmar",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplicera",
|
||||
"delete": "Radera",
|
||||
"unarchive": "Un-Archive",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Set background",
|
||||
"share": "Dela",
|
||||
"newProject": "Nytt projekt",
|
||||
"createProject": "Skapa projekt",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Repeat mode",
|
||||
"monthly": "Monthly",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "From Current Date",
|
||||
"each": "Each",
|
||||
"specifyAmount": "Specify an amount…",
|
||||
"hours": "Hours",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplicate",
|
||||
"delete": "Delete",
|
||||
"unarchive": "Un-Archive",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Set background",
|
||||
"share": "Share",
|
||||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Chế độ lặp lại",
|
||||
"monthly": "Hàng tháng",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "Từ ngày hiện tại",
|
||||
"each": "Mỗi",
|
||||
"specifyAmount": "Chỉ định một số lượng…",
|
||||
"hours": "Giờ",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Nhân bản",
|
||||
"delete": "Xóa",
|
||||
"unarchive": "Bỏ lưu trữ",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Cài hình nền",
|
||||
"share": "Chia sẻ",
|
||||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"titleRequired": "请提供标题。",
|
||||
"delete": "删除此视图",
|
||||
"deleteText": "您确定要删除此视图吗?它将不再可能使用它来查看此项目中的任务。 此操作不会删除任何任务。此操作不能撤销!",
|
||||
"deleteSuccess": "视图已成功删除",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "视图已成功删除"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "每 30 天",
|
||||
"mode": "重复模式",
|
||||
"monthly": "每月",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "从当前日期",
|
||||
"each": "每个",
|
||||
"specifyAmount": "指定数量…",
|
||||
"hours": "小时",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "复制",
|
||||
"delete": "删除",
|
||||
"unarchive": "取消存档",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "设置背景",
|
||||
"share": "共享",
|
||||
"newProject": "新项目",
|
||||
"createProject": "创建项目",
|
||||
|
|
|
@ -396,8 +396,7 @@
|
|||
"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",
|
||||
"onlyAdminsCanEdit": "Only project admins can edit views."
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -922,7 +921,7 @@
|
|||
"every30d": "Every 30 Days",
|
||||
"mode": "Repeat mode",
|
||||
"monthly": "Monthly",
|
||||
"fromCurrentDate": "From completion date",
|
||||
"fromCurrentDate": "From Current Date",
|
||||
"each": "Each",
|
||||
"specifyAmount": "Specify an amount…",
|
||||
"hours": "Hours",
|
||||
|
@ -1060,7 +1059,7 @@
|
|||
"duplicate": "Duplicate",
|
||||
"delete": "Delete",
|
||||
"unarchive": "Un-Archive",
|
||||
"setBackground": "Background settings",
|
||||
"setBackground": "Set background",
|
||||
"share": "Share",
|
||||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
import type {IAbstract} from './IAbstract'
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
|
||||
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_KINDS = ['list', 'gantt', 'table', 'kanban']
|
||||
export type ProjectViewKind = typeof PROJECT_VIEW_KINDS[number]
|
||||
|
||||
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 | null
|
||||
language: SupportedLocale
|
||||
frontendSettings: IFrontendSettings
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
||||
import {beforeEach, afterEach, describe, it, expect, vi} from 'vitest'
|
||||
|
||||
import {ParsedTaskText, parseTaskText, PrefixMode} from './parseTaskText'
|
||||
import {parseDate} from '../helpers/time/parseDate'
|
||||
import {parseTaskText, PrefixMode} from './parseTaskText'
|
||||
import {getDateFromText, parseDate} from '../helpers/time/parseDate'
|
||||
import {calculateDayInterval} from '../helpers/time/calculateDayInterval'
|
||||
import {PRIORITIES} from '@/constants/priorities'
|
||||
import {MILLISECONDS_A_DAY} from '@/constants/date'
|
||||
|
@ -461,69 +461,50 @@ 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) {
|
||||
const assertResult = ({date, text}: ParsedTaskText) => {
|
||||
it(`should parse '${c}' as '${cases[c]}' with the date at the end`, () => {
|
||||
const {date, foundText} = getDateFromText(`Lorem Ipsum ${c}`, now)
|
||||
if (date === null && cases[c] === null) {
|
||||
expect(date).toBeNull()
|
||||
return
|
||||
}
|
||||
|
||||
expect(`${date?.getFullYear()}-${date?.getMonth() + 1}-${date?.getDate()}`).toBe(cases[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))
|
||||
expect(foundText.trim()).toBe(c)
|
||||
})
|
||||
it(`should parse '${c}' as '${cases[c]}' with the date at the beginning`, () => {
|
||||
assertResult(parseTaskText(`${c} Lorem Ipsum`, PrefixMode.Default, now))
|
||||
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)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -55,7 +55,7 @@ interface Prefixes {
|
|||
*
|
||||
* @param text
|
||||
*/
|
||||
export const parseTaskText = (text: string, prefixesMode: PrefixMode = PrefixMode.Default, now: Date = new Date()): ParsedTaskText => {
|
||||
export const parseTaskText = (text: string, prefixesMode: PrefixMode = PrefixMode.Default): 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, now)
|
||||
const {newText, date} = parseDate(result.text)
|
||||
result.text = newText
|
||||
result.date = date
|
||||
|
||||
|
|
|
@ -52,16 +52,12 @@ export default class ProjectService extends AbstractService<IProject> {
|
|||
return window.URL.createObjectURL(new Blob([response.data]))
|
||||
}
|
||||
|
||||
async removeBackground(project: IProject) {
|
||||
async removeBackground(project: Pick<IProject, 'id'>) {
|
||||
const cancel = this.setLoading()
|
||||
|
||||
try {
|
||||
await this.http.delete(`/projects/${project.id}/background`)
|
||||
return {
|
||||
...project,
|
||||
backgroundInformation: null,
|
||||
backgroundBlurHash: '',
|
||||
}
|
||||
const response = await this.http.delete(`/projects/${project.id}/background`, project)
|
||||
return response.data
|
||||
} finally {
|
||||
cancel()
|
||||
}
|
||||
|
|
|
@ -37,8 +37,6 @@ function redirectToProviderIfNothingElseIsEnabled() {
|
|||
}
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const configStore = useConfigStore()
|
||||
|
||||
const authenticated = ref(false)
|
||||
const isLinkShareAuth = ref(false)
|
||||
const needsTotpPasscode = ref(false)
|
||||
|
@ -187,7 +185,8 @@ export const useAuthStore = defineStore('auth', () => {
|
|||
const HTTP = HTTPFactory()
|
||||
setIsLoading(true)
|
||||
|
||||
const fullProvider: IProvider = configStore.auth.openidConnect.providers.find((p: IProvider) => p.key === provider)
|
||||
const {auth} = useConfigStore()
|
||||
const fullProvider: IProvider = auth.openidConnect.providers.find((p: IProvider) => p.key === provider)
|
||||
|
||||
const data = {
|
||||
code: code,
|
||||
|
@ -358,15 +357,8 @@ export const useAuthStore = defineStore('auth', () => {
|
|||
|
||||
const cancel = setModuleLoading(setIsLoadingGeneralSettings)
|
||||
try {
|
||||
let settingsUpdate = {...settings}
|
||||
if (configStore.demoModeEnabled) {
|
||||
settingsUpdate = {
|
||||
...settingsUpdate,
|
||||
language: null,
|
||||
}
|
||||
}
|
||||
const updateSettingsPromise = userSettingsService.update(settingsUpdate)
|
||||
setUserSettings(settingsUpdate)
|
||||
const updateSettingsPromise = userSettingsService.update(settings)
|
||||
setUserSettings({...settings})
|
||||
await setLanguage(settings.language)
|
||||
await updateSettingsPromise
|
||||
if (showMessage) {
|
||||
|
@ -411,12 +403,13 @@ export const useAuthStore = defineStore('auth', () => {
|
|||
await checkAuth()
|
||||
|
||||
// if configured, redirect to OIDC Provider on logout
|
||||
const {auth} = useConfigStore()
|
||||
if (
|
||||
configStore.auth.local.enabled === false &&
|
||||
configStore.auth.openidConnect.enabled &&
|
||||
configStore.auth.openidConnect.providers?.length === 1)
|
||||
auth.local.enabled === false &&
|
||||
auth.openidConnect.enabled &&
|
||||
auth.openidConnect.providers?.length === 1)
|
||||
{
|
||||
redirectToProviderOnLogout(configStore.auth.openidConnect.providers[0])
|
||||
redirectToProviderOnLogout(auth.openidConnect.providers[0])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {computed, reactive, toRefs} from 'vue'
|
||||
import {acceptHMRUpdate, defineStore} from 'pinia'
|
||||
import {defineStore, acceptHMRUpdate} from 'pinia'
|
||||
import {parseURL} from 'ufo'
|
||||
|
||||
import {HTTPFactory} from '@/helpers/fetcher'
|
||||
|
@ -7,7 +7,6 @@ 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,
|
||||
|
@ -84,17 +83,15 @@ 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') {
|
||||
throw new InvalidApiUrlProvidedError()
|
||||
return false
|
||||
}
|
||||
|
||||
setConfig(objectToCamelCase(config))
|
||||
return !!config
|
||||
const success = !!config
|
||||
return success
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -160,6 +160,7 @@ export const useLabelStore = defineStore('label', () => {
|
|||
deleteLabel,
|
||||
updateLabel,
|
||||
createLabel,
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -14,4 +14,33 @@
|
|||
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(() => !!currentProject.value.backgroundInformation)
|
||||
const hasBackground = computed(() => baseStore.background !== null)
|
||||
|
||||
// Show the default collection of backgrounds
|
||||
newBackgroundSearch()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import CreateEdit from '@/components/misc/create-edit.vue'
|
||||
import {watch, ref, computed} from 'vue'
|
||||
import {computed, ref} from 'vue'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
import ProjectViewModel from '@/models/projectView'
|
||||
import type {IProjectView} from '@/modelTypes/IProjectView'
|
||||
|
@ -9,10 +9,6 @@ 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,
|
||||
|
@ -32,17 +28,6 @@ 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
|
||||
|
@ -98,17 +83,13 @@ 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
|
||||
v-if="isAdmin"
|
||||
class="is-flex is-justify-content-end mb-4"
|
||||
>
|
||||
<div class="is-flex is-justify-content-end mb-4">
|
||||
<XButton
|
||||
:loading="projectViewService.loading"
|
||||
@click="createView"
|
||||
|
@ -116,10 +97,6 @@ async function saveView() {
|
|||
{{ $t('project.views.create') }}
|
||||
</XButton>
|
||||
</div>
|
||||
|
||||
<Message v-if="!isAdmin">
|
||||
{{ $t('project.views.onlyAdminsCanEdit') }}
|
||||
</Message>
|
||||
|
||||
<table
|
||||
v-if="views?.length > 0"
|
||||
|
@ -167,7 +144,6 @@ 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="() => {
|
||||
|
@ -176,7 +152,6 @@ async function saveView() {
|
|||
}"
|
||||
/>
|
||||
<XButton
|
||||
v-if="isAdmin"
|
||||
icon="pen"
|
||||
@click="viewToEdit = {...v}"
|
||||
/>
|
||||
|
|
|
@ -309,7 +309,6 @@
|
|||
v-model="task.labels"
|
||||
:disabled="!canWrite"
|
||||
:task-id="taskId"
|
||||
:creatable="!authStore.isLinkShareAuth"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -654,7 +653,6 @@ 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)
|
||||
|
@ -879,7 +877,7 @@ function toggleTaskDone() {
|
|||
done: !task.value.done,
|
||||
}
|
||||
|
||||
if (newTask.done && authStore.settings.frontendSettings.playSoundWhenDone) {
|
||||
if (newTask.done && useAuthStore().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-20240422154211-76c06c4bde6b
|
||||
github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2
|
||||
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.23.0
|
||||
golang.org/x/image v0.16.0
|
||||
golang.org/x/oauth2 v0.20.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/image v0.15.0
|
||||
golang.org/x/oauth2 v0.19.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/term v0.20.0
|
||||
golang.org/x/text v0.15.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/term v0.19.0
|
||||
golang.org/x/text v0.14.0
|
||||
gopkg.in/d4l3k/messagediff.v1 v1.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
|
@ -120,6 +120,7 @@ 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
|
||||
|
@ -180,6 +181,7 @@ 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
|
||||
|
@ -190,4 +192,4 @@ replace github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20
|
|||
|
||||
go 1.21
|
||||
|
||||
toolchain go1.22.3
|
||||
toolchain go1.21.2
|
||||
|
|
25
go.sum
25
go.sum
|
@ -103,8 +103,6 @@ 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=
|
||||
|
@ -191,6 +189,8 @@ 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,6 +318,8 @@ 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=
|
||||
|
@ -501,6 +503,8 @@ 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=
|
||||
|
@ -574,15 +578,11 @@ 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,27 +654,22 @@ 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=
|
||||
|
@ -701,6 +696,8 @@ 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,9 +95,6 @@ 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,135 +349,82 @@ 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",
|
||||
"view": "3",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum"}`)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"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"}`)
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9999"}, `{"title":"Lorem Ipsum"}`)
|
||||
require.Error(t, err)
|
||||
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)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
|
||||
})
|
||||
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",
|
||||
"view": "80",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"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",
|
||||
"view": "24",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "6"}, `{"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",
|
||||
"view": "28",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "7"}, `{"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",
|
||||
"view": "32",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "8"}, `{"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",
|
||||
"view": "36",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"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",
|
||||
"view": "40",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"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",
|
||||
"view": "44",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "11"}, `{"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",
|
||||
"view": "48",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "12"}, `{"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",
|
||||
"view": "52",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "13"}, `{"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",
|
||||
"view": "56",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "14"}, `{"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",
|
||||
"view": "60",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "15"}, `{"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",
|
||||
"view": "64",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "16"}, `{"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",
|
||||
"view": "68",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "17"}, `{"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 UQE_buckets_id;
|
||||
drop index if exists buckets.UQE_buckets_id;
|
||||
|
||||
drop table buckets;
|
||||
|
||||
|
|
|
@ -23,13 +23,11 @@ import (
|
|||
|
||||
// CanCreate checks if a user can create a new bucket
|
||||
func (b *Bucket) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
|
||||
pv, err := GetProjectViewByIDAndProject(s, b.ProjectViewID, b.ProjectID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
pv := &ProjectView{
|
||||
ID: b.ProjectViewID,
|
||||
ProjectID: b.ProjectID,
|
||||
}
|
||||
|
||||
p := &Project{ID: pv.ProjectID}
|
||||
return p.CanUpdate(s, a)
|
||||
return pv.CanUpdate(s, a)
|
||||
}
|
||||
|
||||
// CanUpdate checks if a user can update an existing bucket
|
||||
|
@ -48,11 +46,9 @@ func (b *Bucket) canDoBucket(s *xorm.Session, a web.Auth) (bool, error) {
|
|||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
pv, err := GetProjectViewByIDAndProject(s, bb.ProjectViewID, b.ProjectID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
pv := &ProjectView{
|
||||
ID: bb.ProjectViewID,
|
||||
ProjectID: b.ProjectID,
|
||||
}
|
||||
|
||||
p := &Project{ID: pv.ProjectID}
|
||||
return p.CanUpdate(s, a)
|
||||
return pv.CanUpdate(s, a)
|
||||
}
|
||||
|
|
|
@ -148,14 +148,23 @@ 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: a,
|
||||
User: u,
|
||||
GetForUser: u.ID,
|
||||
Page: page,
|
||||
PerPage: perPage,
|
||||
GetUnusedLabels: true,
|
||||
GroupByLabelIDsOnly: true,
|
||||
GetForUser: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
|
@ -63,29 +64,27 @@ 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) {
|
||||
|
||||
linkShare, isLinkShare := a.(*LinkSharing)
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return false, 0, nil
|
||||
}
|
||||
|
||||
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()
|
||||
u, err := user.GetUserByID(s, a.GetID())
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
|
||||
cond := builder.In("label_tasks.task_id",
|
||||
builder.
|
||||
Select("id").
|
||||
From("tasks").
|
||||
Where(where),
|
||||
Where(builder.In("project_id", getUserProjectsStatement(u.ID, "", false).Select("l.id"))),
|
||||
)
|
||||
|
||||
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 = ?", createdByID).
|
||||
Where("label_tasks.label_id is not null OR labels.created_by_id = ?", u.ID).
|
||||
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: a,
|
||||
User: &user.User{ID: a.GetID()},
|
||||
Search: []string{search},
|
||||
Page: page,
|
||||
TaskIDs: []int64{lt.TaskID},
|
||||
|
@ -159,26 +159,23 @@ type LabelWithTaskID struct {
|
|||
|
||||
// LabelByTaskIDsOptions is a struct to not clutter the function with too many optional parameters.
|
||||
type LabelByTaskIDsOptions struct {
|
||||
User web.Auth
|
||||
User *user.User
|
||||
Search []string
|
||||
Page int
|
||||
PerPage int
|
||||
TaskIDs []int64
|
||||
GetUnusedLabels bool
|
||||
GroupByLabelIDsOnly bool
|
||||
GetForUser bool
|
||||
GetForUser int64
|
||||
}
|
||||
|
||||
// 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 ideataskdetaill solution.
|
||||
// Probably not the most ideal solution.
|
||||
var groupBy = "labels.id,label_tasks.task_id"
|
||||
var selectStmt = "labels.*, label_tasks.task_id"
|
||||
if opts.GroupByLabelIDsOnly {
|
||||
|
@ -189,25 +186,20 @@ 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 {
|
||||
if len(opts.TaskIDs) > 0 && opts.GetForUser == 0 {
|
||||
cond = builder.And(builder.In("label_tasks.task_id", opts.TaskIDs), cond)
|
||||
}
|
||||
if opts.GetForUser {
|
||||
if opts.GetForUser != 0 {
|
||||
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
cond = builder.And(builder.In("label_tasks.task_id",
|
||||
|
@ -217,8 +209,8 @@ func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*Lab
|
|||
Where(builder.In("project_id", projectIDs)),
|
||||
), cond)
|
||||
}
|
||||
if opts.GetUnusedLabels && !isLinkShareAuth {
|
||||
cond = builder.Or(cond, builder.Eq{"labels.created_by_id": opts.User.GetID()})
|
||||
if opts.GetUnusedLabels {
|
||||
cond = builder.Or(cond, builder.Eq{"labels.created_by_id": opts.User.ID})
|
||||
}
|
||||
|
||||
ids := []int64{}
|
||||
|
|
|
@ -62,7 +62,7 @@ func TestLabel_ReadAll(t *testing.T) {
|
|||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantLs []*LabelWithTaskID
|
||||
wantLs interface{}
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
|
@ -143,10 +143,7 @@ func TestLabel_ReadAll(t *testing.T) {
|
|||
t.Errorf("Label.ReadAll() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
got := gotLs.([]*LabelWithTaskID)
|
||||
|
||||
if diff, equal := messagediff.PrettyDiff(got, tt.wantLs); !equal {
|
||||
if diff, equal := messagediff.PrettyDiff(gotLs, 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.IsAdmin(s, a)
|
||||
return pp.CanUpdate(s, a)
|
||||
}
|
||||
|
||||
func (p *ProjectView) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
|
||||
pp := p.getProject()
|
||||
return pp.IsAdmin(s, a)
|
||||
return pp.CanUpdate(s, a)
|
||||
}
|
||||
|
||||
func (p *ProjectView) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
|
||||
pp := p.getProject()
|
||||
return pp.IsAdmin(s, a)
|
||||
return pp.CanUpdate(s, a)
|
||||
}
|
||||
|
||||
func (p *ProjectView) getProject() (pp *Project) {
|
||||
|
|
|
@ -640,7 +640,6 @@ 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,10 +38,6 @@ 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)
|
||||
|
@ -67,6 +63,6 @@ func RegisterEmptyOpenIDTeamCleanupCron() {
|
|||
}
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Could not register empty openid teams cleanup cron: %s", err)
|
||||
log.Fatalf("Could not empty openid teams cleanup cron: %s", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,8 +32,6 @@ 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,13 +17,10 @@
|
|||
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
|
||||
|
@ -213,12 +210,6 @@ 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)
|
||||
|
@ -229,30 +220,7 @@ 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)
|
||||
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)
|
||||
f, err := files.Create(srcf, 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+"&fm=jpg&h="+strconv.FormatInt(background.MaxBackgroundImageHeight, 10)+"&q=80", nil)
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, photo.Urls.Raw+"&w=2560&h=2048&q=90", nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -150,10 +150,36 @@ func Restore(filename string) error {
|
|||
|
||||
delete(dbfiles, "migration")
|
||||
|
||||
err = restoreTableData(dbfiles)
|
||||
if err != nil {
|
||||
return err
|
||||
// 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)
|
||||
}
|
||||
log.Infof("Restored %d tables", len(dbfiles))
|
||||
|
||||
// Run migrations again to migrate a potentially outdated dump
|
||||
migration.Migrate(nil)
|
||||
|
@ -190,53 +216,6 @@ 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: true,
|
||||
GetForUser: u.ID,
|
||||
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, "", 0, -1)
|
||||
iface, _, _, err := tk.ReadAll(s, vcls.user, "", 1, 1000)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return rr, err
|
||||
|
|
Loading…
Reference in New Issue