Compare commits
26
Commits
v1.10.2
...
planka-0.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d28abc7e6 | ||
|
|
5317950b5c | ||
|
|
7501619b7e | ||
|
|
080e29aa03 | ||
|
|
48c532b2c2 | ||
|
|
a62815f825 | ||
|
|
6938232dc4 | ||
|
|
4c4cfa9c66 | ||
|
|
ba89d41b19 | ||
|
|
5c435414f7 | ||
|
|
2d7812a679 | ||
|
|
d386c82973 | ||
|
|
6c4db967c2 | ||
|
|
932d8225ed | ||
|
|
0d481703da | ||
|
|
23e2b4a622 | ||
|
|
f4cc51e1c0 | ||
|
|
793ddd5e20 | ||
|
|
3d84888eb1 | ||
|
|
02eabd203c | ||
|
|
d0a2734161 | ||
|
|
eea57ff121 | ||
|
|
7e2b74627c | ||
|
|
bee199c13b | ||
|
|
8dd331b895 | ||
|
|
13f26b4be1 |
@@ -0,0 +1,42 @@
|
|||||||
|
name: Release Charts
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- "charts/**"
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
# depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions
|
||||||
|
# see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config user.name "$GITHUB_ACTOR"
|
||||||
|
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||||
|
|
||||||
|
- name: Install Helm
|
||||||
|
uses: azure/setup-helm@v3
|
||||||
|
|
||||||
|
- name: Add repositories
|
||||||
|
run: |
|
||||||
|
for dir in $(ls -d charts/*/); do
|
||||||
|
helm dependency list $dir 2> /dev/null | tail +2 | head -n -1 | awk '{ print "helm repo add " $1 " " $3 }' | while read cmd; do $cmd; done
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Run chart-releaser for stable
|
||||||
|
uses: helm/chart-releaser-action@v1.5.0
|
||||||
|
with:
|
||||||
|
charts_dir: charts
|
||||||
|
env:
|
||||||
|
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
@@ -10,3 +10,6 @@ docker-compose.override.yml
|
|||||||
# `npm i --package-lock-only`
|
# `npm i --package-lock-only`
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
|
||||||
|
# Chart dependencies
|
||||||
|
**/charts/*.tgz
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
# Planka
|
# Planka
|
||||||
|
#### Elegant open source project tracking
|
||||||
|
|
||||||
  
|
  
|
||||||
|
|
||||||
A Trello-like kanban board built with React and Redux.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
[**Client demo**](https://plankanban.github.io/planka) (without server features).
|
[**Client demo**](https://plankanban.github.io/planka) (without server features).
|
||||||
@@ -19,306 +18,24 @@ A Trello-like kanban board built with React and Redux.
|
|||||||
- User notifications
|
- User notifications
|
||||||
- Internationalization
|
- Internationalization
|
||||||
|
|
||||||
## Deploy
|
## How to deploy Planka
|
||||||
|
|
||||||
There are 2 types of installation:
|
There are 2 types of installation:
|
||||||
1. [Dockerized](#1-docker-compose)
|
- [Without Docker](https://docs.planka.cloud/docs/installl-planka/Debian%20&%20Ubuntu) ([for Windows](https://docs.planka.cloud/docs/installl-planka/Windows))
|
||||||
2. [Without Docker](#2-without-docker)
|
- [Dockerized](https://docs.planka.cloud/docs/installl-planka/Docker%20Compose)
|
||||||
|
|
||||||
### 1. Docker Compose
|
For configuration, please see the [configuration section](https://docs.planka.cloud/docs/category/configuration).
|
||||||
|
|
||||||
[](https://github.com/plankanban/planka/pkgs/container/planka)
|
## Contact
|
||||||
|
|
||||||
- Make sure you have [Docker](https://docs.docker.com/install/) and [Docker Compose](https://docs.docker.com/compose/install/) installed and operational.
|
- If you want to get a hosted version of Planka, you can contact us via email contact@planka.cloud
|
||||||
- Create `docker-compose.yml` based on [the example](https://raw.githubusercontent.com/plankanban/planka/master/docker-compose.yml). This is the ONLY file you will need. You can create this file on your own machine by copy and pasting the content.
|
- For any security issues, please do not create a public issue on GitHub, instead please write to security@planka.cloud
|
||||||
- Edit `BASE_URL` to match your domain name or IP address.
|
|
||||||
- Edit `SECRET_KEY` with random value. You can generate it by `openssl rand -hex 64`.
|
|
||||||
|
|
||||||
Download the docker-compose.yml:
|
We do NOT offer any public support via email, please use GitHub.
|
||||||
|
|
||||||
```
|
|
||||||
curl -L https://raw.githubusercontent.com/plankanban/planka/master/docker-compose.yml -o docker-compose.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
Pull images and start services:
|
|
||||||
|
|
||||||
```
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Demo user: demo@demo.demo demo
|
|
||||||
|
|
||||||
### 2. Without Docker
|
|
||||||
|
|
||||||
Installing without Docker is a bit more complicated, here's what you need to do:
|
|
||||||
|
|
||||||
1. Clone this repository into a directory of your choice. (e.g. `/var/www/planka`)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p /var/www/planka
|
|
||||||
cd /var/www/planka
|
|
||||||
git clone https://github.com/plankanban/planka.git .
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Install dependencies and build the client.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
npm run client:build
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: You can use `yarn` or `pnpm` instead of `npm`.
|
|
||||||
|
|
||||||
3. Copy the content of the `client/build` directory to the server.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp -r client/build/. server/public
|
|
||||||
cp client/build/index.html server/views/index.ejs
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Copy the start script and navigate to the `server` directory.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp start.sh server
|
|
||||||
cd server
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Configure environment variables.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp .env.sample .env
|
|
||||||
|
|
||||||
# Edit .env file (You could use nano, vim, etc.)
|
|
||||||
nano .env
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: Before continuing, make sure you have your selected database created and running.
|
|
||||||
|
|
||||||
6. Start the server.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./start.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: You can use `pm2` or `systemd` to run the server in the background.
|
|
||||||
|
|
||||||
## Additional information
|
|
||||||
|
|
||||||
### Nginx configuration
|
|
||||||
|
|
||||||
Here is an example of Nginx configuration for Planka, make sure to replace `<domain>` with your domain name, and make sure to configure the SSL.
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
upstream planka {
|
|
||||||
server localhost:1337;
|
|
||||||
keepalive 32;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
listen [::]:443 ssl http2;
|
|
||||||
server_name <domain>;
|
|
||||||
|
|
||||||
access_log /var/log/nginx/planka-access.log;
|
|
||||||
error_log /var/log/nginx/planka-error.log error;
|
|
||||||
|
|
||||||
# SSL Configuration - Replace the example <domain> with your domain
|
|
||||||
ssl_certificate /etc/letsencrypt/live/<domain>/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/<domain>/privkey.pem;
|
|
||||||
ssl_session_cache shared:SSL:10m;
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
|
|
||||||
ssl_prefer_server_ciphers on;
|
|
||||||
|
|
||||||
client_max_body_size 120M;
|
|
||||||
add_header Access-Control-Allow-Origin *;
|
|
||||||
add_header Access-Control-Max-Age 3600;
|
|
||||||
add_header Access-Control-Expose-Headers Content-Length;
|
|
||||||
add_header Access-Control-Allow-Headers Range;
|
|
||||||
|
|
||||||
# Make sure to allow socket.io connections
|
|
||||||
location ~* \.io {
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
client_max_body_size 50M;
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Frame-Options SAMEORIGIN;
|
|
||||||
proxy_buffers 256 16k;
|
|
||||||
proxy_buffer_size 16k;
|
|
||||||
client_body_timeout 60;
|
|
||||||
send_timeout 300;
|
|
||||||
lingering_timeout 5;
|
|
||||||
proxy_connect_timeout 1d;
|
|
||||||
proxy_send_timeout 1d;
|
|
||||||
proxy_read_timeout 1d;
|
|
||||||
proxy_pass http://planka;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
client_max_body_size 50M;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Frame-Options SAMEORIGIN;
|
|
||||||
proxy_buffers 256 16k;
|
|
||||||
proxy_buffer_size 16k;
|
|
||||||
proxy_read_timeout 600s;
|
|
||||||
proxy_cache_revalidate on;
|
|
||||||
proxy_cache_min_uses 2;
|
|
||||||
proxy_cache_use_stale timeout;
|
|
||||||
proxy_cache_lock on;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_pass http://planka;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Logging
|
|
||||||
|
|
||||||
Planka currently allows you to expose the application's logfile directory to the host machine via a shared volume. This feature is not enabled by default.
|
|
||||||
|
|
||||||
To expose the logfile director to the host machine, add the item `./logs/:/app/logs/` under `services.planka.volumes`.
|
|
||||||
|
|
||||||
Note that the directory to the left of the semicolon is regarding the host machine while the directory to the right of the semicolon is regarding the Docker container.
|
|
||||||
|
|
||||||
For example, in the above step, `./logs/:/app/logs/` will create the folder `logs` in the same directory where the `docker-compose.yml` file lives.
|
|
||||||
|
|
||||||
### Rotating Logs
|
|
||||||
|
|
||||||
Logrotate is designed to ease administration of systems that generate large numbers of log files. It allows automatic rotation, compression, removal, and mailing of log files. Each log file may be handled daily, weekly, monthly, or when it grows too large.
|
|
||||||
|
|
||||||
#### Setup logrotate for Planka logs
|
|
||||||
|
|
||||||
Create a file in `/etc/logrotate.d` named `planka` with the following contents:
|
|
||||||
|
|
||||||
```
|
|
||||||
/path/to/planka/logs/planka.log {
|
|
||||||
daily
|
|
||||||
missingok
|
|
||||||
rotate 14
|
|
||||||
compress
|
|
||||||
delaycompress
|
|
||||||
notifempty
|
|
||||||
create 640 root adm
|
|
||||||
sharedscripts
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Ensure to replace logfile directory with your installation’s `/logs/planka.log` location.
|
|
||||||
|
|
||||||
Restart the logrotate service.
|
|
||||||
|
|
||||||
### Fail2ban
|
|
||||||
|
|
||||||
Fail2ban is a service that uses iptables to automatically drop connections for a pre-defined amount of time from IPs that continuously failed to authenticate to the configured services.
|
|
||||||
|
|
||||||
#### Setup a filter and a jail for Planka
|
|
||||||
|
|
||||||
A filter defines regex rules to identify when users fail to authenticate on Planka's user interface.
|
|
||||||
|
|
||||||
Create a file in `/etc/fail2ban/filter.d` named `planka.conf` with the following contents:
|
|
||||||
|
|
||||||
```conf
|
|
||||||
[Definition]
|
|
||||||
failregex = ^(.*) Invalid (email or username:|password!) (\"(.*)\"!)? ?\(IP: <ADDR>\)$
|
|
||||||
ignoreregex =
|
|
||||||
```
|
|
||||||
|
|
||||||
The jail file defines how to handle the failed authentication attempts found by the Planka filter.
|
|
||||||
|
|
||||||
Create a file in `/etc/fail2ban/jail.d` named `planka.local` with the following contents:
|
|
||||||
|
|
||||||
```conf
|
|
||||||
[planka]
|
|
||||||
enabled = true
|
|
||||||
port = http,https
|
|
||||||
filter = planka
|
|
||||||
logpath = /path/to/planka/logs/planka.log
|
|
||||||
maxretry = 5
|
|
||||||
bantime = 900
|
|
||||||
```
|
|
||||||
|
|
||||||
Ensure to replace `logpath`'s value with your installation’s `/logs/planka.log` location. If you are using ports other than 80 and 443 for your Web server you should replace those too. The bantime and findtime are defined in seconds.
|
|
||||||
|
|
||||||
Restart the fail2ban service. You can check the status of your Planka jail by running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
fail2ban-client status planka
|
|
||||||
```
|
|
||||||
|
|
||||||
### Import from Trello
|
|
||||||
|
|
||||||
It's already available in Planka, or you can also use the great tool [trello2planka](https://github.com/christophenne/trello2planka) to do the import.
|
|
||||||
|
|
||||||
### Backup and Restore
|
|
||||||
|
|
||||||
Planka comes with two scripts that allow for manual backup and restore when running Planka with docker-compose.yml.
|
|
||||||
Backups can be triggered with `docker-backup.sh` which will export the Database, User Avatars, Project Backgrounds and Attachments into a single tgz file.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ./docker-backup.sh
|
|
||||||
Exporting postgres database ... Success!
|
|
||||||
Exporting user-avatars ... Success!
|
|
||||||
Exporting project-background-images ... Success!
|
|
||||||
Exporting attachments ... Success!
|
|
||||||
Creating final tarball 2023-01-17T15-37-22Z-backup.tgz ... Success!
|
|
||||||
Cleaning up temporary files and folders ... Success!
|
|
||||||
Backup Complete!
|
|
||||||
```
|
|
||||||
|
|
||||||
The resulting backup can be restored using the `docker-restore.sh` script.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ./docker-restore.sh 2023-01-17T15-37-22Z-backup.tgz
|
|
||||||
Extracting tarball 2023-01-17T11-10-54Z-backup.tgz ... Success!
|
|
||||||
Importing postgres database ...
|
|
||||||
|
|
||||||
[Many lines of postgres output]
|
|
||||||
...
|
|
||||||
|
|
||||||
Success!
|
|
||||||
Importing user-avatars ... Success!
|
|
||||||
Importing project-background-images ... Success!
|
|
||||||
Importing attachments ... Success!
|
|
||||||
Cleaning up temporary files and folders ... Success!
|
|
||||||
Restore complete!
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
Clone the repository and install dependencies:
|
See the [development section](https://docs.planka.cloud/docs/Development).
|
||||||
|
|
||||||
```
|
|
||||||
git clone https://github.com/plankanban/planka.git
|
|
||||||
|
|
||||||
cd planka
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
Either use a local database or start the provided development database:
|
|
||||||
|
|
||||||
```
|
|
||||||
docker-compose -f docker-compose-dev.yml up
|
|
||||||
```
|
|
||||||
|
|
||||||
Create `server/.env` based on `server/.env.sample` and edit `DATABASE_URL` if needed, then initialize the database:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run server:db:init
|
|
||||||
```
|
|
||||||
|
|
||||||
Start the development server:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
Demo user: demo@demo.demo demo
|
|
||||||
|
|
||||||
## Tech stack
|
## Tech stack
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ Most recent release.
|
|||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
Please report any security issues you discovered to meltyshev@gmail.com. If the issue is confirmed, we will release a patch as soon as possible depending on complexity.
|
Please report any security issues you discovered to security@planka.cloud. If the issue is confirmed, we will release a patch as soon as possible depending on complexity.
|
||||||
|
|
||||||
**Do NOT create public issues on GitHub for security vulnerabilities.**
|
**Do NOT create public issues on GitHub for security vulnerabilities.**
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Patterns to ignore when building packages.
|
||||||
|
# This supports shell glob matching, relative path matching, and
|
||||||
|
# negation (prefixed with !). Only one pattern per line.
|
||||||
|
.DS_Store
|
||||||
|
# Common VCS dirs
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.bzr/
|
||||||
|
.bzrignore
|
||||||
|
.hg/
|
||||||
|
.hgignore
|
||||||
|
.svn/
|
||||||
|
# Common backup files
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*.orig
|
||||||
|
*~
|
||||||
|
# Various IDEs
|
||||||
|
.project
|
||||||
|
.idea/
|
||||||
|
*.tmproj
|
||||||
|
.vscode/
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
dependencies:
|
||||||
|
- name: postgresql
|
||||||
|
repository: https://charts.bitnami.com/bitnami
|
||||||
|
version: 12.5.1
|
||||||
|
digest: sha256:01dfb2d07ab6800b4a5a6c81f20f3377a758124b2b96b891d0cd6b4f64cf783b
|
||||||
|
generated: "2023-05-15T00:54:48.1308917+01:00"
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: planka
|
||||||
|
description: A Helm chart to deploy Planka and it's dependencies.
|
||||||
|
|
||||||
|
# A chart can be either an 'application' or a 'library' chart.
|
||||||
|
#
|
||||||
|
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||||
|
# to be deployed.
|
||||||
|
#
|
||||||
|
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||||
|
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||||
|
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||||
|
type: application
|
||||||
|
|
||||||
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
|
# to the chart and its templates, including the app version.
|
||||||
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
|
version: 0.1.2
|
||||||
|
|
||||||
|
# This is the version number of the application being deployed. This version number should be
|
||||||
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
|
# It is recommended to use it with quotes.
|
||||||
|
appVersion: "1.12.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- alias: postgresql
|
||||||
|
condition: postgresql.enabled
|
||||||
|
name: postgresql
|
||||||
|
repository: &bitnami-repo https://charts.bitnami.com/bitnami
|
||||||
|
version: 12.5.1
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# Planka Helm Chart
|
||||||
|
|
||||||
|
[Planka](https://github.com/plankanban/planka) is an OSS alternative to Trello that you can host yourself, and this is a Helm Chart to make it easier to deploy to K8s.
|
||||||
|
|
||||||
|
Shoutout to [this issue](https://github.com/plankanban/planka/issues/192) who have been asking for this Helm Chart.
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
By using the Bitnami chart for PostgreSQL, there is an issue where once deployed, if trying to use a different password then it will be ignored as the Persistant Volume (PV) will already exist with the previous password. See warning from Bitnami below:
|
||||||
|
|
||||||
|
> **Warning!** Setting a password will be ignored on new installation in the case when previous Posgresql release was deleted through the helm command. In that case, old PVC will have an old password, and setting it through helm won't take effect. Deleting persistent volumes (PVs) will solve the issue. Refer to [issue 2061](https://github.com/bitnami/charts/issues/2061) for more details
|
||||||
|
|
||||||
|
If you want to fully uninstall this chart including the data, follow [these steps](https://github.com/bitnami/charts/blob/main/bitnami/postgresql/README.md#uninstalling-the-chart) from the Bitnami Chart's docs.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The basic usage of the chart can be found below:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/Chris-Greaves/planka-helm-chart.git
|
||||||
|
cd planka-helm-chart
|
||||||
|
helm dependency build
|
||||||
|
export SECRETKEY=$(openssl rand -hex 64)
|
||||||
|
helm install planka . --set secretkey=$SECRETKEY
|
||||||
|
```
|
||||||
|
|
||||||
|
> **NOTE:** The command `openssl rand -hex 64` is needed to create a random hexadecimal key for planka. On Windows you can use Git Bash to run that command.
|
||||||
|
|
||||||
|
To access Planka you can port forward using the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl port-forward $POD_NAME 3000:1337
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accessing Externally
|
||||||
|
|
||||||
|
To access Planka externally you can use the following configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# HTTP only
|
||||||
|
helm install planka . --set secretkey=$SECRETKEY \
|
||||||
|
--set ingress.enabled=true \
|
||||||
|
--set ingress.hosts[0].host=planka.example.dev \
|
||||||
|
|
||||||
|
# HTTPS
|
||||||
|
helm install planka . --set secretkey=$SECRETKEY \
|
||||||
|
--set ingress.enabled=true \
|
||||||
|
--set ingress.hosts[0].host=planka.example.dev \
|
||||||
|
--set ingress.tls[0].secretName=planka-tls \
|
||||||
|
--set ingress.tls[0].hosts[0]=planka.example.dev \
|
||||||
|
```
|
||||||
|
|
||||||
|
or create a values.yaml file like:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
secretkey: "<InsertSecretKey>"
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
hosts:
|
||||||
|
- host: planka.example.dev
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
|
||||||
|
# Needed for HTTPS
|
||||||
|
tls:
|
||||||
|
- secretName: planka-tls # existing TLS secret in k8s
|
||||||
|
hosts:
|
||||||
|
- planka.example.dev
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install planka . -f values.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Things to consider if production hosting
|
||||||
|
|
||||||
|
If you want to host Planka for more than just playing around with, you might want to do the following things:
|
||||||
|
|
||||||
|
- Create a `values.yaml` with your config, as this will make applying upgrades much easier in the future.
|
||||||
|
- Create your `secretkey` once and store it either in a secure vault, or in your `values.yaml` file so it will be the same for upgrading in the future.
|
||||||
|
- Specify a password for `postgresql.auth.password` as there have been issues with the postgresql chart generating new passwords locking you out of the data you've already stored. (see [this issue](https://github.com/bitnami/charts/issues/2061))
|
||||||
|
|
||||||
|
Any questions or concerns, [raise an issue](https://github.com/Chris-Greaves/planka-helm-chart/issues/new).
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
1. Get the application URL by running these commands:
|
||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
{{- range $host := .Values.ingress.hosts }}
|
||||||
|
{{- range .paths }}
|
||||||
|
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else if contains "NodePort" .Values.service.type }}
|
||||||
|
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "planka.fullname" . }})
|
||||||
|
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||||
|
echo http://$NODE_IP:$NODE_PORT
|
||||||
|
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||||
|
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||||
|
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "planka.fullname" . }}'
|
||||||
|
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "planka.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||||
|
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||||
|
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||||
|
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "planka.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||||
|
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||||
|
echo "Visit http://localhost:3000 to use your application"
|
||||||
|
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 3000:$CONTAINER_PORT
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "planka.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
If release name contains chart name it will be used as a full name.
|
||||||
|
*/}}
|
||||||
|
{{- define "planka.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride }}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
|
{{- if contains $name .Release.Name }}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create chart name and version as used by the chart label.
|
||||||
|
*/}}
|
||||||
|
{{- define "planka.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "planka.labels" -}}
|
||||||
|
helm.sh/chart: {{ include "planka.chart" . }}
|
||||||
|
{{ include "planka.selectorLabels" . }}
|
||||||
|
{{- if .Chart.AppVersion }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
{{- end }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "planka.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "planka.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create the name of the service account to use
|
||||||
|
*/}}
|
||||||
|
{{- define "planka.serviceAccountName" -}}
|
||||||
|
{{- if .Values.serviceAccount.create }}
|
||||||
|
{{- default (include "planka.fullname" .) .Values.serviceAccount.name }}
|
||||||
|
{{- else }}
|
||||||
|
{{- default "default" .Values.serviceAccount.name }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "planka.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "planka.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
{{- if not .Values.autoscaling.enabled }}
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
{{- end }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "planka.selectorLabels" . | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
{{- with .Values.podAnnotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "planka.selectorLabels" . | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
{{- with .Values.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
serviceAccountName: {{ include "planka.serviceAccountName" . }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: {{ .Values.service.port }}
|
||||||
|
protocol: TCP
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /app/public/user-avatars
|
||||||
|
subPath: user-avatars
|
||||||
|
name: planka
|
||||||
|
- mountPath: /app/public/project-background-images
|
||||||
|
subPath: project-background-images
|
||||||
|
name: planka
|
||||||
|
- mountPath: /app/private/attachments
|
||||||
|
subPath: attachments
|
||||||
|
name: planka
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
|
env:
|
||||||
|
- name: DATABASE_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: planka-postgresql-svcbind-custom-user
|
||||||
|
key: uri
|
||||||
|
- name: BASE_URL
|
||||||
|
{{- if .Values.baseUrl }}
|
||||||
|
value: {{ .Values.baseUrl }}
|
||||||
|
{{- else if .Values.ingress.enabled }}
|
||||||
|
value: {{ printf "https://%s" (first .Values.ingress.hosts).host }}
|
||||||
|
{{- else }}
|
||||||
|
value: http://localhost:3000
|
||||||
|
{{- end }}
|
||||||
|
- name: SECRET_KEY
|
||||||
|
value: {{ required "A secret key needs to be generated using 'openssl rand -hex 64' and assigned to secretkey." .Values.secretkey }}
|
||||||
|
- name: TRUST_PROXY
|
||||||
|
value: "0"
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
volumes:
|
||||||
|
- name: planka
|
||||||
|
{{- if .Values.persistence.enabled }}
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: {{ .Values.persistence.existingClaim | default (include "planka.fullname" .) }}
|
||||||
|
{{- else }}
|
||||||
|
emptyDir: {}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{{- if .Values.autoscaling.enabled }}
|
||||||
|
apiVersion: autoscaling/v2beta1
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: {{ include "planka.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "planka.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: {{ include "planka.fullname" . }}
|
||||||
|
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||||
|
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||||
|
metrics:
|
||||||
|
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: cpu
|
||||||
|
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: memory
|
||||||
|
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
{{- if .Values.ingress.enabled -}}
|
||||||
|
{{- $fullName := include "planka.fullname" . -}}
|
||||||
|
{{- $svcPort := .Values.service.port -}}
|
||||||
|
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||||
|
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||||
|
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||||
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
|
{{- else -}}
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
{{- end }}
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: {{ $fullName }}
|
||||||
|
labels:
|
||||||
|
{{- include "planka.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||||
|
ingressClassName: {{ .Values.ingress.className }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.ingress.tls }}
|
||||||
|
tls:
|
||||||
|
{{- range .Values.ingress.tls }}
|
||||||
|
- hosts:
|
||||||
|
{{- range .hosts }}
|
||||||
|
- {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
secretName: {{ .secretName }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
rules:
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
- host: {{ .host | quote }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
{{- range .paths }}
|
||||||
|
- path: {{ .path }}
|
||||||
|
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
|
||||||
|
pathType: {{ .pathType }}
|
||||||
|
{{- end }}
|
||||||
|
backend:
|
||||||
|
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||||
|
service:
|
||||||
|
name: {{ $fullName }}
|
||||||
|
port:
|
||||||
|
number: {{ $svcPort }}
|
||||||
|
{{- else }}
|
||||||
|
serviceName: {{ $fullName }}
|
||||||
|
servicePort: {{ $svcPort }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}
|
||||||
|
---
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: {{ include "planka.fullname" . }}
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: {{ include "planka.name" . }}
|
||||||
|
helm.sh/chart: {{ include "planka.chart" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- {{ .Values.persistence.accessMode }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.persistence.size | quote }}
|
||||||
|
{{- if .Values.persistence.storageClass }}
|
||||||
|
{{- if (eq "-" .Values.persistence.storageClass) }}
|
||||||
|
storageClassName: ""
|
||||||
|
{{- else }}
|
||||||
|
storageClassName: "{{ .Values.persistence.storageClass }}"
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "planka.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "planka.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.service.port }}
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
{{- include "planka.selectorLabels" . | nindent 4 }}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{{- if .Values.serviceAccount.create -}}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: {{ include "planka.serviceAccountName" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "planka.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.serviceAccount.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: "{{ include "planka.fullname" . }}-test-connection"
|
||||||
|
labels:
|
||||||
|
{{- include "planka.labels" . | nindent 4 }}
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": test
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: wget
|
||||||
|
image: busybox
|
||||||
|
command: ['wget']
|
||||||
|
args: ['{{ include "planka.fullname" . }}:{{ .Values.service.port }}']
|
||||||
|
restartPolicy: Never
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
# Default values for planka.
|
||||||
|
# This is a YAML-formatted file.
|
||||||
|
# Declare variables to be passed into your templates.
|
||||||
|
|
||||||
|
replicaCount: 1
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: ghcr.io/plankanban/planka
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
# Overrides the image tag whose default is the chart appVersion.
|
||||||
|
tag: ""
|
||||||
|
|
||||||
|
imagePullSecrets: []
|
||||||
|
nameOverride: ""
|
||||||
|
fullnameOverride: ""
|
||||||
|
|
||||||
|
# Generate a secret using openssl rand -base64 45
|
||||||
|
secretkey: ""
|
||||||
|
|
||||||
|
# Base url for Planka. Will override `ingress.hosts[0].host`
|
||||||
|
# Defaults to `http://localhost:3000` if ingress is disabled.
|
||||||
|
baseUrl: ""
|
||||||
|
|
||||||
|
serviceAccount:
|
||||||
|
# Specifies whether a service account should be created
|
||||||
|
create: true
|
||||||
|
# Annotations to add to the service account
|
||||||
|
annotations: {}
|
||||||
|
# The name of the service account to use.
|
||||||
|
# If not set and create is true, a name is generated using the fullname template
|
||||||
|
name: ""
|
||||||
|
|
||||||
|
podAnnotations: {}
|
||||||
|
|
||||||
|
podSecurityContext: {}
|
||||||
|
# fsGroup: 2000
|
||||||
|
|
||||||
|
securityContext: {}
|
||||||
|
# capabilities:
|
||||||
|
# drop:
|
||||||
|
# - ALL
|
||||||
|
# readOnlyRootFilesystem: true
|
||||||
|
# runAsNonRoot: true
|
||||||
|
# runAsUser: 1000
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 1337
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: false
|
||||||
|
className: ""
|
||||||
|
annotations: {}
|
||||||
|
# kubernetes.io/ingress.class: nginx
|
||||||
|
# kubernetes.io/tls-acme: "true"
|
||||||
|
hosts:
|
||||||
|
# Used to set planka BASE_URL if no `baseurl` is provided.
|
||||||
|
- host: planka.local
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
tls: []
|
||||||
|
# - secretName: planka-tls
|
||||||
|
# hosts:
|
||||||
|
# - planka.local
|
||||||
|
|
||||||
|
resources: {}
|
||||||
|
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||||
|
# choice for the user. This also increases chances charts run on environments with little
|
||||||
|
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||||
|
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||||
|
# limits:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 128Mi
|
||||||
|
# requests:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 128Mi
|
||||||
|
|
||||||
|
autoscaling:
|
||||||
|
enabled: false
|
||||||
|
minReplicas: 1
|
||||||
|
maxReplicas: 100
|
||||||
|
targetCPUUtilizationPercentage: 80
|
||||||
|
# targetMemoryUtilizationPercentage: 80
|
||||||
|
|
||||||
|
nodeSelector: {}
|
||||||
|
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
affinity: {}
|
||||||
|
|
||||||
|
postgresql:
|
||||||
|
enabled: true
|
||||||
|
auth:
|
||||||
|
database: planka
|
||||||
|
username: planka
|
||||||
|
password: ""
|
||||||
|
postgresPassword: ""
|
||||||
|
replicationPassword: ""
|
||||||
|
# existingSecret: planka-postgresql
|
||||||
|
serviceBindings:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
## PVC-based data storage configuration
|
||||||
|
persistence:
|
||||||
|
enabled: false
|
||||||
|
# existingClaim: netbox-data
|
||||||
|
# storageClass: "-"
|
||||||
|
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
size: 10Gi
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
REACT_APP_VERSION=1.10.2
|
REACT_APP_VERSION=1.12.0
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ const replaceBaseUrl = (compiler) => {
|
|||||||
if (info.content.indexOf(BASE_URL_PLACEHOLDER) >= 0) {
|
if (info.content.indexOf(BASE_URL_PLACEHOLDER) >= 0) {
|
||||||
if (/\.css$/.exec(info.targetPath)) {
|
if (/\.css$/.exec(info.targetPath)) {
|
||||||
// For CSS 'url(...)' import we can use relative import
|
// For CSS 'url(...)' import we can use relative import
|
||||||
const relPath = path.relative(path.dirname(info.targetPath), info.outputPath);
|
const relPath = path
|
||||||
|
.relative(path.dirname(info.targetPath), info.outputPath)
|
||||||
|
.replace(/\\/g, '/');
|
||||||
replaceInFile(info.targetPath, BASE_URL_PLACEHOLDER, `${relPath}/`);
|
replaceInFile(info.targetPath, BASE_URL_PLACEHOLDER, `${relPath}/`);
|
||||||
} else if (/\.js$/.exec(info.targetPath)) {
|
} else if (/\.js$/.exec(info.targetPath)) {
|
||||||
// For JS 'import ... from "some-asset"' we can get the variable injected in the window object
|
// For JS 'import ... from "some-asset"' we can get the variable injected in the window object
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
import { transformUser } from './users';
|
||||||
|
|
||||||
/* Transformers */
|
/* Transformers */
|
||||||
|
|
||||||
@@ -13,6 +14,10 @@ const getActivities = (cardId, data, headers) =>
|
|||||||
socket.get(`/cards/${cardId}/actions`, data, headers).then((body) => ({
|
socket.get(`/cards/${cardId}/actions`, data, headers).then((body) => ({
|
||||||
...body,
|
...body,
|
||||||
items: body.items.map(transformActivity),
|
items: body.items.map(transformActivity),
|
||||||
|
included: {
|
||||||
|
...body.included,
|
||||||
|
users: body.included.users.map(transformUser),
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/* Event handlers */
|
/* Event handlers */
|
||||||
|
|||||||
@@ -1,18 +1,50 @@
|
|||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
|
||||||
|
/* Transformers */
|
||||||
|
|
||||||
|
export const transformBoardMembership = (boardMembership) => ({
|
||||||
|
...boardMembership,
|
||||||
|
createdAt: new Date(boardMembership.createdAt),
|
||||||
|
});
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
const createBoardMembership = (boardId, data, headers) =>
|
const createBoardMembership = (boardId, data, headers) =>
|
||||||
socket.post(`/boards/${boardId}/memberships`, data, headers);
|
socket.post(`/boards/${boardId}/memberships`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformBoardMembership(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const updateBoardMembership = (id, data, headers) =>
|
const updateBoardMembership = (id, data, headers) =>
|
||||||
socket.patch(`/board-memberships/${id}`, data, headers);
|
socket.patch(`/board-memberships/${id}`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformBoardMembership(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const deleteBoardMembership = (id, headers) =>
|
const deleteBoardMembership = (id, headers) =>
|
||||||
socket.delete(`/board-memberships/${id}`, undefined, headers);
|
socket.delete(`/board-memberships/${id}`, undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformBoardMembership(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
|
/* Event handlers */
|
||||||
|
|
||||||
|
const makeHandleBoardMembershipCreate = (next) => (body) => {
|
||||||
|
next({
|
||||||
|
...body,
|
||||||
|
item: transformBoardMembership(body.item),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeHandleBoardMembershipUpdate = makeHandleBoardMembershipCreate;
|
||||||
|
|
||||||
|
const makeHandleBoardMembershipDelete = makeHandleBoardMembershipCreate;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createBoardMembership,
|
createBoardMembership,
|
||||||
updateBoardMembership,
|
updateBoardMembership,
|
||||||
deleteBoardMembership,
|
deleteBoardMembership,
|
||||||
|
makeHandleBoardMembershipCreate,
|
||||||
|
makeHandleBoardMembershipUpdate,
|
||||||
|
makeHandleBoardMembershipDelete,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
import http from './http';
|
import http from './http';
|
||||||
|
import { transformUser } from './users';
|
||||||
|
import { transformBoardMembership } from './board-memberships';
|
||||||
import { transformCard } from './cards';
|
import { transformCard } from './cards';
|
||||||
import { transformAttachment } from './attachments';
|
import { transformAttachment } from './attachments';
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
const createBoard = (projectId, data, headers) =>
|
const createBoard = (projectId, data, headers) =>
|
||||||
socket.post(`/projects/${projectId}/boards`, data, headers);
|
socket.post(`/projects/${projectId}/boards`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
included: {
|
||||||
|
...body.included,
|
||||||
|
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const createBoardWithImport = (projectId, data, requestId, headers) =>
|
const createBoardWithImport = (projectId, data, requestId, headers) =>
|
||||||
http.post(`/projects/${projectId}/boards?requestId=${requestId}`, data, headers);
|
http.post(`/projects/${projectId}/boards?requestId=${requestId}`, data, headers);
|
||||||
@@ -18,6 +26,8 @@ const getBoard = (id, subscribe, headers) =>
|
|||||||
...body,
|
...body,
|
||||||
included: {
|
included: {
|
||||||
...body.included,
|
...body.included,
|
||||||
|
users: body.included.users.map(transformUser),
|
||||||
|
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||||
cards: body.included.cards.map(transformCard),
|
cards: body.included.cards.map(transformCard),
|
||||||
attachments: body.included.attachments.map(transformAttachment),
|
attachments: body.included.attachments.map(transformAttachment),
|
||||||
},
|
},
|
||||||
|
|||||||
+10
-10
@@ -8,11 +8,11 @@ export const transformCard = (card) => ({
|
|||||||
...(card.dueDate && {
|
...(card.dueDate && {
|
||||||
dueDate: new Date(card.dueDate),
|
dueDate: new Date(card.dueDate),
|
||||||
}),
|
}),
|
||||||
...(card.timer && {
|
...(card.stopwatch && {
|
||||||
timer: {
|
stopwatch: {
|
||||||
...card.timer,
|
...card.stopwatch,
|
||||||
...(card.timer.startedAt && {
|
...(card.stopwatch.startedAt && {
|
||||||
startedAt: new Date(card.timer.startedAt),
|
startedAt: new Date(card.stopwatch.startedAt),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -23,11 +23,11 @@ export const transformCardData = (data) => ({
|
|||||||
...(data.dueDate && {
|
...(data.dueDate && {
|
||||||
dueDate: data.dueDate.toISOString(),
|
dueDate: data.dueDate.toISOString(),
|
||||||
}),
|
}),
|
||||||
...(data.timer && {
|
...(data.stopwatch && {
|
||||||
timer: {
|
stopwatch: {
|
||||||
...data.timer,
|
...data.stopwatch,
|
||||||
...(data.timer.startedAt && {
|
...(data.stopwatch.startedAt && {
|
||||||
startedAt: data.timer.startedAt.toISOString(),
|
startedAt: data.stopwatch.startedAt.toISOString(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
|
|
||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
import { transformUser } from './users';
|
||||||
import { transformCard } from './cards';
|
import { transformCard } from './cards';
|
||||||
import { transformActivity } from './activities';
|
import { transformActivity } from './activities';
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ const getNotifications = (headers) =>
|
|||||||
items: body.items.map(transformNotification),
|
items: body.items.map(transformNotification),
|
||||||
included: {
|
included: {
|
||||||
...omit(body.included, 'actions'),
|
...omit(body.included, 'actions'),
|
||||||
|
users: body.included.users.map(transformUser),
|
||||||
cards: body.included.cards.map(transformCard),
|
cards: body.included.cards.map(transformCard),
|
||||||
activities: body.included.actions.map(transformActivity),
|
activities: body.included.actions.map(transformActivity),
|
||||||
},
|
},
|
||||||
@@ -30,6 +32,7 @@ const getNotification = (id, headers) =>
|
|||||||
item: transformNotification(body.item),
|
item: transformNotification(body.item),
|
||||||
included: {
|
included: {
|
||||||
...omit(body.included, 'actions'),
|
...omit(body.included, 'actions'),
|
||||||
|
users: body.included.users.map(transformUser),
|
||||||
cards: body.included.cards.map(transformCard),
|
cards: body.included.cards.map(transformCard),
|
||||||
activities: body.included.actions.map(transformActivity),
|
activities: body.included.actions.map(transformActivity),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,14 +1,40 @@
|
|||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
|
||||||
|
/* Transformers */
|
||||||
|
|
||||||
|
export const transformProjectManager = (projectManager) => ({
|
||||||
|
...projectManager,
|
||||||
|
createdAt: new Date(projectManager.createdAt),
|
||||||
|
});
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
const createProjectManager = (projectId, data, headers) =>
|
const createProjectManager = (projectId, data, headers) =>
|
||||||
socket.post(`/projects/${projectId}/managers`, data, headers);
|
socket.post(`/projects/${projectId}/managers`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformProjectManager(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const deleteProjectManager = (id, headers) =>
|
const deleteProjectManager = (id, headers) =>
|
||||||
socket.delete(`/project-managers/${id}`, undefined, headers);
|
socket.delete(`/project-managers/${id}`, undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformProjectManager(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
|
/* Event handlers */
|
||||||
|
|
||||||
|
const makeHandleProjectManagerCreate = (next) => (body) => {
|
||||||
|
next({
|
||||||
|
...body,
|
||||||
|
item: transformProjectManager(body.item),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeHandleProjectManagerDelete = makeHandleProjectManagerCreate;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createProjectManager,
|
createProjectManager,
|
||||||
deleteProjectManager,
|
deleteProjectManager,
|
||||||
|
makeHandleProjectManagerCreate,
|
||||||
|
makeHandleProjectManagerDelete,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,41 @@
|
|||||||
import http from './http';
|
import http from './http';
|
||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
import { transformUser } from './users';
|
||||||
|
import { transformProjectManager } from './project-managers';
|
||||||
|
import { transformBoardMembership } from './board-memberships';
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
const getProjects = (headers) => socket.get('/projects', undefined, headers);
|
const getProjects = (headers) =>
|
||||||
|
socket.get('/projects', undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
included: {
|
||||||
|
...body.included,
|
||||||
|
users: body.included.users.map(transformUser),
|
||||||
|
projectManagers: body.included.projectManagers.map(transformProjectManager),
|
||||||
|
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const createProject = (data, headers) => socket.post('/projects', data, headers);
|
const createProject = (data, headers) =>
|
||||||
|
socket.post('/projects', data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
included: {
|
||||||
|
...body.included,
|
||||||
|
projectManagers: body.included.projectManagers.map(transformProjectManager),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const getProject = (id, headers) => socket.get(`/projects/${id}`, undefined, headers);
|
const getProject = (id, headers) =>
|
||||||
|
socket.get(`/projects/${id}`, undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
included: {
|
||||||
|
...body.included,
|
||||||
|
users: body.included.users.map(transformUser),
|
||||||
|
projectManagers: body.included.projectManagers.map(transformProjectManager),
|
||||||
|
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const updateProject = (id, data, headers) => socket.patch(`/projects/${id}`, data, headers);
|
const updateProject = (id, data, headers) => socket.patch(`/projects/${id}`, data, headers);
|
||||||
|
|
||||||
|
|||||||
+70
-10
@@ -1,30 +1,87 @@
|
|||||||
import http from './http';
|
import http from './http';
|
||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
|
||||||
|
/* Transformers */
|
||||||
|
|
||||||
|
export const transformUser = (user) => ({
|
||||||
|
...user,
|
||||||
|
createdAt: new Date(user.createdAt),
|
||||||
|
});
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
const getUsers = (headers) => socket.get('/users', undefined, headers);
|
const getUsers = (headers) =>
|
||||||
|
socket.get('/users', undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
items: body.items.map(transformUser),
|
||||||
|
}));
|
||||||
|
|
||||||
const createUser = (data, headers) => socket.post('/users', data, headers);
|
const createUser = (data, headers) =>
|
||||||
|
socket.post('/users', data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const getUser = (id, headers) => socket.get(`/users/${id}`, undefined, headers);
|
const getUser = (id, headers) =>
|
||||||
|
socket.get(`/users/${id}`, undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const getCurrentUser = (subscribe, headers) =>
|
const getCurrentUser = (subscribe, headers) =>
|
||||||
socket.get(`/users/me${subscribe ? '?subscribe=true' : ''}`, undefined, headers);
|
socket.get(`/users/me${subscribe ? '?subscribe=true' : ''}`, undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const updateUser = (id, data, headers) => socket.patch(`/users/${id}`, data, headers);
|
const updateUser = (id, data, headers) =>
|
||||||
|
socket.patch(`/users/${id}`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const updateUserEmail = (id, data, headers) => socket.patch(`/users/${id}/email`, data, headers);
|
const updateUserEmail = (id, data, headers) =>
|
||||||
|
socket.patch(`/users/${id}/email`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const updateUserPassword = (id, data, headers) =>
|
const updateUserPassword = (id, data, headers) =>
|
||||||
socket.patch(`/users/${id}/password`, data, headers);
|
socket.patch(`/users/${id}/password`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const updateUserUsername = (id, data, headers) =>
|
const updateUserUsername = (id, data, headers) =>
|
||||||
socket.patch(`/users/${id}/username`, data, headers);
|
socket.patch(`/users/${id}/username`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const updateUserAvatar = (id, data, headers) => http.post(`/users/${id}/avatar`, data, headers);
|
const updateUserAvatar = (id, data, headers) =>
|
||||||
|
http.post(`/users/${id}/avatar`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const deleteUser = (id, headers) => socket.delete(`/users/${id}`, undefined, headers);
|
const deleteUser = (id, headers) =>
|
||||||
|
socket.delete(`/users/${id}`, undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
|
/* Event handlers */
|
||||||
|
|
||||||
|
const makeHandleUserCreate = (next) => (body) => {
|
||||||
|
next({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeHandleUserUpdate = makeHandleUserCreate;
|
||||||
|
|
||||||
|
const makeHandleUserDelete = makeHandleUserCreate;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getUsers,
|
getUsers,
|
||||||
@@ -37,4 +94,7 @@ export default {
|
|||||||
updateUserUsername,
|
updateUserUsername,
|
||||||
updateUserAvatar,
|
updateUserAvatar,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
|
makeHandleUserCreate,
|
||||||
|
makeHandleUserUpdate,
|
||||||
|
makeHandleUserDelete,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ const Board = React.memo(
|
|||||||
|
|
||||||
const handleMouseDown = useCallback(
|
const handleMouseDown = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
|
// If button is defined and not equal to 0 (left click)
|
||||||
|
if (event.button) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.target !== wrapper.current && !event.target.dataset.dragScroller) {
|
if (event.target !== wrapper.current && !event.target.dataset.dragScroller) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useSteps } from '../../hooks';
|
|||||||
import BoardMembershipsStep from '../BoardMembershipsStep';
|
import BoardMembershipsStep from '../BoardMembershipsStep';
|
||||||
import LabelsStep from '../LabelsStep';
|
import LabelsStep from '../LabelsStep';
|
||||||
import DueDateEditStep from '../DueDateEditStep';
|
import DueDateEditStep from '../DueDateEditStep';
|
||||||
import TimerEditStep from '../TimerEditStep';
|
import StopwatchEditStep from '../StopwatchEditStep';
|
||||||
import CardMoveStep from '../CardMoveStep';
|
import CardMoveStep from '../CardMoveStep';
|
||||||
import DeleteStep from '../DeleteStep';
|
import DeleteStep from '../DeleteStep';
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ const StepTypes = {
|
|||||||
USERS: 'USERS',
|
USERS: 'USERS',
|
||||||
LABELS: 'LABELS',
|
LABELS: 'LABELS',
|
||||||
EDIT_DUE_DATE: 'EDIT_DUE_DATE',
|
EDIT_DUE_DATE: 'EDIT_DUE_DATE',
|
||||||
EDIT_TIMER: 'EDIT_TIMER',
|
EDIT_STOPWATCH: 'EDIT_STOPWATCH',
|
||||||
MOVE: 'MOVE',
|
MOVE: 'MOVE',
|
||||||
DELETE: 'DELETE',
|
DELETE: 'DELETE',
|
||||||
};
|
};
|
||||||
@@ -68,8 +68,8 @@ const ActionsStep = React.memo(
|
|||||||
openStep(StepTypes.EDIT_DUE_DATE);
|
openStep(StepTypes.EDIT_DUE_DATE);
|
||||||
}, [openStep]);
|
}, [openStep]);
|
||||||
|
|
||||||
const handleEditTimerClick = useCallback(() => {
|
const handleEditStopwatchClick = useCallback(() => {
|
||||||
openStep(StepTypes.EDIT_TIMER);
|
openStep(StepTypes.EDIT_STOPWATCH);
|
||||||
}, [openStep]);
|
}, [openStep]);
|
||||||
|
|
||||||
const handleMoveClick = useCallback(() => {
|
const handleMoveClick = useCallback(() => {
|
||||||
@@ -89,10 +89,10 @@ const ActionsStep = React.memo(
|
|||||||
[onUpdate],
|
[onUpdate],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTimerUpdate = useCallback(
|
const handleStopwatchUpdate = useCallback(
|
||||||
(timer) => {
|
(stopwatch) => {
|
||||||
onUpdate({
|
onUpdate({
|
||||||
timer,
|
stopwatch,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[onUpdate],
|
[onUpdate],
|
||||||
@@ -133,11 +133,11 @@ const ActionsStep = React.memo(
|
|||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case StepTypes.EDIT_TIMER:
|
case StepTypes.EDIT_STOPWATCH:
|
||||||
return (
|
return (
|
||||||
<TimerEditStep
|
<StopwatchEditStep
|
||||||
defaultValue={card.timer}
|
defaultValue={card.stopwatch}
|
||||||
onUpdate={handleTimerUpdate}
|
onUpdate={handleStopwatchUpdate}
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
@@ -197,8 +197,8 @@ const ActionsStep = React.memo(
|
|||||||
context: 'title',
|
context: 'title',
|
||||||
})}
|
})}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item className={styles.menuItem} onClick={handleEditTimerClick}>
|
<Menu.Item className={styles.menuItem} onClick={handleEditStopwatchClick}>
|
||||||
{t('action.editTimer', {
|
{t('action.editStopwatch', {
|
||||||
context: 'title',
|
context: 'title',
|
||||||
})}
|
})}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import { Draggable } from 'react-beautiful-dnd';
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
import { usePopup } from '../../lib/popup';
|
import { usePopup } from '../../lib/popup';
|
||||||
|
|
||||||
import { startTimer, stopTimer } from '../../utils/timer';
|
import { startStopwatch, stopStopwatch } from '../../utils/stopwatch';
|
||||||
import Paths from '../../constants/Paths';
|
import Paths from '../../constants/Paths';
|
||||||
import Tasks from './Tasks';
|
import Tasks from './Tasks';
|
||||||
import NameEdit from './NameEdit';
|
import NameEdit from './NameEdit';
|
||||||
@@ -14,7 +14,7 @@ import ActionsStep from './ActionsStep';
|
|||||||
import User from '../User';
|
import User from '../User';
|
||||||
import Label from '../Label';
|
import Label from '../Label';
|
||||||
import DueDate from '../DueDate';
|
import DueDate from '../DueDate';
|
||||||
import Timer from '../Timer';
|
import Stopwatch from '../Stopwatch';
|
||||||
|
|
||||||
import styles from './Card.module.scss';
|
import styles from './Card.module.scss';
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ const Card = React.memo(
|
|||||||
index,
|
index,
|
||||||
name,
|
name,
|
||||||
dueDate,
|
dueDate,
|
||||||
timer,
|
stopwatch,
|
||||||
coverUrl,
|
coverUrl,
|
||||||
boardId,
|
boardId,
|
||||||
listId,
|
listId,
|
||||||
@@ -60,15 +60,15 @@ const Card = React.memo(
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleToggleTimerClick = useCallback(
|
const handleToggleStopwatchClick = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
onUpdate({
|
onUpdate({
|
||||||
timer: timer.startedAt ? stopTimer(timer) : startTimer(timer),
|
stopwatch: stopwatch.startedAt ? stopStopwatch(stopwatch) : startStopwatch(stopwatch),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[timer, onUpdate],
|
[stopwatch, onUpdate],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleNameUpdate = useCallback(
|
const handleNameUpdate = useCallback(
|
||||||
@@ -104,7 +104,7 @@ const Card = React.memo(
|
|||||||
)}
|
)}
|
||||||
<div className={styles.name}>{name}</div>
|
<div className={styles.name}>{name}</div>
|
||||||
{tasks.length > 0 && <Tasks items={tasks} />}
|
{tasks.length > 0 && <Tasks items={tasks} />}
|
||||||
{(dueDate || timer || notificationsTotal > 0) && (
|
{(dueDate || stopwatch || notificationsTotal > 0) && (
|
||||||
<span className={styles.attachments}>
|
<span className={styles.attachments}>
|
||||||
{notificationsTotal > 0 && (
|
{notificationsTotal > 0 && (
|
||||||
<span
|
<span
|
||||||
@@ -122,14 +122,14 @@ const Card = React.memo(
|
|||||||
<DueDate value={dueDate} size="tiny" />
|
<DueDate value={dueDate} size="tiny" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{timer && (
|
{stopwatch && (
|
||||||
<span className={classNames(styles.attachment, styles.attachmentLeft)}>
|
<span className={classNames(styles.attachment, styles.attachmentLeft)}>
|
||||||
<Timer
|
<Stopwatch
|
||||||
as="span"
|
as="span"
|
||||||
startedAt={timer.startedAt}
|
startedAt={stopwatch.startedAt}
|
||||||
total={timer.total}
|
total={stopwatch.total}
|
||||||
size="tiny"
|
size="tiny"
|
||||||
onClick={canEdit ? handleToggleTimerClick : undefined}
|
onClick={canEdit ? handleToggleStopwatchClick : undefined}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -171,7 +171,7 @@ const Card = React.memo(
|
|||||||
<ActionsPopup
|
<ActionsPopup
|
||||||
card={{
|
card={{
|
||||||
dueDate,
|
dueDate,
|
||||||
timer,
|
stopwatch,
|
||||||
boardId,
|
boardId,
|
||||||
listId,
|
listId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -219,7 +219,7 @@ Card.propTypes = {
|
|||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
dueDate: PropTypes.instanceOf(Date),
|
dueDate: PropTypes.instanceOf(Date),
|
||||||
timer: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
stopwatch: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||||
coverUrl: PropTypes.string,
|
coverUrl: PropTypes.string,
|
||||||
boardId: PropTypes.string.isRequired,
|
boardId: PropTypes.string.isRequired,
|
||||||
listId: PropTypes.string.isRequired,
|
listId: PropTypes.string.isRequired,
|
||||||
@@ -252,7 +252,7 @@ Card.propTypes = {
|
|||||||
|
|
||||||
Card.defaultProps = {
|
Card.defaultProps = {
|
||||||
dueDate: undefined,
|
dueDate: undefined,
|
||||||
timer: undefined,
|
stopwatch: undefined,
|
||||||
coverUrl: undefined,
|
coverUrl: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||||
|
|
||||||
import { useClosableForm, useForm } from '../../../hooks';
|
import { useForm } from '../../../hooks';
|
||||||
|
|
||||||
import styles from './CommentEdit.module.scss';
|
import styles from './CommentEdit.module.scss';
|
||||||
|
|
||||||
@@ -35,12 +35,7 @@ const CommentEdit = React.forwardRef(({ children, defaultData, onUpdate }, ref)
|
|||||||
text: data.text.trim(),
|
text: data.text.trim(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!cleanData.text) {
|
if (cleanData.text && !dequal(cleanData, defaultData)) {
|
||||||
textField.current.ref.current.select();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dequal(cleanData, defaultData)) {
|
|
||||||
onUpdate(cleanData);
|
onUpdate(cleanData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,10 +60,9 @@ const CommentEdit = React.forwardRef(({ children, defaultData, onUpdate }, ref)
|
|||||||
[submit],
|
[submit],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
|
const handleFieldBlur = useCallback(() => {
|
||||||
close,
|
submit();
|
||||||
isOpened,
|
}, [submit]);
|
||||||
);
|
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
submit();
|
submit();
|
||||||
@@ -99,13 +93,7 @@ const CommentEdit = React.forwardRef(({ children, defaultData, onUpdate }, ref)
|
|||||||
onBlur={handleFieldBlur}
|
onBlur={handleFieldBlur}
|
||||||
/>
|
/>
|
||||||
<div className={styles.controls}>
|
<div className={styles.controls}>
|
||||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
|
<Button positive content={t('action.save')} />
|
||||||
<Button
|
|
||||||
positive
|
|
||||||
content={t('action.save')}
|
|
||||||
onMouseOver={handleControlMouseOver}
|
|
||||||
onMouseOut={handleControlMouseOut}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Button, Grid, Icon, Modal } from 'semantic-ui-react';
|
|||||||
import { usePopup } from '../../lib/popup';
|
import { usePopup } from '../../lib/popup';
|
||||||
import { Markdown } from '../../lib/custom-ui';
|
import { Markdown } from '../../lib/custom-ui';
|
||||||
|
|
||||||
import { startTimer, stopTimer } from '../../utils/timer';
|
import { startStopwatch, stopStopwatch } from '../../utils/stopwatch';
|
||||||
import NameField from './NameField';
|
import NameField from './NameField';
|
||||||
import DescriptionEdit from './DescriptionEdit';
|
import DescriptionEdit from './DescriptionEdit';
|
||||||
import Tasks from './Tasks';
|
import Tasks from './Tasks';
|
||||||
@@ -17,11 +17,11 @@ import Activities from './Activities';
|
|||||||
import User from '../User';
|
import User from '../User';
|
||||||
import Label from '../Label';
|
import Label from '../Label';
|
||||||
import DueDate from '../DueDate';
|
import DueDate from '../DueDate';
|
||||||
import Timer from '../Timer';
|
import Stopwatch from '../Stopwatch';
|
||||||
import BoardMembershipsStep from '../BoardMembershipsStep';
|
import BoardMembershipsStep from '../BoardMembershipsStep';
|
||||||
import LabelsStep from '../LabelsStep';
|
import LabelsStep from '../LabelsStep';
|
||||||
import DueDateEditStep from '../DueDateEditStep';
|
import DueDateEditStep from '../DueDateEditStep';
|
||||||
import TimerEditStep from '../TimerEditStep';
|
import StopwatchEditStep from '../StopwatchEditStep';
|
||||||
import CardMoveStep from '../CardMoveStep';
|
import CardMoveStep from '../CardMoveStep';
|
||||||
import DeleteStep from '../DeleteStep';
|
import DeleteStep from '../DeleteStep';
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ const CardModal = React.memo(
|
|||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
dueDate,
|
dueDate,
|
||||||
timer,
|
stopwatch,
|
||||||
isSubscribed,
|
isSubscribed,
|
||||||
isActivitiesFetching,
|
isActivitiesFetching,
|
||||||
isAllActivitiesFetched,
|
isAllActivitiesFetched,
|
||||||
@@ -83,11 +83,11 @@ const CardModal = React.memo(
|
|||||||
|
|
||||||
const isGalleryOpened = useRef(false);
|
const isGalleryOpened = useRef(false);
|
||||||
|
|
||||||
const handleToggleTimerClick = useCallback(() => {
|
const handleToggleStopwatchClick = useCallback(() => {
|
||||||
onUpdate({
|
onUpdate({
|
||||||
timer: timer.startedAt ? stopTimer(timer) : startTimer(timer),
|
stopwatch: stopwatch.startedAt ? stopStopwatch(stopwatch) : startStopwatch(stopwatch),
|
||||||
});
|
});
|
||||||
}, [timer, onUpdate]);
|
}, [stopwatch, onUpdate]);
|
||||||
|
|
||||||
const handleNameUpdate = useCallback(
|
const handleNameUpdate = useCallback(
|
||||||
(newName) => {
|
(newName) => {
|
||||||
@@ -116,10 +116,10 @@ const CardModal = React.memo(
|
|||||||
[onUpdate],
|
[onUpdate],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTimerUpdate = useCallback(
|
const handleStopwatchUpdate = useCallback(
|
||||||
(newTimer) => {
|
(newStopwatch) => {
|
||||||
onUpdate({
|
onUpdate({
|
||||||
timer: newTimer,
|
stopwatch: newStopwatch,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[onUpdate],
|
[onUpdate],
|
||||||
@@ -160,7 +160,7 @@ const CardModal = React.memo(
|
|||||||
const BoardMembershipsPopup = usePopup(BoardMembershipsStep);
|
const BoardMembershipsPopup = usePopup(BoardMembershipsStep);
|
||||||
const LabelsPopup = usePopup(LabelsStep);
|
const LabelsPopup = usePopup(LabelsStep);
|
||||||
const DueDateEditPopup = usePopup(DueDateEditStep);
|
const DueDateEditPopup = usePopup(DueDateEditStep);
|
||||||
const TimerEditPopup = usePopup(TimerEditStep);
|
const StopwatchEditPopup = usePopup(StopwatchEditStep);
|
||||||
const CardMovePopup = usePopup(CardMoveStep);
|
const CardMovePopup = usePopup(CardMoveStep);
|
||||||
const DeletePopup = usePopup(DeleteStep);
|
const DeletePopup = usePopup(DeleteStep);
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ const CardModal = React.memo(
|
|||||||
</Grid.Row>
|
</Grid.Row>
|
||||||
<Grid.Row className={styles.modalPadding}>
|
<Grid.Row className={styles.modalPadding}>
|
||||||
<Grid.Column width={canEdit ? 12 : 16} className={styles.contentPadding}>
|
<Grid.Column width={canEdit ? 12 : 16} className={styles.contentPadding}>
|
||||||
{(users.length > 0 || labels.length > 0 || dueDate || timer) && (
|
{(users.length > 0 || labels.length > 0 || dueDate || stopwatch) && (
|
||||||
<div className={styles.moduleWrapper}>
|
<div className={styles.moduleWrapper}>
|
||||||
{users.length > 0 && (
|
{users.length > 0 && (
|
||||||
<div className={styles.attachments}>
|
<div className={styles.attachments}>
|
||||||
@@ -294,30 +294,33 @@ const CardModal = React.memo(
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{timer && (
|
{stopwatch && (
|
||||||
<div className={styles.attachments}>
|
<div className={styles.attachments}>
|
||||||
<div className={styles.text}>
|
<div className={styles.text}>
|
||||||
{t('common.timer', {
|
{t('common.stopwatch', {
|
||||||
context: 'title',
|
context: 'title',
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<span className={styles.attachment}>
|
<span className={styles.attachment}>
|
||||||
{canEdit ? (
|
{canEdit ? (
|
||||||
<TimerEditPopup defaultValue={timer} onUpdate={handleTimerUpdate}>
|
<StopwatchEditPopup
|
||||||
<Timer startedAt={timer.startedAt} total={timer.total} />
|
defaultValue={stopwatch}
|
||||||
</TimerEditPopup>
|
onUpdate={handleStopwatchUpdate}
|
||||||
|
>
|
||||||
|
<Stopwatch startedAt={stopwatch.startedAt} total={stopwatch.total} />
|
||||||
|
</StopwatchEditPopup>
|
||||||
) : (
|
) : (
|
||||||
<Timer startedAt={timer.startedAt} total={timer.total} />
|
<Stopwatch startedAt={stopwatch.startedAt} total={stopwatch.total} />
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
<button
|
<button
|
||||||
onClick={handleToggleTimerClick}
|
onClick={handleToggleStopwatchClick}
|
||||||
type="button"
|
type="button"
|
||||||
className={classNames(styles.attachment, styles.dueDate)}
|
className={classNames(styles.attachment, styles.dueDate)}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name={timer.startedAt ? 'pause' : 'play'}
|
name={stopwatch.startedAt ? 'pause' : 'play'}
|
||||||
size="small"
|
size="small"
|
||||||
className={styles.addAttachment}
|
className={styles.addAttachment}
|
||||||
/>
|
/>
|
||||||
@@ -447,12 +450,12 @@ const CardModal = React.memo(
|
|||||||
})}
|
})}
|
||||||
</Button>
|
</Button>
|
||||||
</DueDateEditPopup>
|
</DueDateEditPopup>
|
||||||
<TimerEditPopup defaultValue={timer} onUpdate={handleTimerUpdate}>
|
<StopwatchEditPopup defaultValue={stopwatch} onUpdate={handleStopwatchUpdate}>
|
||||||
<Button fluid className={styles.actionButton}>
|
<Button fluid className={styles.actionButton}>
|
||||||
<Icon name="clock outline" className={styles.actionIcon} />
|
<Icon name="clock outline" className={styles.actionIcon} />
|
||||||
{t('common.timer')}
|
{t('common.stopwatch')}
|
||||||
</Button>
|
</Button>
|
||||||
</TimerEditPopup>
|
</StopwatchEditPopup>
|
||||||
<AttachmentAddPopup onCreate={onAttachmentCreate}>
|
<AttachmentAddPopup onCreate={onAttachmentCreate}>
|
||||||
<Button fluid className={styles.actionButton}>
|
<Button fluid className={styles.actionButton}>
|
||||||
<Icon name="attach" className={styles.actionIcon} />
|
<Icon name="attach" className={styles.actionIcon} />
|
||||||
@@ -524,7 +527,7 @@ CardModal.propTypes = {
|
|||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
description: PropTypes.string,
|
description: PropTypes.string,
|
||||||
dueDate: PropTypes.instanceOf(Date),
|
dueDate: PropTypes.instanceOf(Date),
|
||||||
timer: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
stopwatch: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||||
isSubscribed: PropTypes.bool.isRequired,
|
isSubscribed: PropTypes.bool.isRequired,
|
||||||
isActivitiesFetching: PropTypes.bool.isRequired,
|
isActivitiesFetching: PropTypes.bool.isRequired,
|
||||||
isAllActivitiesFetched: PropTypes.bool.isRequired,
|
isAllActivitiesFetched: PropTypes.bool.isRequired,
|
||||||
@@ -577,7 +580,7 @@ CardModal.propTypes = {
|
|||||||
CardModal.defaultProps = {
|
CardModal.defaultProps = {
|
||||||
description: undefined,
|
description: undefined,
|
||||||
dueDate: undefined,
|
dueDate: undefined,
|
||||||
timer: undefined,
|
stopwatch: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CardModal;
|
export default CardModal;
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ const DescriptionEdit = React.forwardRef(({ children, defaultValue, onUpdate },
|
|||||||
onChange={setValue}
|
onChange={setValue}
|
||||||
/>
|
/>
|
||||||
<div className={styles.controls}>
|
<div className={styles.controls}>
|
||||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
|
|
||||||
<Button positive content={t('action.save')} />
|
<Button positive content={t('action.save')} />
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||||
|
|
||||||
import { useClosableForm, useField } from '../../../hooks';
|
import { useField } from '../../../hooks';
|
||||||
|
|
||||||
import styles from './NameEdit.module.scss';
|
import styles from './NameEdit.module.scss';
|
||||||
|
|
||||||
@@ -28,12 +28,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
|
|||||||
const submit = useCallback(() => {
|
const submit = useCallback(() => {
|
||||||
const cleanValue = value.trim();
|
const cleanValue = value.trim();
|
||||||
|
|
||||||
if (!cleanValue) {
|
if (cleanValue && cleanValue !== defaultValue) {
|
||||||
field.current.ref.current.select();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cleanValue !== defaultValue) {
|
|
||||||
onUpdate(cleanValue);
|
onUpdate(cleanValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,10 +55,9 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
|
|||||||
[submit],
|
[submit],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
|
const handleFieldBlur = useCallback(() => {
|
||||||
close,
|
submit();
|
||||||
isOpened,
|
}, [submit]);
|
||||||
);
|
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
submit();
|
submit();
|
||||||
@@ -93,13 +87,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
|
|||||||
onBlur={handleFieldBlur}
|
onBlur={handleFieldBlur}
|
||||||
/>
|
/>
|
||||||
<div className={styles.controls}>
|
<div className={styles.controls}>
|
||||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
|
<Button positive content={t('action.save')} />
|
||||||
<Button
|
|
||||||
positive
|
|
||||||
content={t('action.save')}
|
|
||||||
onMouseOver={handleControlMouseOver}
|
|
||||||
onMouseOut={handleControlMouseOut}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Icon, Menu } from 'semantic-ui-react';
|
import { Button, Icon, Menu } from 'semantic-ui-react';
|
||||||
import { usePopup } from '../../lib/popup';
|
import { usePopup } from '../../lib/popup';
|
||||||
|
|
||||||
import Paths from '../../constants/Paths';
|
import Paths from '../../constants/Paths';
|
||||||
@@ -55,15 +55,16 @@ const Header = React.memo(
|
|||||||
>
|
>
|
||||||
<Icon fitted name="arrow left" />
|
<Icon fitted name="arrow left" />
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item className={classNames(styles.item, styles.title)}>
|
||||||
className={classNames(
|
{project.name}
|
||||||
styles.item,
|
{canEditProject && (
|
||||||
canEditProject && styles.itemHoverable,
|
<Button
|
||||||
styles.title,
|
className={classNames(styles.editButton, styles.target)}
|
||||||
)}
|
|
||||||
onClick={handleProjectSettingsClick}
|
onClick={handleProjectSettingsClick}
|
||||||
>
|
>
|
||||||
{project.name}
|
<Icon fitted name="pencil" size="small" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu.Menu>
|
</Menu.Menu>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,20 @@
|
|||||||
:global(#app) {
|
:global(#app) {
|
||||||
|
.editButton {
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 34px;
|
||||||
|
margin-left: 8px;
|
||||||
|
opacity: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 34px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
cursor: auto;
|
cursor: auto;
|
||||||
user-select: auto;
|
user-select: auto;
|
||||||
@@ -11,6 +27,10 @@
|
|||||||
&:hover {
|
&:hover {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: rgba(255, 255, 255, 0.9);
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
|
||||||
|
.target {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-7
@@ -4,9 +4,9 @@ import PropTypes from 'prop-types';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useForceUpdate, usePrevious } from '../../lib/hooks';
|
import { useForceUpdate, usePrevious } from '../../lib/hooks';
|
||||||
|
|
||||||
import { formatTimer } from '../../utils/timer';
|
import { formatStopwatch } from '../../utils/stopwatch';
|
||||||
|
|
||||||
import styles from './Timer.module.scss';
|
import styles from './Stopwatch.module.scss';
|
||||||
|
|
||||||
const SIZES = {
|
const SIZES = {
|
||||||
TINY: 'tiny',
|
TINY: 'tiny',
|
||||||
@@ -14,7 +14,7 @@ const SIZES = {
|
|||||||
MEDIUM: 'medium',
|
MEDIUM: 'medium',
|
||||||
};
|
};
|
||||||
|
|
||||||
const Timer = React.memo(({ as, startedAt, total, size, isDisabled, onClick }) => {
|
const Stopwatch = React.memo(({ as, startedAt, total, size, isDisabled, onClick }) => {
|
||||||
const prevStartedAt = usePrevious(startedAt);
|
const prevStartedAt = usePrevious(startedAt);
|
||||||
const forceUpdate = useForceUpdate();
|
const forceUpdate = useForceUpdate();
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ const Timer = React.memo(({ as, startedAt, total, size, isDisabled, onClick }) =
|
|||||||
onClick && styles.wrapperHoverable,
|
onClick && styles.wrapperHoverable,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{formatTimer({ startedAt, total })}
|
{formatStopwatch({ startedAt, total })}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ const Timer = React.memo(({ as, startedAt, total, size, isDisabled, onClick }) =
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Timer.propTypes = {
|
Stopwatch.propTypes = {
|
||||||
as: PropTypes.elementType,
|
as: PropTypes.elementType,
|
||||||
startedAt: PropTypes.instanceOf(Date),
|
startedAt: PropTypes.instanceOf(Date),
|
||||||
total: PropTypes.number.isRequired, // eslint-disable-line react/no-unused-prop-types
|
total: PropTypes.number.isRequired, // eslint-disable-line react/no-unused-prop-types
|
||||||
@@ -80,7 +80,7 @@ Timer.propTypes = {
|
|||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
Timer.defaultProps = {
|
Stopwatch.defaultProps = {
|
||||||
as: 'button',
|
as: 'button',
|
||||||
startedAt: undefined,
|
startedAt: undefined,
|
||||||
size: SIZES.MEDIUM,
|
size: SIZES.MEDIUM,
|
||||||
@@ -88,4 +88,4 @@ Timer.defaultProps = {
|
|||||||
onClick: undefined,
|
onClick: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Timer;
|
export default Stopwatch;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import Stopwatch from './Stopwatch';
|
||||||
|
|
||||||
|
export default Stopwatch;
|
||||||
+21
-15
@@ -7,12 +7,18 @@ import { useToggle } from '../../lib/hooks';
|
|||||||
import { Input, Popup } from '../../lib/custom-ui';
|
import { Input, Popup } from '../../lib/custom-ui';
|
||||||
|
|
||||||
import { useForm } from '../../hooks';
|
import { useForm } from '../../hooks';
|
||||||
import { createTimer, getTimerParts, startTimer, stopTimer, updateTimer } from '../../utils/timer';
|
import {
|
||||||
|
createStopwatch,
|
||||||
|
getStopwatchParts,
|
||||||
|
startStopwatch,
|
||||||
|
stopStopwatch,
|
||||||
|
updateStopwatch,
|
||||||
|
} from '../../utils/stopwatch';
|
||||||
|
|
||||||
import styles from './TimerEditStep.module.scss';
|
import styles from './StopwatchEditStep.module.scss';
|
||||||
|
|
||||||
const createData = (timer) => {
|
const createData = (stopwatch) => {
|
||||||
if (!timer) {
|
if (!stopwatch) {
|
||||||
return {
|
return {
|
||||||
hours: '0',
|
hours: '0',
|
||||||
minutes: '0',
|
minutes: '0',
|
||||||
@@ -20,7 +26,7 @@ const createData = (timer) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hours, minutes, seconds } = getTimerParts(timer);
|
const { hours, minutes, seconds } = getStopwatchParts(stopwatch);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hours: `${hours}`,
|
hours: `${hours}`,
|
||||||
@@ -29,7 +35,7 @@ const createData = (timer) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const TimerEditStep = React.memo(({ defaultValue, onUpdate, onBack, onClose }) => {
|
const StopwatchEditStep = React.memo(({ defaultValue, onUpdate, onBack, onClose }) => {
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
const [data, handleFieldChange, setData] = useForm(() => createData(defaultValue));
|
const [data, handleFieldChange, setData] = useForm(() => createData(defaultValue));
|
||||||
const [isEditing, toggleEditing] = useToggle();
|
const [isEditing, toggleEditing] = useToggle();
|
||||||
@@ -39,12 +45,12 @@ const TimerEditStep = React.memo(({ defaultValue, onUpdate, onBack, onClose }) =
|
|||||||
const secondsField = useRef(null);
|
const secondsField = useRef(null);
|
||||||
|
|
||||||
const handleStartClick = useCallback(() => {
|
const handleStartClick = useCallback(() => {
|
||||||
onUpdate(startTimer(defaultValue));
|
onUpdate(startStopwatch(defaultValue));
|
||||||
onClose();
|
onClose();
|
||||||
}, [defaultValue, onUpdate, onClose]);
|
}, [defaultValue, onUpdate, onClose]);
|
||||||
|
|
||||||
const handleStopClick = useCallback(() => {
|
const handleStopClick = useCallback(() => {
|
||||||
onUpdate(stopTimer(defaultValue));
|
onUpdate(stopStopwatch(defaultValue));
|
||||||
}, [defaultValue, onUpdate]);
|
}, [defaultValue, onUpdate]);
|
||||||
|
|
||||||
const handleClearClick = useCallback(() => {
|
const handleClearClick = useCallback(() => {
|
||||||
@@ -83,11 +89,11 @@ const TimerEditStep = React.memo(({ defaultValue, onUpdate, onBack, onClose }) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (defaultValue) {
|
if (defaultValue) {
|
||||||
if (!dequal(parts, getTimerParts(defaultValue))) {
|
if (!dequal(parts, getStopwatchParts(defaultValue))) {
|
||||||
onUpdate(updateTimer(defaultValue, parts));
|
onUpdate(updateStopwatch(defaultValue, parts));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onUpdate(createTimer(parts));
|
onUpdate(createStopwatch(parts));
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
@@ -102,7 +108,7 @@ const TimerEditStep = React.memo(({ defaultValue, onUpdate, onBack, onClose }) =
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Popup.Header onBack={onBack}>
|
<Popup.Header onBack={onBack}>
|
||||||
{t('common.editTimer', {
|
{t('common.editStopwatch', {
|
||||||
context: 'title',
|
context: 'title',
|
||||||
})}
|
})}
|
||||||
</Popup.Header>
|
</Popup.Header>
|
||||||
@@ -171,16 +177,16 @@ const TimerEditStep = React.memo(({ defaultValue, onUpdate, onBack, onClose }) =
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
TimerEditStep.propTypes = {
|
StopwatchEditStep.propTypes = {
|
||||||
defaultValue: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
defaultValue: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||||
onUpdate: PropTypes.func.isRequired,
|
onUpdate: PropTypes.func.isRequired,
|
||||||
onBack: PropTypes.func,
|
onBack: PropTypes.func,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
TimerEditStep.defaultProps = {
|
StopwatchEditStep.defaultProps = {
|
||||||
defaultValue: undefined,
|
defaultValue: undefined,
|
||||||
onBack: undefined,
|
onBack: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TimerEditStep;
|
export default StopwatchEditStep;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import StopwatchEditStep from './StopwatchEditStep';
|
||||||
|
|
||||||
|
export default StopwatchEditStep;
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import Timer from './Timer';
|
|
||||||
|
|
||||||
export default Timer;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import TimerEditStep from './TimerEditStep';
|
|
||||||
|
|
||||||
export default TimerEditStep;
|
|
||||||
@@ -20,7 +20,7 @@ const makeMapStateToProps = () => {
|
|||||||
const allLabels = selectors.selectLabelsForCurrentBoard(state);
|
const allLabels = selectors.selectLabelsForCurrentBoard(state);
|
||||||
const currentUserMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
|
const currentUserMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
|
||||||
|
|
||||||
const { name, dueDate, timer, coverUrl, boardId, listId, isPersisted } = selectCardById(
|
const { name, dueDate, stopwatch, coverUrl, boardId, listId, isPersisted } = selectCardById(
|
||||||
state,
|
state,
|
||||||
id,
|
id,
|
||||||
);
|
);
|
||||||
@@ -38,7 +38,7 @@ const makeMapStateToProps = () => {
|
|||||||
index,
|
index,
|
||||||
name,
|
name,
|
||||||
dueDate,
|
dueDate,
|
||||||
timer,
|
stopwatch,
|
||||||
coverUrl,
|
coverUrl,
|
||||||
boardId,
|
boardId,
|
||||||
listId,
|
listId,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const mapStateToProps = (state) => {
|
|||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
dueDate,
|
dueDate,
|
||||||
timer,
|
stopwatch,
|
||||||
isSubscribed,
|
isSubscribed,
|
||||||
isActivitiesFetching,
|
isActivitiesFetching,
|
||||||
isAllActivitiesFetched,
|
isAllActivitiesFetched,
|
||||||
@@ -49,7 +49,7 @@ const mapStateToProps = (state) => {
|
|||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
dueDate,
|
dueDate,
|
||||||
timer,
|
stopwatch,
|
||||||
isSubscribed,
|
isSubscribed,
|
||||||
isActivitiesFetching,
|
isActivitiesFetching,
|
||||||
isAllActivitiesFetched,
|
isAllActivitiesFetched,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export default {
|
|||||||
|
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
|
aboutPlanka: 'O aplikaci Planka',
|
||||||
account: 'Účet',
|
account: 'Účet',
|
||||||
actions: 'Akce',
|
actions: 'Akce',
|
||||||
addAttachment_title: 'Přidat přílohu',
|
addAttachment_title: 'Přidat přílohu',
|
||||||
@@ -45,6 +46,9 @@ export default {
|
|||||||
background: 'Pozadí',
|
background: 'Pozadí',
|
||||||
board: 'Tabule',
|
board: 'Tabule',
|
||||||
boardNotFound_title: 'Tabule nenalezena',
|
boardNotFound_title: 'Tabule nenalezena',
|
||||||
|
canComment: 'Může komentovat',
|
||||||
|
canEditContentOfBoard: 'Může upravovat obsah tabule',
|
||||||
|
canOnlyViewBoard: 'Může zobrazit pouze tabuli',
|
||||||
cardActions_title: 'Akce na kartě',
|
cardActions_title: 'Akce na kartě',
|
||||||
cardNotFound_title: 'Karta nenalezena',
|
cardNotFound_title: 'Karta nenalezena',
|
||||||
cardOrActionAreDeleted: 'Karta nebo akce je smazána',
|
cardOrActionAreDeleted: 'Karta nebo akce je smazána',
|
||||||
@@ -68,15 +72,19 @@ export default {
|
|||||||
deleteTask_title: 'Smazat úkol',
|
deleteTask_title: 'Smazat úkol',
|
||||||
deleteUser_title: 'Smazat uživatele',
|
deleteUser_title: 'Smazat uživatele',
|
||||||
description: 'Popis',
|
description: 'Popis',
|
||||||
|
detectAutomatically: 'Automatická detekce',
|
||||||
dropFileToUpload: 'Přetáhnutím nahraj soubor',
|
dropFileToUpload: 'Přetáhnutím nahraj soubor',
|
||||||
|
editor: 'Editor',
|
||||||
editAttachment_title: 'Upravit přílohu',
|
editAttachment_title: 'Upravit přílohu',
|
||||||
editAvatar_title: 'Upravit avatar',
|
editAvatar_title: 'Upravit avatar',
|
||||||
editBoard_title: 'Upravit tabuli',
|
editBoard_title: 'Upravit tabuli',
|
||||||
editDueDate_title: 'Upravit Termín do',
|
editDueDate_title: 'Upravit Termín do',
|
||||||
editEmail_title: 'Upravit e-mail',
|
editEmail_title: 'Upravit e-mail',
|
||||||
|
editInformation_title: 'Upravit informace',
|
||||||
editLabel_title: 'Upravit štítek',
|
editLabel_title: 'Upravit štítek',
|
||||||
editPassword_title: 'Upravit heslo',
|
editPassword_title: 'Upravit heslo',
|
||||||
editTimer_title: 'Upravit časovač',
|
editPermissions_title: 'Upravit oprávnění',
|
||||||
|
editStopwatch_title: 'Upravit časovač',
|
||||||
editUsername_title: 'Upravit uživatelské jméno',
|
editUsername_title: 'Upravit uživatelské jméno',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'E-mail se již používá',
|
emailAlreadyInUse: 'E-mail se již používá',
|
||||||
@@ -89,10 +97,13 @@ export default {
|
|||||||
filterByLabels_title: 'Filtruj podle štítku',
|
filterByLabels_title: 'Filtruj podle štítku',
|
||||||
filterByMembers_title: 'Filtruj podle člena',
|
filterByMembers_title: 'Filtruj podle člena',
|
||||||
fromComputer_title: 'Z počítače',
|
fromComputer_title: 'Z počítače',
|
||||||
|
fromTrello: 'Z Trella',
|
||||||
general: 'Obecné',
|
general: 'Obecné',
|
||||||
hours: 'Hodiny',
|
hours: 'Hodiny',
|
||||||
|
importBoard_title: 'Importovat tabuli',
|
||||||
invalidCurrentPassword: 'Neplatné aktuální heslo',
|
invalidCurrentPassword: 'Neplatné aktuální heslo',
|
||||||
labels: 'Štítky',
|
labels: 'Štítky',
|
||||||
|
language: 'Jazyk',
|
||||||
leaveBoard_title: 'Opustit tabuli',
|
leaveBoard_title: 'Opustit tabuli',
|
||||||
leaveProject_title: 'Opustit projekt',
|
leaveProject_title: 'Opustit projekt',
|
||||||
list: 'Seznam',
|
list: 'Seznam',
|
||||||
@@ -122,16 +133,21 @@ export default {
|
|||||||
projectNotFound_title: 'Projekt nenalezen',
|
projectNotFound_title: 'Projekt nenalezen',
|
||||||
removeManager_title: 'Odstranit vedoucího',
|
removeManager_title: 'Odstranit vedoucího',
|
||||||
removeMember_title: 'Odstranit člena',
|
removeMember_title: 'Odstranit člena',
|
||||||
|
searchLabels: 'Hledat štítky...',
|
||||||
|
searchMembers: 'Hledat členy...',
|
||||||
|
searchUsers: 'Hledat uživatele...',
|
||||||
seconds: 'Vteřin',
|
seconds: 'Vteřin',
|
||||||
selectBoard: 'Vybrat tabuli',
|
selectBoard: 'Vybrat tabuli',
|
||||||
selectList: 'Vybrat seznam',
|
selectList: 'Vybrat seznam',
|
||||||
|
selectPermissions_title: 'Vybrat oprávnění',
|
||||||
selectProject: 'Vybrat projekt',
|
selectProject: 'Vybrat projekt',
|
||||||
settings: 'Nastavení',
|
settings: 'Nastavení',
|
||||||
|
stopwatch: 'Časovač',
|
||||||
subscribeToMyOwnCardsByDefault: 'Ve výchozím nastavení odebírat vlastní karty',
|
subscribeToMyOwnCardsByDefault: 'Ve výchozím nastavení odebírat vlastní karty',
|
||||||
taskActions_title: 'Akce na úkolu',
|
taskActions_title: 'Akce na úkolu',
|
||||||
tasks: 'Úkoly',
|
tasks: 'Úkoly',
|
||||||
|
thereIsNoPreviewAvailableForThisAttachment: 'Pro tuto přílohu není k dispozici žádný náhled',
|
||||||
time: 'Čas',
|
time: 'Čas',
|
||||||
timer: 'Časovač',
|
|
||||||
title: 'Titulek',
|
title: 'Titulek',
|
||||||
userActions_title: 'Akce na uživateli',
|
userActions_title: 'Akce na uživateli',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> přidal kartu do {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> přidal kartu do {{list}}</1>',
|
||||||
@@ -142,6 +158,8 @@ export default {
|
|||||||
username: 'Uživatelské jméno',
|
username: 'Uživatelské jméno',
|
||||||
usernameAlreadyInUse: 'Uživatelské jméno se již používá',
|
usernameAlreadyInUse: 'Uživatelské jméno se již používá',
|
||||||
users: 'Uživatelé',
|
users: 'Uživatelé',
|
||||||
|
version: 'Verze',
|
||||||
|
viewer: 'Prohlížeč',
|
||||||
writeComment: 'Napsat komentář...',
|
writeComment: 'Napsat komentář...',
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -153,6 +171,7 @@ export default {
|
|||||||
addCard_title: 'Přidat kartu',
|
addCard_title: 'Přidat kartu',
|
||||||
addComment: 'Přidat komentář',
|
addComment: 'Přidat komentář',
|
||||||
addList: 'Přidat seznam',
|
addList: 'Přidat seznam',
|
||||||
|
addMember: 'Přidat člena',
|
||||||
addMoreDetailedDescription: 'Přidat další detailní popis',
|
addMoreDetailedDescription: 'Přidat další detailní popis',
|
||||||
addTask: 'Přidat úkol',
|
addTask: 'Přidat úkol',
|
||||||
addToCard: 'Přidat na kartu',
|
addToCard: 'Přidat na kartu',
|
||||||
@@ -182,10 +201,14 @@ export default {
|
|||||||
editDueDate_title: 'Upravit termín do',
|
editDueDate_title: 'Upravit termín do',
|
||||||
editDescription_title: 'Upravit popis',
|
editDescription_title: 'Upravit popis',
|
||||||
editEmail_title: 'Upravit e-mail',
|
editEmail_title: 'Upravit e-mail',
|
||||||
|
editInformation_title: 'Upravit informace',
|
||||||
editPassword_title: 'Upravit heslo',
|
editPassword_title: 'Upravit heslo',
|
||||||
editTimer_title: 'Upravit časovač',
|
editPermissions: 'Upravit oprávnění',
|
||||||
|
editStopwatch_title: 'Upravit časovač',
|
||||||
editTitle_title: 'Upravit titulek',
|
editTitle_title: 'Upravit titulek',
|
||||||
editUsername_title: 'Upravit uživatelské jméno',
|
editUsername_title: 'Upravit uživatelské jméno',
|
||||||
|
hideDetails: 'Skrýt detaily',
|
||||||
|
import: 'Import',
|
||||||
leaveBoard: 'Opustit tabuli',
|
leaveBoard: 'Opustit tabuli',
|
||||||
leaveProject: 'Opustit projekt',
|
leaveProject: 'Opustit projekt',
|
||||||
logOut_title: 'Odhlásit se',
|
logOut_title: 'Odhlásit se',
|
||||||
@@ -202,6 +225,7 @@ export default {
|
|||||||
save: 'Uložit',
|
save: 'Uložit',
|
||||||
showAllAttachments: 'Zozbrazit všechny přílohy ({{hidden}} skryté)',
|
showAllAttachments: 'Zozbrazit všechny přílohy ({{hidden}} skryté)',
|
||||||
showFewerAttachments: 'Zobrazit méně příloh',
|
showFewerAttachments: 'Zobrazit méně příloh',
|
||||||
|
showDetails: 'Zobrazit detaily',
|
||||||
start: 'Start',
|
start: 'Start',
|
||||||
stop: 'Stop',
|
stop: 'Stop',
|
||||||
subscribe: 'Odebírat',
|
subscribe: 'Odebírat',
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export default {
|
|||||||
editEmail_title: 'Ændre E-mail',
|
editEmail_title: 'Ændre E-mail',
|
||||||
editLabel_title: 'ændre mærkat',
|
editLabel_title: 'ændre mærkat',
|
||||||
editPassword_title: 'Ændre løsen',
|
editPassword_title: 'Ændre løsen',
|
||||||
editTimer_title: 'Ændre timer',
|
editStopwatch_title: 'Ændre timer',
|
||||||
editUsername_title: 'Ændre brugernavn',
|
editUsername_title: 'Ændre brugernavn',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'E-mail allerede i brug',
|
emailAlreadyInUse: 'E-mail allerede i brug',
|
||||||
@@ -126,11 +126,11 @@ export default {
|
|||||||
selectList: 'Vælg liste',
|
selectList: 'Vælg liste',
|
||||||
selectProject: 'Vælg projekt',
|
selectProject: 'Vælg projekt',
|
||||||
settings: 'Indstillinger',
|
settings: 'Indstillinger',
|
||||||
|
stopwatch: 'Tidsmåler',
|
||||||
subscribeToMyOwnCardsByDefault: 'Abonnér på egne kort som standard',
|
subscribeToMyOwnCardsByDefault: 'Abonnér på egne kort som standard',
|
||||||
taskActions_title: 'Opgave handlinger',
|
taskActions_title: 'Opgave handlinger',
|
||||||
tasks: 'Opgaver',
|
tasks: 'Opgaver',
|
||||||
time: 'Tid',
|
time: 'Tid',
|
||||||
timer: 'Tidsmåler',
|
|
||||||
title: 'Overskrift',
|
title: 'Overskrift',
|
||||||
userActions_title: 'Brugerhandlinger',
|
userActions_title: 'Brugerhandlinger',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> tilføjede kortet til {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> tilføjede kortet til {{list}}</1>',
|
||||||
@@ -183,7 +183,7 @@ export default {
|
|||||||
editDescription_title: 'Ændre beskrivelse',
|
editDescription_title: 'Ændre beskrivelse',
|
||||||
editEmail_title: 'Ændre e-mail',
|
editEmail_title: 'Ændre e-mail',
|
||||||
editPassword_title: 'Ændre Løsen',
|
editPassword_title: 'Ændre Løsen',
|
||||||
editTimer_title: 'Ændre tidstager',
|
editStopwatch_title: 'Ændre tidstager',
|
||||||
editTitle_title: 'Ændre overskrift',
|
editTitle_title: 'Ændre overskrift',
|
||||||
editUsername_title: 'Ændre brugernavn',
|
editUsername_title: 'Ændre brugernavn',
|
||||||
leaveBoard: 'Forlad bræt',
|
leaveBoard: 'Forlad bræt',
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export default {
|
|||||||
editEmail_title: 'E-Mail-Adresse bearbeiten',
|
editEmail_title: 'E-Mail-Adresse bearbeiten',
|
||||||
editLabel_title: 'Label bearbeiten',
|
editLabel_title: 'Label bearbeiten',
|
||||||
editPassword_title: 'Passwort ändern',
|
editPassword_title: 'Passwort ändern',
|
||||||
editTimer_title: 'Timer bearbeiten',
|
editStopwatch_title: 'Stoppuhr bearbeiten',
|
||||||
editUsername_title: 'Benutzername ändern',
|
editUsername_title: 'Benutzername ändern',
|
||||||
email: 'E-Mail-Adresse',
|
email: 'E-Mail-Adresse',
|
||||||
emailAlreadyInUse: 'E-Mail-Adresse bereits in Verwendung',
|
emailAlreadyInUse: 'E-Mail-Adresse bereits in Verwendung',
|
||||||
@@ -134,12 +134,12 @@ export default {
|
|||||||
selectList: 'Liste auswählen',
|
selectList: 'Liste auswählen',
|
||||||
selectProject: 'Projekt auswählen',
|
selectProject: 'Projekt auswählen',
|
||||||
settings: 'Einstellungen',
|
settings: 'Einstellungen',
|
||||||
|
stopwatch: 'Stoppuhr',
|
||||||
subscribeToMyOwnCardsByDefault: 'Standardmäßig meine eigenen Karten abonnieren',
|
subscribeToMyOwnCardsByDefault: 'Standardmäßig meine eigenen Karten abonnieren',
|
||||||
taskActions_title: 'Aufgabenaktionen',
|
taskActions_title: 'Aufgabenaktionen',
|
||||||
tasks: 'Aufgaben',
|
tasks: 'Aufgaben',
|
||||||
thereIsNoPreviewAvailableForThisAttachment: 'Für diesen Anhang ist keine Vorschau verfügbar',
|
thereIsNoPreviewAvailableForThisAttachment: 'Für diesen Anhang ist keine Vorschau verfügbar',
|
||||||
time: 'Zeit',
|
time: 'Zeit',
|
||||||
timer: 'Timer',
|
|
||||||
title: 'Titel',
|
title: 'Titel',
|
||||||
userActions_title: 'Benutzeraktionen',
|
userActions_title: 'Benutzeraktionen',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> hat diese Karte hinzugefügt zu {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> hat diese Karte hinzugefügt zu {{list}}</1>',
|
||||||
@@ -193,7 +193,7 @@ export default {
|
|||||||
editDescription_title: 'Beschreibung ändern',
|
editDescription_title: 'Beschreibung ändern',
|
||||||
editEmail_title: 'E-Mail-Adresse bearbeiten',
|
editEmail_title: 'E-Mail-Adresse bearbeiten',
|
||||||
editPassword_title: 'Passwort ändern',
|
editPassword_title: 'Passwort ändern',
|
||||||
editTimer_title: 'Timer bearbeiten',
|
editStopwatch_title: 'Stoppuhr bearbeiten',
|
||||||
editTitle_title: 'Titel bearbeiten',
|
editTitle_title: 'Titel bearbeiten',
|
||||||
editUsername_title: 'Benutzername ändern',
|
editUsername_title: 'Benutzername ändern',
|
||||||
hideDetails: 'Details ausblenden',
|
hideDetails: 'Details ausblenden',
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default {
|
|||||||
editLabel_title: 'Edit Label',
|
editLabel_title: 'Edit Label',
|
||||||
editPassword_title: 'Edit Password',
|
editPassword_title: 'Edit Password',
|
||||||
editPermissions_title: 'Edit Permissions',
|
editPermissions_title: 'Edit Permissions',
|
||||||
editTimer_title: 'Edit Timer',
|
editStopwatch_title: 'Edit Stopwatch',
|
||||||
editUsername_title: 'Edit Username',
|
editUsername_title: 'Edit Username',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'E-mail already in use',
|
emailAlreadyInUse: 'E-mail already in use',
|
||||||
@@ -138,13 +138,13 @@ export default {
|
|||||||
selectPermissions_title: 'Select Permissions',
|
selectPermissions_title: 'Select Permissions',
|
||||||
selectProject: 'Select project',
|
selectProject: 'Select project',
|
||||||
settings: 'Settings',
|
settings: 'Settings',
|
||||||
|
stopwatch: 'Stopwatch',
|
||||||
subscribeToMyOwnCardsByDefault: 'Subscribe to my own cards by default',
|
subscribeToMyOwnCardsByDefault: 'Subscribe to my own cards by default',
|
||||||
taskActions_title: 'Task Actions',
|
taskActions_title: 'Task Actions',
|
||||||
tasks: 'Tasks',
|
tasks: 'Tasks',
|
||||||
thereIsNoPreviewAvailableForThisAttachment:
|
thereIsNoPreviewAvailableForThisAttachment:
|
||||||
'There is no preview available for this attachment',
|
'There is no preview available for this attachment',
|
||||||
time: 'Time',
|
time: 'Time',
|
||||||
timer: 'Timer',
|
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
userActions_title: 'User Actions',
|
userActions_title: 'User Actions',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> added this card to {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> added this card to {{list}}</1>',
|
||||||
@@ -201,7 +201,7 @@ export default {
|
|||||||
editInformation_title: 'Edit Information',
|
editInformation_title: 'Edit Information',
|
||||||
editPassword_title: 'Edit Password',
|
editPassword_title: 'Edit Password',
|
||||||
editPermissions: 'Edit permissions',
|
editPermissions: 'Edit permissions',
|
||||||
editTimer_title: 'Edit Timer',
|
editStopwatch_title: 'Edit Stopwatch',
|
||||||
editTitle_title: 'Edit Title',
|
editTitle_title: 'Edit Title',
|
||||||
editUsername_title: 'Edit Username',
|
editUsername_title: 'Edit Username',
|
||||||
hideDetails: 'Hide details',
|
hideDetails: 'Hide details',
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export default {
|
|||||||
editEmail_title: 'Editar Correo',
|
editEmail_title: 'Editar Correo',
|
||||||
editLabel_title: 'Editar Etiqueta',
|
editLabel_title: 'Editar Etiqueta',
|
||||||
editPassword_title: 'Editar Contraseña',
|
editPassword_title: 'Editar Contraseña',
|
||||||
editTimer_title: 'Editar Temporizador',
|
editStopwatch_title: 'Editar Temporizador',
|
||||||
editUsername_title: 'Edit nombre de usuario',
|
editUsername_title: 'Edit nombre de usuario',
|
||||||
email: 'Correo',
|
email: 'Correo',
|
||||||
emailAlreadyInUse: 'El correo ya está en uso',
|
emailAlreadyInUse: 'El correo ya está en uso',
|
||||||
@@ -115,11 +115,11 @@ export default {
|
|||||||
selectList: 'Seleccionar Lista',
|
selectList: 'Seleccionar Lista',
|
||||||
selectProject: 'Seleccionar Proyecto',
|
selectProject: 'Seleccionar Proyecto',
|
||||||
settings: 'Configuraciones',
|
settings: 'Configuraciones',
|
||||||
|
stopwatch: 'Temporizador',
|
||||||
subscribeToMyOwnCardsByDefault: 'Suscribirme a mis propias tarjetas por defecto',
|
subscribeToMyOwnCardsByDefault: 'Suscribirme a mis propias tarjetas por defecto',
|
||||||
taskActions_title: 'Acciones de Tareas',
|
taskActions_title: 'Acciones de Tareas',
|
||||||
tasks: 'Tareas',
|
tasks: 'Tareas',
|
||||||
time: 'Tiempo',
|
time: 'Tiempo',
|
||||||
timer: 'Temporizador',
|
|
||||||
title: 'Título',
|
title: 'Título',
|
||||||
userActions_title: 'Acciones de Usuario',
|
userActions_title: 'Acciones de Usuario',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> añadido a esta tarjeta en {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> añadido a esta tarjeta en {{list}}</1>',
|
||||||
@@ -172,7 +172,7 @@ export default {
|
|||||||
editDescription_title: 'Editar Descripción',
|
editDescription_title: 'Editar Descripción',
|
||||||
editEmail_title: 'Editar Correo',
|
editEmail_title: 'Editar Correo',
|
||||||
editPassword_title: 'Editar Contraseña',
|
editPassword_title: 'Editar Contraseña',
|
||||||
editTimer_title: 'Edit Temporizador',
|
editStopwatch_title: 'Edit Temporizador',
|
||||||
editTitle_title: 'Edit Título',
|
editTitle_title: 'Edit Título',
|
||||||
editUsername_title: 'Editar Nombre de Usuario',
|
editUsername_title: 'Editar Nombre de Usuario',
|
||||||
logOut_title: 'Cerrar Sesión',
|
logOut_title: 'Cerrar Sesión',
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export default {
|
|||||||
editEmail_title: "Modifier l'e-mail",
|
editEmail_title: "Modifier l'e-mail",
|
||||||
editLabel_title: "Modifier l'étiquette",
|
editLabel_title: "Modifier l'étiquette",
|
||||||
editPassword_title: 'Modifier le mot de passe',
|
editPassword_title: 'Modifier le mot de passe',
|
||||||
editTimer_title: 'Modifier la minuterie',
|
editStopwatch_title: 'Modifier la minuterie',
|
||||||
editUsername_title: "Modifier le nom d'utilisateur",
|
editUsername_title: "Modifier le nom d'utilisateur",
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'Email déjà utilisé',
|
emailAlreadyInUse: 'Email déjà utilisé',
|
||||||
@@ -114,11 +114,11 @@ export default {
|
|||||||
selectList: 'Sélectionner une liste',
|
selectList: 'Sélectionner une liste',
|
||||||
selectProject: 'Sélectionner un projet',
|
selectProject: 'Sélectionner un projet',
|
||||||
settings: 'Réglages',
|
settings: 'Réglages',
|
||||||
|
stopwatch: 'Minuteur',
|
||||||
subscribeToMyOwnCardsByDefault: 'Abonnez-vous à mes propres cartes par défaut',
|
subscribeToMyOwnCardsByDefault: 'Abonnez-vous à mes propres cartes par défaut',
|
||||||
taskActions_title: 'Actions de tâche',
|
taskActions_title: 'Actions de tâche',
|
||||||
tasks: 'Tâches',
|
tasks: 'Tâches',
|
||||||
time: 'Temps',
|
time: 'Temps',
|
||||||
timer: 'Minuteur',
|
|
||||||
title: 'Titre',
|
title: 'Titre',
|
||||||
userActions_title: "Actions de l'utilisateur",
|
userActions_title: "Actions de l'utilisateur",
|
||||||
userAddedThisCardToList: '<0> {{user}} </0> <1> a ajouté cette carte à {{list}} </1>',
|
userAddedThisCardToList: '<0> {{user}} </0> <1> a ajouté cette carte à {{list}} </1>',
|
||||||
@@ -172,7 +172,7 @@ export default {
|
|||||||
editDescription_title: 'Éditer la description',
|
editDescription_title: 'Éditer la description',
|
||||||
editEmail_title: "Modifier l'e-mail",
|
editEmail_title: "Modifier l'e-mail",
|
||||||
editPassword_title: 'Modifier le mot de passe',
|
editPassword_title: 'Modifier le mot de passe',
|
||||||
editTimer_title: 'Modifier la minuterie',
|
editStopwatch_title: 'Modifier la minuterie',
|
||||||
editTitle_title: 'Modifier le titre',
|
editTitle_title: 'Modifier le titre',
|
||||||
editUsername_title: "Modifier le nom d'utilisateur",
|
editUsername_title: "Modifier le nom d'utilisateur",
|
||||||
logOut_title: 'Se déconnecter',
|
logOut_title: 'Se déconnecter',
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export default {
|
|||||||
editLabel_title: 'Modifica Etichetta',
|
editLabel_title: 'Modifica Etichetta',
|
||||||
editPassword_title: 'Modifica Password',
|
editPassword_title: 'Modifica Password',
|
||||||
editPermissions_title: 'Modifica Permessi',
|
editPermissions_title: 'Modifica Permessi',
|
||||||
editTimer_title: 'Modifica Timer',
|
editStopwatch_title: 'Modifica Timer',
|
||||||
editUsername_title: 'Modifica Username',
|
editUsername_title: 'Modifica Username',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'E-mail già in uso',
|
emailAlreadyInUse: 'E-mail già in uso',
|
||||||
@@ -135,13 +135,13 @@ export default {
|
|||||||
selectPermissions_title: 'Seleziona Permessi',
|
selectPermissions_title: 'Seleziona Permessi',
|
||||||
selectProject: 'Seleziona progetto',
|
selectProject: 'Seleziona progetto',
|
||||||
settings: 'Impostazioni',
|
settings: 'Impostazioni',
|
||||||
|
stopwatch: 'Timer',
|
||||||
subscribeToMyOwnCardsByDefault: 'Abbonati alle mie card per impostazione predefinita',
|
subscribeToMyOwnCardsByDefault: 'Abbonati alle mie card per impostazione predefinita',
|
||||||
taskActions_title: 'Task Azioni',
|
taskActions_title: 'Task Azioni',
|
||||||
tasks: 'Tasks',
|
tasks: 'Tasks',
|
||||||
thereIsNoPreviewAvailableForThisAttachment:
|
thereIsNoPreviewAvailableForThisAttachment:
|
||||||
'Non è disponibile alcuna anteprima per questo allegato',
|
'Non è disponibile alcuna anteprima per questo allegato',
|
||||||
time: 'Tempo',
|
time: 'Tempo',
|
||||||
timer: 'Timer',
|
|
||||||
title: 'Titolo',
|
title: 'Titolo',
|
||||||
userActions_title: 'Azioni Utente',
|
userActions_title: 'Azioni Utente',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> ha aggiunto questa card a {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> ha aggiunto questa card a {{list}}</1>',
|
||||||
@@ -198,7 +198,7 @@ export default {
|
|||||||
editInformation_title: 'Modifca Informazioni',
|
editInformation_title: 'Modifca Informazioni',
|
||||||
editPassword_title: 'Modifica Password',
|
editPassword_title: 'Modifica Password',
|
||||||
editPermissions: 'Modifica permessi',
|
editPermissions: 'Modifica permessi',
|
||||||
editTimer_title: 'Modifica Timer',
|
editStopwatch_title: 'Modifica Timer',
|
||||||
editTitle_title: 'Modifica Titolo',
|
editTitle_title: 'Modifica Titolo',
|
||||||
editUsername_title: 'Modifica Username',
|
editUsername_title: 'Modifica Username',
|
||||||
hideDetails: 'Nascondi dettagli',
|
hideDetails: 'Nascondi dettagli',
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export default {
|
|||||||
editEmail_title: 'Eメールを編集',
|
editEmail_title: 'Eメールを編集',
|
||||||
editLabel_title: 'ラベルを編集',
|
editLabel_title: 'ラベルを編集',
|
||||||
editPassword_title: 'パスワードを編集',
|
editPassword_title: 'パスワードを編集',
|
||||||
editTimer_title: 'タイマーを編集',
|
editStopwatch_title: 'タイマーを編集',
|
||||||
editUsername_title: 'ユーザー名を編集',
|
editUsername_title: 'ユーザー名を編集',
|
||||||
email: 'Eメール',
|
email: 'Eメール',
|
||||||
emailAlreadyInUse: 'Eメールは既に使われています',
|
emailAlreadyInUse: 'Eメールは既に使われています',
|
||||||
@@ -127,11 +127,11 @@ export default {
|
|||||||
selectList: 'リストを選択',
|
selectList: 'リストを選択',
|
||||||
selectProject: 'プロジェクトを選択',
|
selectProject: 'プロジェクトを選択',
|
||||||
settings: '設定',
|
settings: '設定',
|
||||||
|
stopwatch: 'タイマー',
|
||||||
subscribeToMyOwnCardsByDefault: '自分のカードをデフォルトで購読する',
|
subscribeToMyOwnCardsByDefault: '自分のカードをデフォルトで購読する',
|
||||||
taskActions_title: 'タスクのアクション',
|
taskActions_title: 'タスクのアクション',
|
||||||
tasks: 'タスク',
|
tasks: 'タスク',
|
||||||
time: '時間',
|
time: '時間',
|
||||||
timer: 'タイマー',
|
|
||||||
title: 'タイトル',
|
title: 'タイトル',
|
||||||
userActions_title: 'ユーザーのアクション',
|
userActions_title: 'ユーザーのアクション',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0> は <1>{{list}} をこのカードに追加しました</1>',
|
userAddedThisCardToList: '<0>{{user}}</0> は <1>{{list}} をこのカードに追加しました</1>',
|
||||||
@@ -185,7 +185,7 @@ export default {
|
|||||||
editDescription_title: '説明を編集',
|
editDescription_title: '説明を編集',
|
||||||
editEmail_title: 'Eメールを編集',
|
editEmail_title: 'Eメールを編集',
|
||||||
editPassword_title: 'パスワードの編集',
|
editPassword_title: 'パスワードの編集',
|
||||||
editTimer_title: 'タイマーの編集',
|
editStopwatch_title: 'タイマーの編集',
|
||||||
editTitle_title: 'タイトルの編集',
|
editTitle_title: 'タイトルの編集',
|
||||||
editUsername_title: 'ユーザー名の編集',
|
editUsername_title: 'ユーザー名の編集',
|
||||||
leaveBoard: 'ボードから退出',
|
leaveBoard: 'ボードから退出',
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export default {
|
|||||||
editInformation_title: '정보 수정',
|
editInformation_title: '정보 수정',
|
||||||
editLabel_title: '라벨 수정',
|
editLabel_title: '라벨 수정',
|
||||||
editPassword_title: '비밀번호 수정',
|
editPassword_title: '비밀번호 수정',
|
||||||
editTimer_title: '타이머 수정',
|
editStopwatch_title: '타이머 수정',
|
||||||
editUsername_title: '사용자이름 수정',
|
editUsername_title: '사용자이름 수정',
|
||||||
email: '메일',
|
email: '메일',
|
||||||
emailAlreadyInUse: '기 가입된 이메일이 존재합니다.',
|
emailAlreadyInUse: '기 가입된 이메일이 존재합니다.',
|
||||||
@@ -130,13 +130,13 @@ export default {
|
|||||||
selectList: '리스트 선택',
|
selectList: '리스트 선택',
|
||||||
selectProject: '프로젝트 선택',
|
selectProject: '프로젝트 선택',
|
||||||
settings: '설정',
|
settings: '설정',
|
||||||
|
stopwatch: '활동시간',
|
||||||
subscribeToMyOwnCardsByDefault: '내 항목카드는 기본으로 구독',
|
subscribeToMyOwnCardsByDefault: '내 항목카드는 기본으로 구독',
|
||||||
taskActions_title: '태스크 활동',
|
taskActions_title: '태스크 활동',
|
||||||
tasks: '태스크',
|
tasks: '태스크',
|
||||||
thereIsNoPreviewAvailableForThisAttachment:
|
thereIsNoPreviewAvailableForThisAttachment:
|
||||||
'이 첨부 파일에 사용할 수 있는 미리보기가 없습니다.',
|
'이 첨부 파일에 사용할 수 있는 미리보기가 없습니다.',
|
||||||
time: '시간',
|
time: '시간',
|
||||||
timer: '활동시간',
|
|
||||||
title: '제목',
|
title: '제목',
|
||||||
userActions_title: '',
|
userActions_title: '',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1>이 카드를 {{list}}</1>에 추가했습니다.',
|
userAddedThisCardToList: '<0>{{user}}</0><1>이 카드를 {{list}}</1>에 추가했습니다.',
|
||||||
@@ -190,7 +190,7 @@ export default {
|
|||||||
editEmail_title: '메일 수정',
|
editEmail_title: '메일 수정',
|
||||||
editInformation_title: '정보 수정',
|
editInformation_title: '정보 수정',
|
||||||
editPassword_title: '패스워드 수정',
|
editPassword_title: '패스워드 수정',
|
||||||
editTimer_title: '시간 수정',
|
editStopwatch_title: '시간 수정',
|
||||||
editTitle_title: '제목 수정',
|
editTitle_title: '제목 수정',
|
||||||
editUsername_title: '이름 수정',
|
editUsername_title: '이름 수정',
|
||||||
leaveBoard: '보드에서 나가기',
|
leaveBoard: '보드에서 나가기',
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export default {
|
|||||||
editEmail_title: 'Edytuj E-mail',
|
editEmail_title: 'Edytuj E-mail',
|
||||||
editLabel_title: 'Edytuj Oznaczenie',
|
editLabel_title: 'Edytuj Oznaczenie',
|
||||||
editPassword_title: 'Edytuj Hasło',
|
editPassword_title: 'Edytuj Hasło',
|
||||||
editTimer_title: 'Edytuj Odliczanie',
|
editStopwatch_title: 'Edytuj Odliczanie',
|
||||||
editUsername_title: 'Edytuj Nazwę Użytkownika',
|
editUsername_title: 'Edytuj Nazwę Użytkownika',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'E-mail jest już używany',
|
emailAlreadyInUse: 'E-mail jest już używany',
|
||||||
@@ -115,11 +115,11 @@ export default {
|
|||||||
selectList: 'Wybierz listę',
|
selectList: 'Wybierz listę',
|
||||||
selectProject: 'Wybierz projekt',
|
selectProject: 'Wybierz projekt',
|
||||||
settings: 'Ustawienia',
|
settings: 'Ustawienia',
|
||||||
|
stopwatch: 'Odliczanie',
|
||||||
subscribeToMyOwnCardsByDefault: 'Zapisz mnie do moich kart automatycznie',
|
subscribeToMyOwnCardsByDefault: 'Zapisz mnie do moich kart automatycznie',
|
||||||
taskActions_title: 'Akcje Zadania',
|
taskActions_title: 'Akcje Zadania',
|
||||||
tasks: 'Zadania',
|
tasks: 'Zadania',
|
||||||
time: 'Czas',
|
time: 'Czas',
|
||||||
timer: 'Odliczanie',
|
|
||||||
title: 'Tytuł',
|
title: 'Tytuł',
|
||||||
userActions_title: 'Akcje użytkownika',
|
userActions_title: 'Akcje użytkownika',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> dodał tę kartę w {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> dodał tę kartę w {{list}}</1>',
|
||||||
@@ -172,7 +172,7 @@ export default {
|
|||||||
editDescription_title: 'Edytuj Opis',
|
editDescription_title: 'Edytuj Opis',
|
||||||
editEmail_title: 'Edytuj E-mail',
|
editEmail_title: 'Edytuj E-mail',
|
||||||
editPassword_title: 'Edytuj Hasło',
|
editPassword_title: 'Edytuj Hasło',
|
||||||
editTimer_title: 'Edytuj Odliczanie',
|
editStopwatch_title: 'Edytuj Odliczanie',
|
||||||
editTitle_title: 'Edytuj Tytuł',
|
editTitle_title: 'Edytuj Tytuł',
|
||||||
editUsername_title: 'Edytuj Nazwę Użytkownika',
|
editUsername_title: 'Edytuj Nazwę Użytkownika',
|
||||||
logOut_title: 'Wyloguj',
|
logOut_title: 'Wyloguj',
|
||||||
|
|||||||
@@ -13,10 +13,12 @@ export default {
|
|||||||
|
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
|
aboutPlanka: 'О проекте Planka',
|
||||||
account: 'Учетная запись',
|
account: 'Учетная запись',
|
||||||
actions: 'Действия',
|
actions: 'Действия',
|
||||||
addAttachment: 'Добавление вложения',
|
addAttachment: 'Добавление вложения',
|
||||||
addComment: 'Добавление комментария',
|
addComment: 'Добавление комментария',
|
||||||
|
addManager_title: 'Добавление менеджера',
|
||||||
addMember: 'Добавление участника',
|
addMember: 'Добавление участника',
|
||||||
addUser: 'Добавление пользователя',
|
addUser: 'Добавление пользователя',
|
||||||
administrator: 'Администратор',
|
administrator: 'Администратор',
|
||||||
@@ -32,11 +34,16 @@ export default {
|
|||||||
areYouSureYouWantToDeleteThisProject: 'Вы уверены, что хотите удалить этот проект?',
|
areYouSureYouWantToDeleteThisProject: 'Вы уверены, что хотите удалить этот проект?',
|
||||||
areYouSureYouWantToDeleteThisTask: 'Вы уверены, что хотите удалить эту задачу?',
|
areYouSureYouWantToDeleteThisTask: 'Вы уверены, что хотите удалить эту задачу?',
|
||||||
areYouSureYouWantToDeleteThisUser: 'Вы уверены, что хотите удалить этого пользователя?',
|
areYouSureYouWantToDeleteThisUser: 'Вы уверены, что хотите удалить этого пользователя?',
|
||||||
areYouSureYouWantToRemoveThisMemberFromProject:
|
areYouSureYouWantToLeaveBoard: 'Вы уверены, что хотите покинуть эту доску?',
|
||||||
'Вы уверены, что хотите удалить этого участника из проекта?',
|
areYouSureYouWantToLeaveProject: 'Вы уверены, что хотите покинуть этот проект?',
|
||||||
|
areYouSureYouWantToRemoveThisManagerFromProject:
|
||||||
|
'Вы уверены, что хотите удалить этого менеджера из проекта?',
|
||||||
|
areYouSureYouWantToRemoveThisMemberFromBoard:
|
||||||
|
'Вы уверены, что хотите удалить этого участника из доски?',
|
||||||
attachment: 'Вложение',
|
attachment: 'Вложение',
|
||||||
attachments: 'Вложения',
|
attachments: 'Вложения',
|
||||||
authentication: 'Аутентификация',
|
authentication: 'Аутентификация',
|
||||||
|
background: 'Фон',
|
||||||
board: 'Доска',
|
board: 'Доска',
|
||||||
boardNotFound: 'Доска не найдена',
|
boardNotFound: 'Доска не найдена',
|
||||||
canComment: 'Может комментировать',
|
canComment: 'Может комментировать',
|
||||||
@@ -53,6 +60,7 @@ export default {
|
|||||||
createProject: 'Создание проекта',
|
createProject: 'Создание проекта',
|
||||||
createTextFile: 'Создание текстового файла',
|
createTextFile: 'Создание текстового файла',
|
||||||
currentPassword: 'Текущий пароль',
|
currentPassword: 'Текущий пароль',
|
||||||
|
dangerZone_title: 'Опасная зона',
|
||||||
date: 'Дата',
|
date: 'Дата',
|
||||||
dueDate: 'Срок',
|
dueDate: 'Срок',
|
||||||
deleteAttachment: 'Удаление вложения',
|
deleteAttachment: 'Удаление вложения',
|
||||||
@@ -74,10 +82,11 @@ export default {
|
|||||||
editBackground: 'Изменение фона',
|
editBackground: 'Изменение фона',
|
||||||
editDueDate: 'Изменение срока',
|
editDueDate: 'Изменение срока',
|
||||||
editEmail: 'Изменение e-mail',
|
editEmail: 'Изменение e-mail',
|
||||||
editLabel: 'Изменения метки',
|
editInformation: 'Изменение информации',
|
||||||
|
editLabel: 'Изменение метки',
|
||||||
editPassword: 'Изменение пароля',
|
editPassword: 'Изменение пароля',
|
||||||
editPermissions: 'Редактирование разрешений',
|
editPermissions: 'Редактирование разрешений',
|
||||||
editTimer: 'Изменение таймера',
|
editStopwatch: 'Изменение секундомера',
|
||||||
editTitle: 'Изменение названия',
|
editTitle: 'Изменение названия',
|
||||||
editUsername: 'Изменение имени пользователя',
|
editUsername: 'Изменение имени пользователя',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
@@ -91,12 +100,18 @@ export default {
|
|||||||
filterByLabels: 'Фильтр по меткам',
|
filterByLabels: 'Фильтр по меткам',
|
||||||
filterByMembers: 'Фильтр по участникам',
|
filterByMembers: 'Фильтр по участникам',
|
||||||
fromComputer: 'С компьютера',
|
fromComputer: 'С компьютера',
|
||||||
|
fromTrello: 'Из Trello',
|
||||||
|
general: 'Основные',
|
||||||
hours: 'Часы',
|
hours: 'Часы',
|
||||||
|
importBoard: 'Импорт доски',
|
||||||
invalidCurrentPassword: 'Неверный текущий пароль',
|
invalidCurrentPassword: 'Неверный текущий пароль',
|
||||||
labels: 'Метки',
|
labels: 'Метки',
|
||||||
language: 'Язык',
|
language: 'Язык',
|
||||||
|
leaveBoard: 'Покинуть доску',
|
||||||
|
leaveProject: 'Покинуть проект',
|
||||||
list: 'Список',
|
list: 'Список',
|
||||||
listActions: 'Действия со списком',
|
listActions: 'Действия со списком',
|
||||||
|
managers: 'Менеджеры',
|
||||||
members: 'Участники',
|
members: 'Участники',
|
||||||
minutes: 'Минуты',
|
minutes: 'Минуты',
|
||||||
moveCard: 'Перемещение карточки',
|
moveCard: 'Перемещение карточки',
|
||||||
@@ -120,6 +135,7 @@ export default {
|
|||||||
project: 'Проект',
|
project: 'Проект',
|
||||||
projectActions: 'Действия с проектом',
|
projectActions: 'Действия с проектом',
|
||||||
projectNotFound: 'Проект не найден',
|
projectNotFound: 'Проект не найден',
|
||||||
|
removeManager: 'Удалить менеджера',
|
||||||
removeMember: 'Удаление участника',
|
removeMember: 'Удаление участника',
|
||||||
searchLabels: 'Поиск меток...',
|
searchLabels: 'Поиск меток...',
|
||||||
searchMembers: 'Поиск участников...',
|
searchMembers: 'Поиск участников...',
|
||||||
@@ -130,12 +146,12 @@ export default {
|
|||||||
selectPermissions: 'Выбор разрешений',
|
selectPermissions: 'Выбор разрешений',
|
||||||
selectProject: 'Выберите проект',
|
selectProject: 'Выберите проект',
|
||||||
settings: 'Настройки',
|
settings: 'Настройки',
|
||||||
subscribeToMyOwnCardsByDefault: 'По умолчанию подписаться на мои собственные карточки',
|
stopwatch: 'Секундомер',
|
||||||
|
subscribeToMyOwnCardsByDefault: 'По умолчанию подписываться на мои собственные карточки',
|
||||||
taskActions: 'Действия с задачей',
|
taskActions: 'Действия с задачей',
|
||||||
tasks: 'Задачи',
|
tasks: 'Задачи',
|
||||||
thereIsNoPreviewAvailableForThisAttachment: 'Предпросмотр для этого вложения недоступен',
|
thereIsNoPreviewAvailableForThisAttachment: 'Предпросмотр для этого вложения недоступен',
|
||||||
time: 'Время',
|
time: 'Время',
|
||||||
timer: 'Таймер',
|
|
||||||
title: 'Название',
|
title: 'Название',
|
||||||
userActions: 'Действия с пользователем',
|
userActions: 'Действия с пользователем',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> добавил(а) эту карточку в {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> добавил(а) эту карточку в {{list}}</1>',
|
||||||
@@ -147,6 +163,7 @@ export default {
|
|||||||
username: 'Имя пользователя',
|
username: 'Имя пользователя',
|
||||||
usernameAlreadyInUse: 'Имя пользователя уже занято',
|
usernameAlreadyInUse: 'Имя пользователя уже занято',
|
||||||
users: 'Пользователи',
|
users: 'Пользователи',
|
||||||
|
version: 'Версия',
|
||||||
viewer: 'Читатель',
|
viewer: 'Читатель',
|
||||||
writeComment: 'Напишите комментарий...',
|
writeComment: 'Напишите комментарий...',
|
||||||
},
|
},
|
||||||
@@ -185,13 +202,17 @@ export default {
|
|||||||
editDueDate: 'Изменить срок',
|
editDueDate: 'Изменить срок',
|
||||||
editDescription: 'Изменить описание',
|
editDescription: 'Изменить описание',
|
||||||
editEmail: 'Изменить e-mail',
|
editEmail: 'Изменить e-mail',
|
||||||
|
editInformation: 'Изменить информацию',
|
||||||
editPassword: 'Изменить пароль',
|
editPassword: 'Изменить пароль',
|
||||||
editPermissions: 'Изменить разрешения',
|
editPermissions: 'Изменить разрешения',
|
||||||
|
editStopwatch: 'Изменить секундомер',
|
||||||
editTask: 'Изменить задачу',
|
editTask: 'Изменить задачу',
|
||||||
editTimer: 'Изменить таймер',
|
|
||||||
editTitle: 'Изменить название',
|
editTitle: 'Изменить название',
|
||||||
editUsername: 'Изменить имя пользователя',
|
editUsername: 'Изменить имя пользователя',
|
||||||
hideDetails: 'Скрыть подробности',
|
hideDetails: 'Скрыть подробности',
|
||||||
|
import: 'Импорт',
|
||||||
|
leaveBoard: 'Покинуть доску',
|
||||||
|
leaveProject: 'Покинуть проект',
|
||||||
logOut: 'Выйти',
|
logOut: 'Выйти',
|
||||||
makeCover: 'Сделать обложкой',
|
makeCover: 'Сделать обложкой',
|
||||||
move: 'Переместить',
|
move: 'Переместить',
|
||||||
@@ -199,7 +220,9 @@ export default {
|
|||||||
remove: 'Убрать',
|
remove: 'Убрать',
|
||||||
removeBackground: 'Убрать фон',
|
removeBackground: 'Убрать фон',
|
||||||
removeCover: 'Убрать обложку',
|
removeCover: 'Убрать обложку',
|
||||||
|
removeFromBoard: 'Удалить из доски',
|
||||||
removeFromProject: 'Удалить из проекта',
|
removeFromProject: 'Удалить из проекта',
|
||||||
|
removeManager: 'Удалить менеджера',
|
||||||
removeMember: 'Удалить участника',
|
removeMember: 'Удалить участника',
|
||||||
save: 'Сохранить',
|
save: 'Сохранить',
|
||||||
showAllAttachments: 'Показать все вложения ({{hidden}} скрыто)',
|
showAllAttachments: 'Показать все вложения ({{hidden}} скрыто)',
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export default {
|
|||||||
editInformation_title: 'Upraviť informácie',
|
editInformation_title: 'Upraviť informácie',
|
||||||
editLabel_title: 'Upraviť štítok',
|
editLabel_title: 'Upraviť štítok',
|
||||||
editPassword_title: 'Upraviť heslo',
|
editPassword_title: 'Upraviť heslo',
|
||||||
editTimer_title: 'Upraviť časovač',
|
editStopwatch_title: 'Upraviť časovač',
|
||||||
editUsername_title: 'Upraviť používateľské meno',
|
editUsername_title: 'Upraviť používateľské meno',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'E-mail je už použitý',
|
emailAlreadyInUse: 'E-mail je už použitý',
|
||||||
@@ -128,11 +128,11 @@ export default {
|
|||||||
selectList: 'Vybrať zoznam',
|
selectList: 'Vybrať zoznam',
|
||||||
selectProject: 'Vybrať projekt',
|
selectProject: 'Vybrať projekt',
|
||||||
settings: 'Nastavenia',
|
settings: 'Nastavenia',
|
||||||
|
stopwatch: 'Časovač',
|
||||||
subscribeToMyOwnCardsByDefault: 'Predvolene odoberať vlastné karty',
|
subscribeToMyOwnCardsByDefault: 'Predvolene odoberať vlastné karty',
|
||||||
taskActions_title: 'Akcie na úlohe',
|
taskActions_title: 'Akcie na úlohe',
|
||||||
tasks: 'Úlohy',
|
tasks: 'Úlohy',
|
||||||
time: 'Čas',
|
time: 'Čas',
|
||||||
timer: 'Časovač',
|
|
||||||
title: 'Názov',
|
title: 'Názov',
|
||||||
userActions_title: 'Akcie na používateľovi',
|
userActions_title: 'Akcie na používateľovi',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> pridal kartu do {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> pridal kartu do {{list}}</1>',
|
||||||
@@ -185,7 +185,7 @@ export default {
|
|||||||
editEmail_title: 'Upraviť e-mail',
|
editEmail_title: 'Upraviť e-mail',
|
||||||
editInformation_title: 'Upraviť informácie',
|
editInformation_title: 'Upraviť informácie',
|
||||||
editPassword_title: 'Upraviť heslo',
|
editPassword_title: 'Upraviť heslo',
|
||||||
editTimer_title: 'Upraviť časovač',
|
editStopwatch_title: 'Upraviť časovač',
|
||||||
editTitle_title: 'Upraviť názov',
|
editTitle_title: 'Upraviť názov',
|
||||||
editUsername_title: 'Upraviť používateľské meno',
|
editUsername_title: 'Upraviť používateľské meno',
|
||||||
leaveBoard: 'Opustiť tabuľu',
|
leaveBoard: 'Opustiť tabuľu',
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export default {
|
|||||||
editEmail_title: 'Redigera E-mail',
|
editEmail_title: 'Redigera E-mail',
|
||||||
editLabel_title: 'Redigera Etikett',
|
editLabel_title: 'Redigera Etikett',
|
||||||
editPassword_title: 'Redigera Lösenord',
|
editPassword_title: 'Redigera Lösenord',
|
||||||
editTimer_title: 'Redigera Timer',
|
editStopwatch_title: 'Redigera Timer',
|
||||||
editUsername_title: 'Redigera Användarnamn',
|
editUsername_title: 'Redigera Användarnamn',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'E-mail används redan',
|
emailAlreadyInUse: 'E-mail används redan',
|
||||||
@@ -129,11 +129,11 @@ export default {
|
|||||||
selectList: 'Välj lista',
|
selectList: 'Välj lista',
|
||||||
selectProject: 'Välj projekt',
|
selectProject: 'Välj projekt',
|
||||||
settings: 'Inställningar',
|
settings: 'Inställningar',
|
||||||
|
stopwatch: 'Timer',
|
||||||
subscribeToMyOwnCardsByDefault: 'Prenumerera på mina egna kort som standard',
|
subscribeToMyOwnCardsByDefault: 'Prenumerera på mina egna kort som standard',
|
||||||
taskActions_title: 'Uppgiftsåtgärder',
|
taskActions_title: 'Uppgiftsåtgärder',
|
||||||
tasks: 'Uppgifter',
|
tasks: 'Uppgifter',
|
||||||
time: 'Tid',
|
time: 'Tid',
|
||||||
timer: 'Timer',
|
|
||||||
title: 'Titel',
|
title: 'Titel',
|
||||||
userActions_title: 'Användaråtgärder',
|
userActions_title: 'Användaråtgärder',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> lade till detta kort i {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> lade till detta kort i {{list}}</1>',
|
||||||
@@ -186,7 +186,7 @@ export default {
|
|||||||
editDescription_title: 'Redigera Beskrivning',
|
editDescription_title: 'Redigera Beskrivning',
|
||||||
editEmail_title: 'Redigera E-mail',
|
editEmail_title: 'Redigera E-mail',
|
||||||
editPassword_title: 'Redigera Lösenord',
|
editPassword_title: 'Redigera Lösenord',
|
||||||
editTimer_title: 'Redigera Timer',
|
editStopwatch_title: 'Redigera Timer',
|
||||||
editTitle_title: 'Redigera Titel',
|
editTitle_title: 'Redigera Titel',
|
||||||
editUsername_title: 'Redigera Användarnamn',
|
editUsername_title: 'Redigera Användarnamn',
|
||||||
leaveBoard: 'Lämna tavla',
|
leaveBoard: 'Lämna tavla',
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export default {
|
|||||||
editEmail_title: 'E-mail ni Tahrirlash',
|
editEmail_title: 'E-mail ni Tahrirlash',
|
||||||
editLabel_title: 'Yorliqni Tahrirlash',
|
editLabel_title: 'Yorliqni Tahrirlash',
|
||||||
editPassword_title: 'Parolni Tahrirlash',
|
editPassword_title: 'Parolni Tahrirlash',
|
||||||
editTimer_title: 'Taymerni Tahrirlash',
|
editStopwatch_title: 'Taymerni Tahrirlash',
|
||||||
editUsername_title: 'Foydalanuvchi nomini Tahrirlash',
|
editUsername_title: 'Foydalanuvchi nomini Tahrirlash',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'E-mail allaqachon mavjud',
|
emailAlreadyInUse: 'E-mail allaqachon mavjud',
|
||||||
@@ -122,11 +122,11 @@ export default {
|
|||||||
selectList: "Ro'yxatni tanlash",
|
selectList: "Ro'yxatni tanlash",
|
||||||
selectProject: "Loyihani '",
|
selectProject: "Loyihani '",
|
||||||
settings: 'Sozlamalar',
|
settings: 'Sozlamalar',
|
||||||
|
stopwatch: 'Taymer',
|
||||||
subscribeToMyOwnCardsByDefault: "Odatiy holda o'z kartalarimga obuna bo'ling",
|
subscribeToMyOwnCardsByDefault: "Odatiy holda o'z kartalarimga obuna bo'ling",
|
||||||
taskActions_title: 'Vazifa Amallari',
|
taskActions_title: 'Vazifa Amallari',
|
||||||
tasks: 'Vazifalar',
|
tasks: 'Vazifalar',
|
||||||
time: 'Vaqt',
|
time: 'Vaqt',
|
||||||
timer: 'Taymer',
|
|
||||||
title: 'Sarlavha',
|
title: 'Sarlavha',
|
||||||
userActions_title: 'Foydalanuvchi Amallari',
|
userActions_title: 'Foydalanuvchi Amallari',
|
||||||
userAddedThisCardToList: "<1>Ushbu kartani {{list}} ga</1><0>{{user}}</0> qo'shdi",
|
userAddedThisCardToList: "<1>Ushbu kartani {{list}} ga</1><0>{{user}}</0> qo'shdi",
|
||||||
@@ -179,7 +179,7 @@ export default {
|
|||||||
editDescription_title: 'Izohni Tahrirlash',
|
editDescription_title: 'Izohni Tahrirlash',
|
||||||
editEmail_title: 'E-mailni Tahrirlash',
|
editEmail_title: 'E-mailni Tahrirlash',
|
||||||
editPassword_title: 'Parolni Tahrirlash',
|
editPassword_title: 'Parolni Tahrirlash',
|
||||||
editTimer_title: 'Taymerni Tahrirlash',
|
editStopwatch_title: 'Taymerni Tahrirlash',
|
||||||
editTitle_title: 'Sarlavhani Tahrirlash',
|
editTitle_title: 'Sarlavhani Tahrirlash',
|
||||||
editUsername_title: 'Foydalanuvchi nomini Tahrirlash',
|
editUsername_title: 'Foydalanuvchi nomini Tahrirlash',
|
||||||
leaveBoard: 'Doskadan chiqish',
|
leaveBoard: 'Doskadan chiqish',
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default {
|
|||||||
dueDate_title: '截止日期',
|
dueDate_title: '截止日期',
|
||||||
deleteAttachment_title: '删除附件',
|
deleteAttachment_title: '删除附件',
|
||||||
deleteBoard_title: '删除面板',
|
deleteBoard_title: '删除面板',
|
||||||
deleteCard_title: '删除擦片',
|
deleteCard_title: '删除卡片',
|
||||||
deleteComment_title: '删除评论',
|
deleteComment_title: '删除评论',
|
||||||
deleteLabel_title: '删除标签',
|
deleteLabel_title: '删除标签',
|
||||||
deleteList_title: '删除列表',
|
deleteList_title: '删除列表',
|
||||||
@@ -69,7 +69,7 @@ export default {
|
|||||||
editEmail_title: '编辑邮箱',
|
editEmail_title: '编辑邮箱',
|
||||||
editLabel_title: '编辑标签',
|
editLabel_title: '编辑标签',
|
||||||
editPassword_title: '修改密码',
|
editPassword_title: '修改密码',
|
||||||
editTimer_title: '修改时间',
|
editStopwatch_title: '修改时间',
|
||||||
editUsername_title: '修改用户名',
|
editUsername_title: '修改用户名',
|
||||||
email: '邮箱',
|
email: '邮箱',
|
||||||
emailAlreadyInUse: '邮箱已使用',
|
emailAlreadyInUse: '邮箱已使用',
|
||||||
@@ -81,7 +81,7 @@ export default {
|
|||||||
enterTaskDescription: '输入任务描述...',
|
enterTaskDescription: '输入任务描述...',
|
||||||
filterByLabels_title: '通过标签筛选',
|
filterByLabels_title: '通过标签筛选',
|
||||||
filterByMembers_title: '通过成员筛选',
|
filterByMembers_title: '通过成员筛选',
|
||||||
fromComputer_title: '从电脑',
|
fromComputer_title: '从本机',
|
||||||
general: '全体',
|
general: '全体',
|
||||||
hours: '小时',
|
hours: '小时',
|
||||||
invalidCurrentPassword: '当前密码错误',
|
invalidCurrentPassword: '当前密码错误',
|
||||||
@@ -110,7 +110,7 @@ export default {
|
|||||||
phone: '电话',
|
phone: '电话',
|
||||||
preferences: '偏好',
|
preferences: '偏好',
|
||||||
pressPasteShortcutToAddAttachmentFromClipboard:
|
pressPasteShortcutToAddAttachmentFromClipboard:
|
||||||
'提示: 点击 Ctrl-V (Mac: Cmd-V) 从剪切板添加附件',
|
'提示: 按下 Ctrl-V (Mac: Cmd-V) 从剪切板添加附件',
|
||||||
project: '项目',
|
project: '项目',
|
||||||
projectNotFound_title: '项目未找到',
|
projectNotFound_title: '项目未找到',
|
||||||
removeManager_title: '删除管理员',
|
removeManager_title: '删除管理员',
|
||||||
@@ -120,11 +120,11 @@ export default {
|
|||||||
selectList: '选择列表',
|
selectList: '选择列表',
|
||||||
selectProject: '选择项目',
|
selectProject: '选择项目',
|
||||||
settings: '设置',
|
settings: '设置',
|
||||||
|
stopwatch: '计时器',
|
||||||
subscribeToMyOwnCardsByDefault: '默认关注自己创建的卡片',
|
subscribeToMyOwnCardsByDefault: '默认关注自己创建的卡片',
|
||||||
taskActions_title: '任务操作',
|
taskActions_title: '任务操作',
|
||||||
tasks: '任务',
|
tasks: '任务',
|
||||||
time: '时间',
|
time: '时间',
|
||||||
timer: '计时器',
|
|
||||||
title: '标题',
|
title: '标题',
|
||||||
userActions_title: '用户操作',
|
userActions_title: '用户操作',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> 向列表 {{list}} 添加了该卡片</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> 向列表 {{list}} 添加了该卡片</1>',
|
||||||
@@ -134,7 +134,7 @@ export default {
|
|||||||
userMovedThisCardFromListToList:
|
userMovedThisCardFromListToList:
|
||||||
'<0>{{user}}</0><1> 将该卡片从 {{fromList}} 移动到 {{toList}}</1>',
|
'<0>{{user}}</0><1> 将该卡片从 {{fromList}} 移动到 {{toList}}</1>',
|
||||||
username: '用户名',
|
username: '用户名',
|
||||||
usernameAlreadyInUse: '用户名已占用',
|
usernameAlreadyInUse: '用户名已被占用',
|
||||||
users: '用户',
|
users: '用户',
|
||||||
writeComment: '编写评论...',
|
writeComment: '编写评论...',
|
||||||
},
|
},
|
||||||
@@ -149,7 +149,7 @@ export default {
|
|||||||
addList: '添加列表',
|
addList: '添加列表',
|
||||||
addMoreDetailedDescription: '添加更多详细描述',
|
addMoreDetailedDescription: '添加更多详细描述',
|
||||||
addTask: '添加任务',
|
addTask: '添加任务',
|
||||||
addToCard: '添加擦篇',
|
addToCard: '添加到卡片',
|
||||||
addUser: '添加用户',
|
addUser: '添加用户',
|
||||||
createBoard: '创建面板',
|
createBoard: '创建面板',
|
||||||
createFile: '创建文件',
|
createFile: '创建文件',
|
||||||
@@ -177,7 +177,7 @@ export default {
|
|||||||
editDescription_title: '编辑描述',
|
editDescription_title: '编辑描述',
|
||||||
editEmail_title: '编辑邮箱',
|
editEmail_title: '编辑邮箱',
|
||||||
editPassword_title: '编辑密码',
|
editPassword_title: '编辑密码',
|
||||||
editTimer_title: '编辑实践',
|
editStopwatch_title: '编辑时间',
|
||||||
editTitle_title: '编辑标题',
|
editTitle_title: '编辑标题',
|
||||||
editUsername_title: '编辑用户名',
|
editUsername_title: '编辑用户名',
|
||||||
leaveBoard: '离开面板',
|
leaveBoard: '离开面板',
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ export default class extends BaseModel {
|
|||||||
coverUrl: attr(),
|
coverUrl: attr(),
|
||||||
image: attr(),
|
image: attr(),
|
||||||
name: attr(),
|
name: attr(),
|
||||||
|
createdAt: attr({
|
||||||
|
getDefault: () => new Date(),
|
||||||
|
}),
|
||||||
cardId: fk({
|
cardId: fk({
|
||||||
to: 'Card',
|
to: 'Card',
|
||||||
as: 'card',
|
as: 'card',
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ export default class extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getOrderedMembershipsQuerySet() {
|
getOrderedMembershipsQuerySet() {
|
||||||
return this.memberships.orderBy('id');
|
return this.memberships.orderBy('createdAt');
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrderedLabelsQuerySet() {
|
getOrderedLabelsQuerySet() {
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ export default class extends BaseModel {
|
|||||||
id: attr(),
|
id: attr(),
|
||||||
role: attr(),
|
role: attr(),
|
||||||
canComment: attr(),
|
canComment: attr(),
|
||||||
|
createdAt: attr({
|
||||||
|
getDefault: () => new Date(),
|
||||||
|
}),
|
||||||
boardId: fk({
|
boardId: fk({
|
||||||
to: 'Board',
|
to: 'Board',
|
||||||
as: 'board',
|
as: 'board',
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default class extends BaseModel {
|
|||||||
name: attr(),
|
name: attr(),
|
||||||
description: attr(),
|
description: attr(),
|
||||||
dueDate: attr(),
|
dueDate: attr(),
|
||||||
timer: attr(),
|
stopwatch: attr(),
|
||||||
isSubscribed: attr({
|
isSubscribed: attr({
|
||||||
getDefault: () => false,
|
getDefault: () => false,
|
||||||
}),
|
}),
|
||||||
@@ -244,7 +244,7 @@ export default class extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getOrderedAttachmentsQuerySet() {
|
getOrderedAttachmentsQuerySet() {
|
||||||
return this.attachments.orderBy('id', false);
|
return this.attachments.orderBy('createdAt', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilteredOrderedInCardActivitiesQuerySet() {
|
getFilteredOrderedInCardActivitiesQuerySet() {
|
||||||
@@ -256,7 +256,7 @@ export default class extends BaseModel {
|
|||||||
filter.type = ActivityTypes.COMMENT_CARD;
|
filter.type = ActivityTypes.COMMENT_CARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.activities.filter(filter).orderBy('id', false);
|
return this.activities.filter(filter).orderBy('createdAt', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
getUnreadNotificationsQuerySet() {
|
getUnreadNotificationsQuerySet() {
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ export default class extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getOrderedManagersQuerySet() {
|
getOrderedManagersQuerySet() {
|
||||||
return this.managers.orderBy('id');
|
return this.managers.orderBy('createdAt');
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrderedBoardsQuerySet() {
|
getOrderedBoardsQuerySet() {
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ export default class extends BaseModel {
|
|||||||
|
|
||||||
static fields = {
|
static fields = {
|
||||||
id: attr(),
|
id: attr(),
|
||||||
|
createdAt: attr({
|
||||||
|
getDefault: () => new Date(),
|
||||||
|
}),
|
||||||
projectId: fk({
|
projectId: fk({
|
||||||
to: 'Project',
|
to: 'Project',
|
||||||
as: 'project',
|
as: 'project',
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ export default class extends BaseModel {
|
|||||||
organization: attr(),
|
organization: attr(),
|
||||||
language: attr(),
|
language: attr(),
|
||||||
subscribeToOwnCards: attr(),
|
subscribeToOwnCards: attr(),
|
||||||
|
createdAt: attr({
|
||||||
|
getDefault: () => new Date(),
|
||||||
|
}),
|
||||||
deletedAt: attr(),
|
deletedAt: attr(),
|
||||||
isAdmin: attr({
|
isAdmin: attr({
|
||||||
getDefault: () => false,
|
getDefault: () => false,
|
||||||
@@ -293,15 +296,15 @@ export default class extends BaseModel {
|
|||||||
static getOrderedUndeletedQuerySet() {
|
static getOrderedUndeletedQuerySet() {
|
||||||
return this.filter({
|
return this.filter({
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
}).orderBy('id');
|
}).orderBy('createdAt');
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrderedProjectManagersQuerySet() {
|
getOrderedProjectManagersQuerySet() {
|
||||||
return this.projectManagers.orderBy('id');
|
return this.projectManagers.orderBy('createdAt');
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrderedBoardMembershipsQuerySet() {
|
getOrderedBoardMembershipsQuerySet() {
|
||||||
return this.boardMemberships.orderBy('id');
|
return this.boardMemberships.orderBy('createdAt');
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrderedUnreadNotificationsQuerySet() {
|
getOrderedUnreadNotificationsQuerySet() {
|
||||||
@@ -309,7 +312,7 @@ export default class extends BaseModel {
|
|||||||
.filter({
|
.filter({
|
||||||
isRead: false,
|
isRead: false,
|
||||||
})
|
})
|
||||||
.orderBy('id', false);
|
.orderBy('createdAt', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrderedAvailableProjectsModelArray() {
|
getOrderedAvailableProjectsModelArray() {
|
||||||
|
|||||||
@@ -16,17 +16,17 @@ const createSocketEventsChannel = () =>
|
|||||||
emit(entryActions.handleSocketReconnect());
|
emit(entryActions.handleSocketReconnect());
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUserCreate = ({ item }) => {
|
const handleUserCreate = api.makeHandleUserCreate(({ item }) => {
|
||||||
emit(entryActions.handleUserCreate(item));
|
emit(entryActions.handleUserCreate(item));
|
||||||
};
|
});
|
||||||
|
|
||||||
const handleUserUpdate = ({ item }) => {
|
const handleUserUpdate = api.makeHandleUserUpdate(({ item }) => {
|
||||||
emit(entryActions.handleUserUpdate(item));
|
emit(entryActions.handleUserUpdate(item));
|
||||||
};
|
});
|
||||||
|
|
||||||
const handleUserDelete = ({ item }) => {
|
const handleUserDelete = api.makeHandleUserDelete(({ item }) => {
|
||||||
emit(entryActions.handleUserDelete(item));
|
emit(entryActions.handleUserDelete(item));
|
||||||
};
|
});
|
||||||
|
|
||||||
const handleProjectCreate = ({ item }) => {
|
const handleProjectCreate = ({ item }) => {
|
||||||
emit(entryActions.handleProjectCreate(item));
|
emit(entryActions.handleProjectCreate(item));
|
||||||
@@ -40,13 +40,13 @@ const createSocketEventsChannel = () =>
|
|||||||
emit(entryActions.handleProjectDelete(item));
|
emit(entryActions.handleProjectDelete(item));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleProjectManagerCreate = ({ item }) => {
|
const handleProjectManagerCreate = api.makeHandleProjectManagerCreate(({ item }) => {
|
||||||
emit(entryActions.handleProjectManagerCreate(item));
|
emit(entryActions.handleProjectManagerCreate(item));
|
||||||
};
|
});
|
||||||
|
|
||||||
const handleProjectManagerDelete = ({ item }) => {
|
const handleProjectManagerDelete = api.makeHandleProjectManagerDelete(({ item }) => {
|
||||||
emit(entryActions.handleProjectManagerDelete(item));
|
emit(entryActions.handleProjectManagerDelete(item));
|
||||||
};
|
});
|
||||||
|
|
||||||
const handleBoardCreate = ({ item, requestId }) => {
|
const handleBoardCreate = ({ item, requestId }) => {
|
||||||
emit(entryActions.handleBoardCreate(item, requestId));
|
emit(entryActions.handleBoardCreate(item, requestId));
|
||||||
@@ -60,17 +60,17 @@ const createSocketEventsChannel = () =>
|
|||||||
emit(entryActions.handleBoardDelete(item));
|
emit(entryActions.handleBoardDelete(item));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBoardMembershipCreate = ({ item }) => {
|
const handleBoardMembershipCreate = api.makeHandleBoardMembershipCreate(({ item }) => {
|
||||||
emit(entryActions.handleBoardMembershipCreate(item));
|
emit(entryActions.handleBoardMembershipCreate(item));
|
||||||
};
|
});
|
||||||
|
|
||||||
const handleBoardMembershipUpdate = ({ item }) => {
|
const handleBoardMembershipUpdate = api.makeHandleBoardMembershipUpdate(({ item }) => {
|
||||||
emit(entryActions.handleBoardMembershipUpdate(item));
|
emit(entryActions.handleBoardMembershipUpdate(item));
|
||||||
};
|
});
|
||||||
|
|
||||||
const handleBoardMembershipDelete = ({ item }) => {
|
const handleBoardMembershipDelete = api.makeHandleBoardMembershipDelete(({ item }) => {
|
||||||
emit(entryActions.handleBoardMembershipDelete(item));
|
emit(entryActions.handleBoardMembershipDelete(item));
|
||||||
};
|
});
|
||||||
|
|
||||||
const handleListCreate = ({ item }) => {
|
const handleListCreate = ({ item }) => {
|
||||||
emit(entryActions.handleListCreate(item));
|
emit(entryActions.handleListCreate(item));
|
||||||
|
|||||||
@@ -6,28 +6,28 @@ const getFullSeconds = ({ startedAt, total }) => {
|
|||||||
return total;
|
return total;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createTimer = ({ hours, minutes, seconds }) => ({
|
export const createStopwatch = ({ hours, minutes, seconds }) => ({
|
||||||
startedAt: null,
|
startedAt: null,
|
||||||
total: hours * 60 * 60 + minutes * 60 + seconds,
|
total: hours * 60 * 60 + minutes * 60 + seconds,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const updateTimer = ({ startedAt }, parts) => ({
|
export const updateStopwatch = ({ startedAt }, parts) => ({
|
||||||
...createTimer(parts),
|
...createStopwatch(parts),
|
||||||
startedAt: startedAt && new Date(),
|
startedAt: startedAt && new Date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const startTimer = (timer) => ({
|
export const startStopwatch = (stopwatch) => ({
|
||||||
startedAt: new Date(),
|
startedAt: new Date(),
|
||||||
total: timer ? timer.total : 0,
|
total: stopwatch ? stopwatch.total : 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const stopTimer = (timer) => ({
|
export const stopStopwatch = (stopwatch) => ({
|
||||||
startedAt: null,
|
startedAt: null,
|
||||||
total: getFullSeconds(timer),
|
total: getFullSeconds(stopwatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getTimerParts = (timer) => {
|
export const getStopwatchParts = (stopwatch) => {
|
||||||
const fullSeconds = getFullSeconds(timer);
|
const fullSeconds = getFullSeconds(stopwatch);
|
||||||
|
|
||||||
const hours = Math.floor(fullSeconds / 3600);
|
const hours = Math.floor(fullSeconds / 3600);
|
||||||
const minutes = Math.floor((fullSeconds - hours * 3600) / 60);
|
const minutes = Math.floor((fullSeconds - hours * 3600) / 60);
|
||||||
@@ -40,8 +40,8 @@ export const getTimerParts = (timer) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatTimer = (timer) => {
|
export const formatStopwatch = (stopwatch) => {
|
||||||
const { hours, minutes, seconds } = getTimerParts(timer);
|
const { hours, minutes, seconds } = getStopwatchParts(stopwatch);
|
||||||
|
|
||||||
return [hours, ...[minutes, seconds].map((part) => (part < 10 ? `0${part}` : part))].join(':');
|
return [hours, ...[minutes, seconds].map((part) => (part < 10 ? `0${part}` : part))].join(':');
|
||||||
};
|
};
|
||||||
@@ -23,6 +23,14 @@ services:
|
|||||||
- TRUST_PROXY=0
|
- TRUST_PROXY=0
|
||||||
- DATABASE_URL=postgresql://postgres@postgres/planka
|
- DATABASE_URL=postgresql://postgres@postgres/planka
|
||||||
- SECRET_KEY=notsecretkey
|
- SECRET_KEY=notsecretkey
|
||||||
|
|
||||||
|
# related: https://github.com/knex/knex/issues/2354
|
||||||
|
# As knex does not pass query parameters from the connection string we
|
||||||
|
# have to use environment variables in order to pass the desired values, e.g.
|
||||||
|
# - PGSSLMODE=<value>
|
||||||
|
|
||||||
|
# Configure knex to accept SSL certificates
|
||||||
|
# - KNEX_REJECT_UNAUTHORIZED_SSL_CERTIFICATE=false
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "planka",
|
"name": "planka",
|
||||||
"version": "1.10.2",
|
"version": "1.12.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "planka",
|
"name": "planka",
|
||||||
"version": "1.10.2",
|
"version": "1.12.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "planka",
|
"name": "planka",
|
||||||
"version": "1.10.2",
|
"version": "1.12.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://plankanban.github.io/planka",
|
"homepage": "https://plankanban.github.io/planka",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ SECRET_KEY=notsecretkey
|
|||||||
# TRUST_PROXY=0
|
# TRUST_PROXY=0
|
||||||
# TOKEN_EXPIRES_IN=365 # In days
|
# TOKEN_EXPIRES_IN=365 # In days
|
||||||
|
|
||||||
|
# related: https://github.com/knex/knex/issues/2354
|
||||||
|
# As knex does not pass query parameters from the connection string we
|
||||||
|
# have to use environment variables in order to pass the desired values, e.g.
|
||||||
|
# PGSSLMODE=<value>
|
||||||
|
|
||||||
|
# Configure knex to accept SSL certificates
|
||||||
|
# KNEX_REJECT_UNAUTHORIZED_SSL_CERTIFICATE=false
|
||||||
|
|
||||||
## Do not edit this
|
## Do not edit this
|
||||||
|
|
||||||
TZ=UTC
|
TZ=UTC
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ module.exports = {
|
|||||||
accessToken,
|
accessToken,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
}).set({
|
}).set({
|
||||||
deletedAt: new Date().toUTCString(),
|
deletedAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const Errors = {
|
|||||||
|
|
||||||
const dueDateValidator = (value) => moment(value, moment.ISO_8601, true).isValid();
|
const dueDateValidator = (value) => moment(value, moment.ISO_8601, true).isValid();
|
||||||
|
|
||||||
const timerValidator = (value) => {
|
const stopwatchValidator = (value) => {
|
||||||
if (!_.isPlainObject(value) || _.size(value) !== 2) {
|
if (!_.isPlainObject(value) || _.size(value) !== 2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -57,9 +57,9 @@ module.exports = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
custom: dueDateValidator,
|
custom: dueDateValidator,
|
||||||
},
|
},
|
||||||
timer: {
|
stopwatch: {
|
||||||
type: 'json',
|
type: 'json',
|
||||||
custom: timerValidator,
|
custom: stopwatchValidator,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ module.exports = {
|
|||||||
throw Errors.NOT_ENOUGH_RIGHTS;
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const values = _.pick(inputs, ['position', 'name', 'description', 'dueDate', 'timer']);
|
const values = _.pick(inputs, ['position', 'name', 'description', 'dueDate', 'stopwatch']);
|
||||||
|
|
||||||
const card = await sails.helpers.cards.createOne
|
const card = await sails.helpers.cards.createOne
|
||||||
.with({
|
.with({
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const Errors = {
|
|||||||
|
|
||||||
const dueDateValidator = (value) => moment(value, moment.ISO_8601, true).isValid();
|
const dueDateValidator = (value) => moment(value, moment.ISO_8601, true).isValid();
|
||||||
|
|
||||||
const timerValidator = (value) => {
|
const stopwatchValidator = (value) => {
|
||||||
if (!_.isPlainObject(value) || _.size(value) !== 2) {
|
if (!_.isPlainObject(value) || _.size(value) !== 2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -80,9 +80,9 @@ module.exports = {
|
|||||||
custom: dueDateValidator,
|
custom: dueDateValidator,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
timer: {
|
stopwatch: {
|
||||||
type: 'json',
|
type: 'json',
|
||||||
custom: timerValidator,
|
custom: stopwatchValidator,
|
||||||
},
|
},
|
||||||
isSubscribed: {
|
isSubscribed: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
@@ -171,7 +171,7 @@ module.exports = {
|
|||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
'dueDate',
|
'dueDate',
|
||||||
'timer',
|
'stopwatch',
|
||||||
'isSubscribed',
|
'isSubscribed',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ module.exports = {
|
|||||||
id: inputs.record.id,
|
id: inputs.record.id,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
}).set({
|
}).set({
|
||||||
deletedAt: new Date().toUTCString(),
|
deletedAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ module.exports = {
|
|||||||
if (!_.isUndefined(values.password)) {
|
if (!_.isUndefined(values.password)) {
|
||||||
Object.assign(values, {
|
Object.assign(values, {
|
||||||
password: bcrypt.hashSync(values.password, 10),
|
password: bcrypt.hashSync(values.password, 10),
|
||||||
passwordChangedAt: new Date().toUTCString(),
|
passwordChangedAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Object.keys(values).length === 1) {
|
if (Object.keys(values).length === 1) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ module.exports = {
|
|||||||
type: 'ref',
|
type: 'ref',
|
||||||
columnName: 'due_date',
|
columnName: 'due_date',
|
||||||
},
|
},
|
||||||
timer: {
|
stopwatch: {
|
||||||
type: 'json',
|
type: 'json',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -80,13 +80,13 @@ module.exports.models = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
beforeCreate(valuesToSet, proceed) {
|
beforeCreate(valuesToSet, proceed) {
|
||||||
valuesToSet.createdAt = new Date().toUTCString(); // eslint-disable-line no-param-reassign
|
valuesToSet.createdAt = new Date().toISOString(); // eslint-disable-line no-param-reassign
|
||||||
|
|
||||||
proceed();
|
proceed();
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeUpdate(valuesToSet, proceed) {
|
beforeUpdate(valuesToSet, proceed) {
|
||||||
valuesToSet.updatedAt = new Date().toUTCString(); // eslint-disable-line no-param-reassign
|
valuesToSet.updatedAt = new Date().toISOString(); // eslint-disable-line no-param-reassign
|
||||||
|
|
||||||
proceed();
|
proceed();
|
||||||
},
|
},
|
||||||
|
|||||||
+14
-1
@@ -6,9 +6,22 @@ dotenv.config({
|
|||||||
path: path.resolve(__dirname, '../.env'),
|
path: path.resolve(__dirname, '../.env'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function buildSSLConfig() {
|
||||||
|
if (process.env.KNEX_REJECT_UNAUTHORIZED_SSL_CERTIFICATE === 'false') {
|
||||||
|
return {
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
client: 'pg',
|
client: 'pg',
|
||||||
connection: process.env.DATABASE_URL,
|
connection: {
|
||||||
|
connectionString: process.env.DATABASE_URL,
|
||||||
|
ssl: buildSSLConfig(),
|
||||||
|
},
|
||||||
migrations: {
|
migrations: {
|
||||||
tableName: 'migration',
|
tableName: 'migration',
|
||||||
directory: path.join(__dirname, 'migrations'),
|
directory: path.join(__dirname, 'migrations'),
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
module.exports.up = (knex) =>
|
||||||
|
knex.schema.table('card', (table) => {
|
||||||
|
table.renameColumn('timer', 'stopwatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.down = (knex) =>
|
||||||
|
knex.schema.table('card', (table) => {
|
||||||
|
table.renameColumn('stopwatch', 'timer');
|
||||||
|
});
|
||||||
@@ -1,16 +1,12 @@
|
|||||||
const bcrypt = require('bcrypt');
|
const bcrypt = require('bcrypt');
|
||||||
|
|
||||||
exports.seed = (knex) => {
|
exports.seed = (knex) =>
|
||||||
const date = new Date().toUTCString();
|
knex('user_account').insert({
|
||||||
|
|
||||||
return knex('user_account').insert({
|
|
||||||
email: 'demo@demo.demo',
|
email: 'demo@demo.demo',
|
||||||
password: bcrypt.hashSync('demo', 10),
|
password: bcrypt.hashSync('demo', 10),
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
name: 'Demo Demo',
|
name: 'Demo Demo',
|
||||||
username: 'demo',
|
username: 'demo',
|
||||||
subscribeToOwnCards: false,
|
subscribeToOwnCards: false,
|
||||||
createdAt: date,
|
createdAt: new Date().toISOString(),
|
||||||
updatedAt: date,
|
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|||||||
Reference in New Issue
Block a user