Compare commits

..
74 Commits
Author SHA1 Message Date
Maksim Eltyshev a0f584e814 chore: Update version 2024-09-16 21:21:30 +02:00
Maksim Eltyshev 135f5eccb5 chore: Bump express and related dependencies 2024-09-16 13:26:29 +02:00
Maksim Eltyshev 10001db8cf chore: Update dependencies 2024-09-16 12:04:56 +02:00
Maksim Eltyshev 7ee351fe30 fix: Preserve extension for attachments with long filename
Closes #77
2024-09-16 00:45:53 +02:00
Maksim Eltyshev 73746b83d1 chore: Bump custom-ui css 2024-09-16 00:18:08 +02:00
Aline Fauquette f9a7b38ce9 feat: Change update strategy in case of persistence in Helm (#876) 2024-09-13 22:54:46 +02:00
dependabot[bot] 1f261fe7b6 chore(deps): Bump micromatch from 4.0.7 to 4.0.8 in /client (#879)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.7 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 22:43:00 +02:00
dependabot[bot] 80bee30463 chore(deps): Bump serve-static and express in /client (#878)
Bumps [serve-static](https://github.com/expressjs/serve-static) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `serve-static` from 1.15.0 to 1.16.2
- [Release notes](https://github.com/expressjs/serve-static/releases)
- [Changelog](https://github.com/expressjs/serve-static/blob/v1.16.2/HISTORY.md)
- [Commits](https://github.com/expressjs/serve-static/compare/v1.15.0...v1.16.2)

Updates `express` from 4.19.2 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

---
updated-dependencies:
- dependency-name: serve-static
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 22:42:49 +02:00
dependabot[bot] a9957481b6 chore(deps): Bump send and express in /client (#877)
Bumps [send](https://github.com/pillarjs/send) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `send` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0)

Updates `express` from 4.19.2 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 22:42:39 +02:00
dependabot[bot] 36b05a5478 chore(deps): Bump webpack from 5.91.0 to 5.94.0 in /client (#864)
Bumps [webpack](https://github.com/webpack/webpack) from 5.91.0 to 5.94.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.91.0...v5.94.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 22:42:08 +02:00
dependabot[bot] af91512abf chore(deps): Bump micromatch from 4.0.7 to 4.0.8 (#874)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.7 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 22:39:40 +02:00
Maël Gangloff 8fd0f682d9 feat: Google Chat notifications (#867) 2024-09-04 15:33:43 +02:00
Maksim Eltyshev 9699fbe76a feat: Additional httpOnly token for enhanced security in browsers 2024-09-01 09:31:04 +02:00
Aurélien Troncy 4176a62f1a fix: Update French translation (#862) 2024-08-30 13:27:39 +02:00
Tyler Sweat c12f0844d7 fix: Update Spanish translation (#861) 2024-08-30 11:50:45 +02:00
Aurélien Troncy e6644eb745 feat: Ability to show detailed auth errors, set to false by default (#860) 2024-08-30 11:47:29 +02:00
Blyamur b2e1fba9a0 fix: Update Russian translation (#857) 2024-08-27 16:09:31 +02:00
Maksim Eltyshev 818ae4a6df chore: Update version 2024-08-14 21:39:43 +02:00
Aurélien Troncy 0de0e809ac fix: Update French translation (#849) 2024-08-14 21:37:02 +02:00
Maksim Eltyshev da727c0a0b chore: Generate separate file to store version
Closes #848
2024-08-13 22:26:44 +02:00
Ken 4841a65c77 feat: Add Traditional Chinese translation (#846) 2024-08-13 12:53:42 +02:00
Maksim Eltyshev 0ec4b619d9 chore: Update version 2024-08-12 23:23:50 +02:00
Maksim Eltyshev 12f05adde7 fix: Fix styles, refactoring 2024-08-12 23:17:17 +02:00
Arkadiusz Dzięgiel c594e8bd71 feat: Colorize due date and make it toggleable (#845) 2024-08-12 18:29:50 +02:00
Maksim Eltyshev f84166406f ref: Refactoring 2024-07-26 20:51:59 +02:00
Fayad 82b2ba298d feat: Improvements for mobile version (#776) 2024-07-26 19:02:13 +02:00
jaycoolslm 38b6805ba1 fix: Update healthcheck parameters in docker-compose-dev.yml (#834) 2024-07-24 13:10:56 +02:00
dependabot[bot] 6a7a582045 chore(deps): Bump socket.io-parser from 3.3.3 to 3.3.4 in /client (#833)
Bumps [socket.io-parser](https://github.com/Automattic/socket.io-parser) from 3.3.3 to 3.3.4.
- [Release notes](https://github.com/Automattic/socket.io-parser/releases)
- [Changelog](https://github.com/socketio/socket.io-parser/blob/3.3.4/CHANGELOG.md)
- [Commits](https://github.com/Automattic/socket.io-parser/compare/3.3.3...3.3.4)

---
updated-dependencies:
- dependency-name: socket.io-parser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-23 12:50:41 +02:00
Blyamur c53a00b4ba fix: Update Russian translation (#832) 2024-07-22 16:32:12 +02:00
Blyamur 52103c775b fix: Update Russian translation (#831) 2024-07-22 16:30:44 +02:00
Maksim Eltyshev eeb1bd80cc chore: Update version 2024-07-21 19:44:49 +02:00
dependabot[bot] e6b8538863 chore(deps): Bump ws, engine.io and socket.io-adapter in /server (#830)
Bumps [ws](https://github.com/websockets/ws), [engine.io](https://github.com/socketio/engine.io) and [socket.io-adapter](https://github.com/socketio/socket.io-adapter). These dependencies needed to be updated together.

Updates `ws` from 8.11.0 to 8.17.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.11.0...8.17.1)

Updates `engine.io` from 6.5.4 to 6.5.5
- [Release notes](https://github.com/socketio/engine.io/releases)
- [Changelog](https://github.com/socketio/engine.io/blob/6.5.5/CHANGELOG.md)
- [Commits](https://github.com/socketio/engine.io/compare/6.5.4...6.5.5)

Updates `socket.io-adapter` from 2.5.4 to 2.5.5
- [Release notes](https://github.com/socketio/socket.io-adapter/releases)
- [Changelog](https://github.com/socketio/socket.io-adapter/blob/2.5.5/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io-adapter/compare/2.5.4...2.5.5)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
- dependency-name: engine.io
  dependency-type: indirect
- dependency-name: socket.io-adapter
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-21 19:42:36 +02:00
Maksim Eltyshev 9dc38932fb feat: Languages with country codes 2024-07-21 19:33:57 +02:00
Aurélien Troncy e9b4a4adfc fix: Disable x-powered-by header (#829) 2024-07-21 18:27:26 +02:00
Mazyar Yousefiniyae shad 76c78c7252 feat: Add Persian translation (#828) 2024-07-21 18:21:18 +02:00
Maksim Eltyshev 04f52a63f2 chore: Update version 2024-07-17 14:03:00 +02:00
Maksim Eltyshev 6b16a104b1 docs: Update docker pulls counter 2024-07-16 12:39:57 +02:00
Maksim Eltyshev aff853c602 ref: Refactoring, fix linting 2024-07-16 12:33:38 +02:00
aleb_the_flash ad2966c5d6 feat: Improve OIDC support for strict providers (#824) 2024-07-16 12:19:27 +02:00
Maksim Eltyshev 8d74cc1732 chore: Update version 2024-07-15 16:24:31 +02:00
Emmanuel Guyot ea94fe8bf3 fix: Order users by name (#806) 2024-07-15 15:26:26 +02:00
Αλέξανδρος 6052f8999f fix: Fix application crashing when webhook is unavailable (#815) 2024-07-15 14:40:33 +02:00
ItzAndriss 67d1b1c194 fix: Update Hungarian translation (#823) 2024-07-15 14:25:31 +02:00
Stephan Michard b3cb8405f5 fix: Update German translation (#821) 2024-07-15 14:19:06 +02:00
Maksim Eltyshev 7acaec6d04 fix: Fix incorrect related data when transferring card
Closes #431, closes #803
2024-07-15 14:01:06 +02:00
Maksim Eltyshev 72896a66b3 chore: Update version 2024-06-21 12:11:15 +02:00
Maksim Eltyshev 4be4783251 fix: Fix icon spacing for action buttons 2024-06-21 12:01:46 +02:00
HannesOberreiter 5c6c6a0bcf feat: Add copy link action to card modal (#804) 2024-06-21 11:48:36 +02:00
HannesOberreiter ba178d66fa docs: Add types to webhook function (#796) 2024-06-18 15:41:12 +02:00
HannesOberreiter 270ce29f87 feat: Add custom user agent with base url to webhook (#799) 2024-06-18 15:36:51 +02:00
HannesOberreiter de1ed3c784 ref: Define all possible event types in webhook module (#795) 2024-06-18 15:32:39 +02:00
Maksim Eltyshev caf8790f28 chore: Update version 2024-06-14 17:18:42 +02:00
Sarah Soo 81436b373d feat: Ability to specify existing secrets for values in Helm (#791)
Closes #790
2024-06-14 16:41:10 +02:00
NathanVss cc1e886a31 feat: Ability to allow everyone to create projects (#787) 2024-06-14 16:38:06 +02:00
Maksim Eltyshev f0e9fe7904 chore: Update version 2024-06-12 09:06:20 +02:00
Gavin Mogan 5fcea5d651 fix: Handle WEBHOOKS env variable being unset (#785)
Closes #784
2024-06-12 08:59:36 +02:00
Maksim Eltyshev a963d85a66 chore: Update version 2024-06-12 01:02:55 +02:00
Samuel 04b42decef meta: Add issue templates (#782) 2024-06-12 00:59:37 +02:00
Maksim Eltyshev c065566c15 feat: Webhooks configuration, all events support, refactoring 2024-06-12 00:51:36 +02:00
HannesOberreiter 3779bdb053 feat: Events via webhook (#771)
Closes #215, closes #656
2024-06-06 20:22:14 +02:00
HannesOberreiter 4124ab17d2 fix: Focus end of field when editing (#779) 2024-06-06 20:06:16 +02:00
Maksim Eltyshev 0e3bc92a61 chore: Update version 2024-06-02 01:36:29 +02:00
Maksim Eltyshev b8d7e713b9 chore: Update dependencies 2024-06-02 01:34:03 +02:00
Emmanuel Guyot 017eee8302 fix: Use server base path for socket connection (#772)
Closes #768
2024-05-23 23:59:44 +02:00
Mitch Harvey 1b28892cf5 fix: Fix Helm template for dburl secret (#770) 2024-05-21 23:32:07 +02:00
Maksim Eltyshev 013ea1b869 chore: Update version 2024-05-21 13:58:28 +02:00
Blyamur 4975fa2eeb fix: Update Russian translation (#767) 2024-05-21 13:51:57 +02:00
Blyamur 3e28ad87c7 fix: Update Russian translation (#766) 2024-05-21 13:51:34 +02:00
Smiley3112 2d6666d693 feat: Add SMTP_NAME environment variable (#761)
Closes #758
2024-05-18 15:02:21 +02:00
Maksim Eltyshev 4cbd2f85c7 chore: Update version 2024-05-16 21:06:30 +02:00
Maksim Eltyshev fae9de4e1c fix: Prevent loading fonts from third-party resources
Closes #538, closes #712, closes #742, closes #755
2024-05-16 21:02:58 +02:00
IT Creativity + Art Team 89c1ed71e1 feat: Add Bulgarian translation (#753) 2024-05-13 13:05:20 +02:00
Mitch Harvey 1725f3cb8a feat: Ability to specify existing secret as database url (#751)
Closes #750
2024-05-13 11:07:57 +02:00
Christopher Greaves f7be49e402 docs: Update Kubernetes README (#740) 2024-05-03 21:04:34 +02:00
311 changed files with 20876 additions and 18640 deletions
+53
View File
@@ -0,0 +1,53 @@
name: "🐛 Bug Report"
description: Report a bug found while using Planka
title: "[Bug]: "
labels: ["Type: Bug", "Status: Triage"]
body:
- type: dropdown
id: issue-type
attributes:
label: Where is the problem occurring?
description: Select the part of the application where you encountered the issue.
options:
- "I encountered the problem while using the application (Frontend)"
- "I encountered the problem while interacting with the server (Backend)"
- "I'm not sure"
- type: dropdown
id: browsers
attributes:
label: What browsers are you seeing the problem on?
multiple: true
options:
- Brave
- Chrome
- Firefox
- Microsoft Edge
- Safari
- Other
- type: textarea
id: current-behavior
attributes:
label: Current behaviour
description: A description of what is currently happening, including screenshots and other useful information (**DO NOT INCLUDE PRIVATE INFORMATION**).
placeholder: Currently...
validations:
required: true
- type: textarea
id: desired-behavior
attributes:
label: Desired behaviour
description: A clear description of what you think should happen.
placeholder: In this situation, I expected ...
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: Clearly describe which steps or actions you have taken to arrive at the problem. If you have some experience with the code, please link to the specific pieces of code.
placeholder: I did X, then Y, before arriving at Z, when ERROR ...
validations:
required: true
- type: textarea
id: other
attributes:
label: Other information
description: Any other details?
@@ -0,0 +1,33 @@
name: "✨ Feature Request"
description: Suggest a feature or enhancement to improve Planka.
labels: ["Type: Idea"]
body:
- type: dropdown
id: idea-type
attributes:
label: Is this a feature for the backend or frontend?
multiple: true
options:
- Backend
- Frontend
validations:
required: true
- type: textarea
id: feature
attributes:
label: What would you like?
description: A clear description of the feature or enhancement wanted.
placeholder: I'd like to be able to...
validations:
required: true
- type: textarea
id: reason
attributes:
label: Why is this needed?
description: A clear description of why this would be useful to have.
placeholder: I want this because...
- type: textarea
id: other
attributes:
label: Other information
placeholder: Any other details?
Executable → Regular
-2
View File
@@ -1,4 +1,2 @@
#!/bin/sh #!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged npx lint-staged
+1 -1
View File
@@ -1,7 +1,7 @@
# Planka # Planka
#### Elegant open source project tracking. #### Elegant open source project tracking.
![David (path)](https://img.shields.io/github/package-json/v/plankanban/planka) ![Docker Pulls](https://img.shields.io/docker/pulls/meltyshev/planka) ![GitHub](https://img.shields.io/github/license/plankanban/planka) ![David (path)](https://img.shields.io/github/package-json/v/plankanban/planka) ![Docker Pulls](https://img.shields.io/badge/docker_pulls-4M%2B-%23066da5) ![GitHub](https://img.shields.io/github/license/plankanban/planka)
![](https://raw.githubusercontent.com/plankanban/planka/master/demo.gif) ![](https://raw.githubusercontent.com/plankanban/planka/master/demo.gif)
+2 -2
View File
@@ -15,13 +15,13 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # 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. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.29 version: 0.2.8
# This is the version number of the application being deployed. This version number should be # 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 # 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. # follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes. # It is recommended to use it with quotes.
appVersion: "1.17.2" appVersion: "1.22.0"
dependencies: dependencies:
- alias: postgresql - alias: postgresql
+6 -2
View File
@@ -14,11 +14,15 @@ If you want to fully uninstall this chart including the data, follow [these step
## Usage ## Usage
If you just want to spin up an instance using help, please see [these docs](https://docs.planka.cloud/docs/installation/kubernetes/helm_chart/). If you want to make changes to the chart locally, and deploy them, see the below section.
## Local Building and Using the Chart
The basic usage of the chart can be found below: The basic usage of the chart can be found below:
```bash ```bash
git clone https://github.com/Chris-Greaves/planka-helm-chart.git git clone https://github.com/plankanban/planka.git
cd planka-helm-chart cd planka/charts/planka
helm dependency build helm dependency build
export SECRETKEY=$(openssl rand -hex 64) export SECRETKEY=$(openssl rand -hex 64)
helm install planka . --set secretkey=$SECRETKEY \ helm install planka . --set secretkey=$SECRETKEY \
+35 -3
View File
@@ -11,6 +11,10 @@ spec:
selector: selector:
matchLabels: matchLabels:
{{- include "planka.selectorLabels" . | nindent 6 }} {{- include "planka.selectorLabels" . | nindent 6 }}
{{- if .Values.persistence.enabled }}
strategy:
type: Recreate
{{- end }}
template: template:
metadata: metadata:
{{- with .Values.podAnnotations }} {{- with .Values.podAnnotations }}
@@ -64,8 +68,16 @@ spec:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.resources | nindent 12 }}
env: env:
{{- if not .Values.postgresql.enabled }} {{- if not .Values.postgresql.enabled }}
{{- if .Values.existingDburlSecret }}
- name: DATABASE_URL - name: DATABASE_URL
value: {{ required "If the included postgresql deployment is disabled you need to define a Database URL in 'dburl'" .Values.dburl }} valueFrom:
secretKeyRef:
name: {{ .Values.existingDburlSecret }}
key: uri
{{- else }}
- name: DATABASE_URL
value: {{ required "If the included postgresql deployment is disabled you need to provide an existing secret in .Values.existingDburlSecret or define a Database URL in 'dburl'" .Values.dburl }}
{{- end }}
{{- else }} {{- else }}
- name: DATABASE_URL - name: DATABASE_URL
valueFrom: valueFrom:
@@ -82,17 +94,37 @@ spec:
value: http://localhost:3000 value: http://localhost:3000
{{- end }} {{- end }}
- name: SECRET_KEY - name: SECRET_KEY
{{- if .Values.existingSecretkeySecret }}
valueFrom:
secretKeyRef:
name: {{ .Values.existingSecretkeySecret }}
key: key
{{- else }}
value: {{ required "A secret key needs to be generated using 'openssl rand -hex 64' and assigned to secretkey." .Values.secretkey }} value: {{ required "A secret key needs to be generated using 'openssl rand -hex 64' and assigned to secretkey." .Values.secretkey }}
{{- end }}
- name: TRUST_PROXY - name: TRUST_PROXY
value: "0" value: "0"
- name: DEFAULT_ADMIN_EMAIL - name: DEFAULT_ADMIN_EMAIL
value: {{ .Values.admin_email }} value: {{ .Values.admin_email }}
- name: DEFAULT_ADMIN_PASSWORD
value: {{ .Values.admin_password }}
- name: DEFAULT_ADMIN_NAME - name: DEFAULT_ADMIN_NAME
value: {{ .Values.admin_name }} value: {{ .Values.admin_name }}
{{- if .Values.existingAdminCredsSecret }}
- name: DEFAULT_ADMIN_USERNAME
valueFrom:
secretKeyRef:
name: {{ .Values.existingAdminCredsSecret }}
key: username
- name: DEFAULT_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.existingAdminCredsSecret }}
key: password
{{- else }}
- name: DEFAULT_ADMIN_USERNAME - name: DEFAULT_ADMIN_USERNAME
value: {{ .Values.admin_username }} value: {{ .Values.admin_username }}
- name: DEFAULT_ADMIN_PASSWORD
value: {{ .Values.admin_password }}
{{- end }}
{{ range $k, $v := .Values.env }} {{ range $k, $v := .Values.env }}
- name: {{ $k | quote }} - name: {{ $k | quote }}
value: {{ $v | quote }} value: {{ $v | quote }}
+17 -1
View File
@@ -17,6 +17,16 @@ fullnameOverride: ""
# Generate a secret using openssl rand -base64 45 # Generate a secret using openssl rand -base64 45
secretkey: "" secretkey: ""
## @param existingSecretkeySecret Name of an existing secret containing the session key string
## NOTE: Must contain key `key`
## NOTE: When it's set, the secretkey parameter is ignored
existingSecretkeySecret: ""
## @param existingAdminCredsSecret Name of an existing secret containing the admin username and password
## NOTE: Must contain keys `username` and `password`
## NOTE: When it's set, the `admin_username` and `admin_password` parameters are ignored
existingAdminCredsSecret: ""
# Base url for Planka. Will override `ingress.hosts[0].host` # Base url for Planka. Will override `ingress.hosts[0].host`
# Defaults to `http://localhost:3000` if ingress is disabled. # Defaults to `http://localhost:3000` if ingress is disabled.
baseUrl: "" baseUrl: ""
@@ -105,9 +115,15 @@ postgresql:
serviceBindings: serviceBindings:
enabled: true enabled: true
## Set this if you disable the built-in postgresql deployment ## Set this or existingDburlSecret if you disable the built-in postgresql deployment
dburl: dburl:
## @param existingDburlSecret Name of an existing secret containing a DBurl connection string
## NOTE: Must contain key `uri`
## NOTE: When it's set, the `dburl` parameter is ignored
##
existingDburlSecret: ""
## PVC-based data storage configuration ## PVC-based data storage configuration
persistence: persistence:
enabled: false enabled: false
-1
View File
@@ -1 +0,0 @@
REACT_APP_VERSION=1.17.2
+2216 -7883
View File
File diff suppressed because it is too large Load Diff
+17 -16
View File
@@ -65,8 +65,8 @@
"dequal": "^2.0.3", "dequal": "^2.0.3",
"easymde": "^2.18.0", "easymde": "^2.18.0",
"history": "^5.3.0", "history": "^5.3.0",
"i18next": "^23.11.2", "i18next": "^23.15.1",
"i18next-browser-languagedetector": "^7.2.1", "i18next-browser-languagedetector": "^8.0.0",
"initials": "^3.1.2", "initials": "^3.1.2",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
@@ -75,20 +75,20 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
"node-sass": "^9.0.0", "node-sass": "^9.0.0",
"photoswipe": "^5.4.3", "photoswipe": "^5.4.4",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^18.2.0", "react": "18.2.0",
"react-app-rewired": "^2.2.1", "react-app-rewired": "^2.2.1",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
"react-datepicker": "^4.25.0", "react-datepicker": "^4.25.0",
"react-dom": "^18.2.0", "react-dom": "18.2.0",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-i18next": "^13.5.0", "react-i18next": "^15.0.2",
"react-input-mask": "^2.0.4", "react-input-mask": "^2.0.4",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
"react-photoswipe-gallery": "^2.2.7", "react-photoswipe-gallery": "^2.2.7",
"react-redux": "^8.1.3", "react-redux": "^8.1.3",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.26.2",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-simplemde-editor": "^5.2.0", "react-simplemde-editor": "^5.2.0",
"react-textarea-autosize": "^8.5.3", "react-textarea-autosize": "^8.5.3",
@@ -102,22 +102,23 @@
"sails.io.js": "^1.2.1", "sails.io.js": "^1.2.1",
"semantic-ui-react": "^2.1.5", "semantic-ui-react": "^2.1.5",
"socket.io-client": "^2.5.0", "socket.io-client": "^2.5.0",
"validator": "^13.11.0", "validator": "^13.12.0",
"whatwg-fetch": "^3.6.20", "whatwg-fetch": "^3.6.20",
"zxcvbn": "^4.4.2" "zxcvbn": "^4.4.2"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^6.4.2", "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@testing-library/react": "^14.3.1", "@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^15.0.7",
"@testing-library/user-event": "^14.5.2", "@testing-library/user-event": "^14.5.2",
"babel-preset-airbnb": "^5.0.0", "babel-preset-airbnb": "^5.0.0",
"chai": "^4.4.1", "chai": "^4.5.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.30.0",
"eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-jsx-a11y": "^6.10.0",
"eslint-plugin-react": "^7.34.1", "eslint-plugin-react": "^7.36.1",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.2",
"react-test-renderer": "^18.2.0" "react-test-renderer": "18.2.0"
} }
} }
+1 -1
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta
name="description" name="description"
+6 -1
View File
@@ -57,10 +57,15 @@ updateCard.failure = (id, error) => ({
}, },
}); });
const handleCardUpdate = (card) => ({ const handleCardUpdate = (card, isFetched, cardMemberships, cardLabels, tasks, attachments) => ({
type: ActionTypes.CARD_UPDATE_HANDLE, type: ActionTypes.CARD_UPDATE_HANDLE,
payload: { payload: {
card, card,
isFetched,
cardMemberships,
cardLabels,
tasks,
attachments,
}, },
}); });
+4 -5
View File
@@ -1,15 +1,14 @@
import http from './http'; import http from './http';
import socket from './socket';
/* Actions */ /* Actions */
const createAccessToken = (data, headers) => http.post('/access-tokens', data, headers); const createAccessToken = (data, headers) =>
http.post('/access-tokens?withHttpOnlyToken=true', data, headers);
const exchangeForAccessTokenUsingOidc = (data, headers) => const exchangeForAccessTokenUsingOidc = (data, headers) =>
http.post('/access-tokens/exchange-using-oidc', data, headers); http.post('/access-tokens/exchange-using-oidc?withHttpOnlyToken=true', data, headers);
const deleteCurrentAccessToken = (headers) => const deleteCurrentAccessToken = (headers) => http.delete('/access-tokens/me', undefined, headers);
socket.delete('/access-tokens/me', undefined, headers);
export default { export default {
createAccessToken, createAccessToken,
+2 -1
View File
@@ -5,7 +5,7 @@ import Config from '../constants/Config';
const http = {}; const http = {};
// TODO: add all methods // TODO: add all methods
['GET', 'POST'].forEach((method) => { ['GET', 'POST', 'DELETE'].forEach((method) => {
http[method.toLowerCase()] = (url, data, headers) => { http[method.toLowerCase()] = (url, data, headers) => {
const formData = const formData =
data && data &&
@@ -19,6 +19,7 @@ const http = {};
method, method,
headers, headers,
body: formData, body: formData,
credentials: 'include',
}) })
.then((response) => .then((response) =>
response.json().then((body) => ({ response.json().then((body) => ({
+1 -1
View File
@@ -13,7 +13,7 @@ io.sails.environment = process.env.NODE_ENV;
const { socket } = io; const { socket } = io;
socket.path = `${Config.BASE_PATH}/socket.io`; socket.path = `${Config.SERVER_BASE_PATH}/socket.io`;
socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle
['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => { ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => {
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,8 @@
:global(#app) { :global(#app) {
.wrapper { .wrapper {
height: 100%; height: 100%;
max-height: 100vh;
max-width: 100vw;
position: fixed; position: fixed;
width: 100%; width: 100%;
z-index: -1; z-index: -1;
@@ -48,16 +48,6 @@
min-width: 100%; min-width: 100%;
} }
.panel {
align-items: center;
display: flex;
margin-bottom: 20px;
}
.panelItem {
margin-right: 20px;
}
.wrapper { .wrapper {
margin: 0 20px; margin: 0 20px;
} }
@@ -3,12 +3,13 @@
align-items: center; align-items: center;
display: flex; display: flex;
flex: 0 0 auto; flex: 0 0 auto;
margin-right: 20px;
} }
.actions { .actions {
align-items: center; align-items: center;
display: flex; display: flex;
gap: 20px;
justify-content: flex-start;
margin: 20px 20px; margin: 20px 20px;
} }
@@ -50,6 +50,10 @@
transition: width 0.2s ease; transition: width 0.2s ease;
width: 280px; width: 280px;
@media only screen and (max-width: 797px) {
width: 220px;
}
input { input {
font-size: 13px; font-size: 13px;
} }
+4 -1
View File
@@ -24,6 +24,7 @@ const Card = React.memo(
index, index,
name, name,
dueDate, dueDate,
isDueDateCompleted,
stopwatch, stopwatch,
coverUrl, coverUrl,
boardId, boardId,
@@ -120,7 +121,7 @@ const Card = React.memo(
)} )}
{dueDate && ( {dueDate && (
<span className={classNames(styles.attachment, styles.attachmentLeft)}> <span className={classNames(styles.attachment, styles.attachmentLeft)}>
<DueDate value={dueDate} size="tiny" /> <DueDate value={dueDate} isCompleted={isDueDateCompleted} size="tiny" />
</span> </span>
)} )}
{stopwatch && ( {stopwatch && (
@@ -221,6 +222,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),
isDueDateCompleted: PropTypes.bool,
stopwatch: 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,
@@ -255,6 +257,7 @@ Card.propTypes = {
Card.defaultProps = { Card.defaultProps = {
dueDate: undefined, dueDate: undefined,
isDueDateCompleted: undefined,
stopwatch: undefined, stopwatch: undefined,
coverUrl: undefined, coverUrl: undefined,
}; };
@@ -21,6 +21,14 @@
background: #ebeef0; background: #ebeef0;
color: #516b7a; color: #516b7a;
} }
@media only screen and (max-width: 797px) {
&:focus,
&:active {
background: #ebeef0;
color: #516b7a;
}
}
} }
.attachment { .attachment {
@@ -55,6 +63,12 @@
box-shadow: 0 1px 0 #ccc; box-shadow: 0 1px 0 #ccc;
position: relative; position: relative;
@media only screen and (max-width: 797px) {
.target {
opacity: 1;
}
}
&:hover { &:hover {
background: #f5f6f7; background: #f5f6f7;
border-bottom-color: rgba(9, 30, 66, 0.25); border-bottom-color: rgba(9, 30, 66, 0.25);
+2 -1
View File
@@ -5,6 +5,7 @@ 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 { useClosableForm, useField } from '../../hooks';
import { focusEnd } from '../../utils/element-helpers';
import styles from './NameEdit.module.scss'; import styles from './NameEdit.module.scss';
@@ -79,7 +80,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
useEffect(() => { useEffect(() => {
if (isOpened) { if (isOpened) {
field.current.ref.current.focus(); focusEnd(field.current.ref.current);
} }
}, [isOpened]); }, [isOpened]);
@@ -6,6 +6,7 @@ import TextareaAutosize from 'react-textarea-autosize';
import { Button, Form, TextArea } from 'semantic-ui-react'; import { Button, Form, TextArea } from 'semantic-ui-react';
import { useForm } from '../../../hooks'; import { useForm } from '../../../hooks';
import { focusEnd } from '../../../utils/element-helpers';
import styles from './CommentEdit.module.scss'; import styles from './CommentEdit.module.scss';
@@ -70,7 +71,7 @@ const CommentEdit = React.forwardRef(({ children, defaultData, onUpdate }, ref)
useEffect(() => { useEffect(() => {
if (isOpened) { if (isOpened) {
textField.current.ref.current.focus(); focusEnd(textField.current.ref.current);
} }
}, [isOpened]); }, [isOpened]);
@@ -2,7 +2,7 @@
.dropzone { .dropzone {
background: white; background: white;
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: bold;
height: 100%; height: 100%;
line-height: 30px; line-height: 30px;
opacity: 0.7; opacity: 0.7;
@@ -16,7 +16,7 @@
.contentError { .contentError {
color: #fff; color: #fff;
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: bold;
height: 20px; height: 20px;
width: 470px; width: 470px;
} }
@@ -35,7 +35,7 @@
color: #5e6c84; color: #5e6c84;
display: block; display: block;
font-size: 18px; font-size: 18px;
font-weight: 700; font-weight: bold;
height: 100%; height: 100%;
line-height: 80px; line-height: 80px;
overflow: hidden; overflow: hidden;
@@ -50,7 +50,7 @@
.name { .name {
color: #17394d; color: #17394d;
font-size: 14px; font-size: 14px;
font-weight: 700; font-weight: bold;
line-height: 20px; line-height: 20px;
word-wrap: break-word; word-wrap: break-word;
} }
+49 -7
View File
@@ -1,8 +1,8 @@
import React, { useCallback, useRef } from 'react'; import React, { useCallback, useRef, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, Grid, Icon, Modal } from 'semantic-ui-react'; import { Button, Checkbox, 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';
@@ -32,6 +32,7 @@ const CardModal = React.memo(
name, name,
description, description,
dueDate, dueDate,
isDueDateCompleted,
stopwatch, stopwatch,
isSubscribed, isSubscribed,
isActivitiesFetching, isActivitiesFetching,
@@ -81,6 +82,7 @@ const CardModal = React.memo(
onClose, onClose,
}) => { }) => {
const [t] = useTranslation(); const [t] = useTranslation();
const [isLinkCopied, setIsLinkCopied] = useState(false);
const isGalleryOpened = useRef(false); const isGalleryOpened = useRef(false);
@@ -117,6 +119,12 @@ const CardModal = React.memo(
[onUpdate], [onUpdate],
); );
const handleDueDateCompletionChange = useCallback(() => {
onUpdate({
isDueDateCompleted: !isDueDateCompleted,
});
}, [isDueDateCompleted, onUpdate]);
const handleStopwatchUpdate = useCallback( const handleStopwatchUpdate = useCallback(
(newStopwatch) => { (newStopwatch) => {
onUpdate({ onUpdate({
@@ -146,6 +154,14 @@ const CardModal = React.memo(
onClose(); onClose();
}, [onDuplicate, onClose]); }, [onDuplicate, onClose]);
const handleCopyLinkClick = useCallback(() => {
navigator.clipboard.writeText(window.location.href);
setIsLinkCopied(true);
setTimeout(() => {
setIsLinkCopied(false);
}, 5000);
}, []);
const handleGalleryOpen = useCallback(() => { const handleGalleryOpen = useCallback(() => {
isGalleryOpened.current = true; isGalleryOpened.current = true;
}, []); }, []);
@@ -291,13 +307,24 @@ const CardModal = React.memo(
context: 'title', context: 'title',
})} })}
</div> </div>
<span className={styles.attachment}> <span className={classNames(styles.attachment, styles.attachmentDueDate)}>
{canEdit ? ( {canEdit ? (
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}> <>
<DueDate value={dueDate} /> <Checkbox
</DueDateEditPopup> checked={isDueDateCompleted}
disabled={!canEdit}
onChange={handleDueDateCompletionChange}
/>
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}>
<DueDate
withStatusIcon
value={dueDate}
isCompleted={isDueDateCompleted}
/>
</DueDateEditPopup>
</>
) : ( ) : (
<DueDate value={dueDate} /> <DueDate withStatusIcon value={dueDate} isCompleted={isDueDateCompleted} />
)} )}
</span> </span>
</div> </div>
@@ -506,6 +533,19 @@ const CardModal = React.memo(
<Icon name="copy outline" className={styles.actionIcon} /> <Icon name="copy outline" className={styles.actionIcon} />
{t('action.duplicate')} {t('action.duplicate')}
</Button> </Button>
{window.isSecureContext && (
<Button fluid className={styles.actionButton} onClick={handleCopyLinkClick}>
<Icon
name={isLinkCopied ? 'linkify' : 'unlink'}
className={styles.actionIcon}
/>
{isLinkCopied
? t('common.linkIsCopied')
: t('action.copyLink', {
context: 'title',
})}
</Button>
)}
<DeletePopup <DeletePopup
title="common.deleteCard" title="common.deleteCard"
content="common.areYouSureYouWantToDeleteThisCard" content="common.areYouSureYouWantToDeleteThisCard"
@@ -540,6 +580,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),
isDueDateCompleted: PropTypes.bool,
stopwatch: 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,
@@ -594,6 +635,7 @@ CardModal.propTypes = {
CardModal.defaultProps = { CardModal.defaultProps = {
description: undefined, description: undefined,
dueDate: undefined, dueDate: undefined,
isDueDateCompleted: false,
stopwatch: undefined, stopwatch: undefined,
}; };
@@ -20,18 +20,26 @@
.actionIcon { .actionIcon {
color: #17394d; color: #17394d;
display: inline;
margin-right: 8px; margin-right: 8px;
} }
.actions { .actions {
margin-bottom: 24px; margin-bottom: 24px;
@media only screen and (max-width: 797px) {
flex: 1;
}
@media only screen and (max-width: 425px) {
padding: 0;
width: 100%;
}
} }
.actionsTitle { .actionsTitle {
color: #8c8c8c; color: #8c8c8c;
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: normal;
letter-spacing: 0.04em; letter-spacing: 0.04em;
margin-top: 16px; margin-top: 16px;
text-transform: uppercase; text-transform: uppercase;
@@ -50,6 +58,12 @@
max-width: 100%; max-width: 100%;
} }
.attachmentDueDate {
align-items: center;
display: flex;
gap: 4px;
}
.attachments { .attachments {
display: inline-block; display: inline-block;
margin: 0 8px 8px 0; margin: 0 8px 8px 0;
@@ -63,6 +77,11 @@
.contentPadding { .contentPadding {
padding: 8px 8px 0 16px; padding: 8px 8px 0 16px;
@media only screen and (max-width: 797px) {
padding-right: 16px;
width: 100% !important;
}
} }
.cursorPointer { .cursorPointer {
@@ -139,7 +158,7 @@
.headerTitle { .headerTitle {
color: #17394d; color: #17394d;
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: bold;
line-height: 24px; line-height: 24px;
} }
@@ -155,6 +174,11 @@
.modalPadding { .modalPadding {
padding: 0px; padding: 0px;
@media only screen and (max-width: 797px) {
display: flex;
flex-flow: column nowrap;
}
} }
.moduleHeader { .moduleHeader {
@@ -185,6 +209,15 @@
.sidebarPadding { .sidebarPadding {
padding: 8px 16px 0 8px; padding: 8px 16px 0 8px;
@media only screen and (max-width: 797px) {
align-items: flex-start;
display: flex;
flex-flow: row wrap;
gap: 10px;
padding-left: 16px;
width: 100% !important;
}
} }
.text { .text {
@@ -57,6 +57,7 @@ const DescriptionEdit = React.forwardRef(({ children, defaultValue, onUpdate },
const mdEditorOptions = useMemo( const mdEditorOptions = useMemo(
() => ({ () => ({
autoDownloadFontAwesome: false,
autofocus: true, autofocus: true,
spellChecker: false, spellChecker: false,
status: false, status: false,
@@ -6,7 +6,7 @@
box-shadow: none; box-shadow: none;
color: #17394d; color: #17394d;
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: bold;
line-height: 24px; line-height: 24px;
margin: -5px; margin: -5px;
overflow: hidden; overflow: hidden;
@@ -5,6 +5,7 @@ import TextareaAutosize from 'react-textarea-autosize';
import { Button, Form, TextArea } from 'semantic-ui-react'; import { Button, Form, TextArea } from 'semantic-ui-react';
import { useField } from '../../../hooks'; import { useField } from '../../../hooks';
import { focusEnd } from '../../../utils/element-helpers';
import styles from './NameEdit.module.scss'; import styles from './NameEdit.module.scss';
@@ -65,7 +66,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
useEffect(() => { useEffect(() => {
if (isOpened) { if (isOpened) {
field.current.ref.current.focus(); focusEnd(field.current.ref.current);
} }
}, [isOpened]); }, [isOpened]);
@@ -4,6 +4,7 @@
border-radius: 4px; border-radius: 4px;
bottom: 20px; bottom: 20px;
box-shadow: #b04632 0 1px 0; box-shadow: #b04632 0 1px 0;
max-width: calc(100% - 40px);
padding: 12px 18px; padding: 12px 18px;
position: fixed; position: fixed;
right: 20px; right: 20px;
+81 -2
View File
@@ -1,8 +1,10 @@
import upperFirst from 'lodash/upperFirst'; import upperFirst from 'lodash/upperFirst';
import React from 'react'; import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Icon } from 'semantic-ui-react';
import { useForceUpdate } from '../../lib/hooks';
import getDateFormat from '../../utils/get-date-format'; import getDateFormat from '../../utils/get-date-format';
@@ -14,6 +16,12 @@ const SIZES = {
MEDIUM: 'medium', MEDIUM: 'medium',
}; };
const STATUSES = {
DUE_SOON: 'dueSoon',
OVERDUE: 'overdue',
COMPLETED: 'completed',
};
const LONG_DATE_FORMAT_BY_SIZE = { const LONG_DATE_FORMAT_BY_SIZE = {
tiny: 'longDate', tiny: 'longDate',
small: 'longDate', small: 'longDate',
@@ -26,8 +34,47 @@ const FULL_DATE_FORMAT_BY_SIZE = {
medium: 'fullDateTime', medium: 'fullDateTime',
}; };
const DueDate = React.memo(({ value, size, isDisabled, onClick }) => { const STATUS_ICON_PROPS_BY_STATUS = {
[STATUSES.DUE_SOON]: {
name: 'hourglass half',
color: 'orange',
},
[STATUSES.OVERDUE]: {
name: 'hourglass end',
color: 'red',
},
[STATUSES.COMPLETED]: {
name: 'checkmark',
color: 'green',
},
};
const getStatus = (dateTime, isCompleted) => {
if (isCompleted) {
return STATUSES.COMPLETED;
}
const secondsLeft = Math.floor((dateTime.getTime() - new Date().getTime()) / 1000);
if (secondsLeft <= 0) {
return STATUSES.OVERDUE;
}
if (secondsLeft <= 24 * 60 * 60) {
return STATUSES.DUE_SOON;
}
return null;
};
const DueDate = React.memo(({ value, size, isCompleted, isDisabled, withStatusIcon, onClick }) => {
const [t] = useTranslation(); const [t] = useTranslation();
const forceUpdate = useForceUpdate();
const statusRef = useRef(null);
statusRef.current = getStatus(value, isCompleted);
const intervalRef = useRef(null);
const dateFormat = getDateFormat( const dateFormat = getDateFormat(
value, value,
@@ -35,11 +82,34 @@ const DueDate = React.memo(({ value, size, isDisabled, onClick }) => {
FULL_DATE_FORMAT_BY_SIZE[size], FULL_DATE_FORMAT_BY_SIZE[size],
); );
useEffect(() => {
if ([null, STATUSES.DUE_SOON].includes(statusRef.current)) {
intervalRef.current = setInterval(() => {
const status = getStatus(value, isCompleted);
if (status !== statusRef.current) {
forceUpdate();
}
if (status === STATUSES.OVERDUE) {
clearInterval(intervalRef.current);
}
}, 1000);
}
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [value, isCompleted, forceUpdate]);
const contentNode = ( const contentNode = (
<span <span
className={classNames( className={classNames(
styles.wrapper, styles.wrapper,
styles[`wrapper${upperFirst(size)}`], styles[`wrapper${upperFirst(size)}`],
!withStatusIcon && statusRef.current && styles[`wrapper${upperFirst(statusRef.current)}`],
onClick && styles.wrapperHoverable, onClick && styles.wrapperHoverable,
)} )}
> >
@@ -47,6 +117,10 @@ const DueDate = React.memo(({ value, size, isDisabled, onClick }) => {
value, value,
postProcess: 'formatDate', postProcess: 'formatDate',
})} })}
{withStatusIcon && statusRef.current && (
// eslint-disable-next-line react/jsx-props-no-spreading
<Icon {...STATUS_ICON_PROPS_BY_STATUS[statusRef.current]} className={styles.statusIcon} />
)}
</span> </span>
); );
@@ -62,14 +136,19 @@ const DueDate = React.memo(({ value, size, isDisabled, onClick }) => {
DueDate.propTypes = { DueDate.propTypes = {
value: PropTypes.instanceOf(Date).isRequired, value: PropTypes.instanceOf(Date).isRequired,
size: PropTypes.oneOf(Object.values(SIZES)), size: PropTypes.oneOf(Object.values(SIZES)),
isCompleted: PropTypes.bool.isRequired,
isDisabled: PropTypes.bool, isDisabled: PropTypes.bool,
withStatusIcon: PropTypes.bool,
onClick: PropTypes.func, onClick: PropTypes.func,
onCompletionToggle: PropTypes.func,
}; };
DueDate.defaultProps = { DueDate.defaultProps = {
size: SIZES.MEDIUM, size: SIZES.MEDIUM,
isDisabled: false, isDisabled: false,
withStatusIcon: false,
onClick: undefined, onClick: undefined,
onCompletionToggle: undefined,
}; };
export default DueDate; export default DueDate;
@@ -8,16 +8,17 @@
padding: 0; padding: 0;
} }
.statusIcon {
line-height: 1;
margin: 0 0 0 8px;
}
.wrapper { .wrapper {
background: #dce0e4; background: #dce0e4;
border: none;
border-radius: 3px; border-radius: 3px;
color: #6a808b; color: #6a808b;
display: inline-block; display: inline-block;
outline: none;
text-align: left;
transition: background 0.3s ease; transition: background 0.3s ease;
vertical-align: top;
} }
.wrapperHoverable:hover { .wrapperHoverable:hover {
@@ -43,4 +44,21 @@
line-height: 20px; line-height: 20px;
padding: 6px 12px; padding: 6px 12px;
} }
/* Statuses */
.wrapperDueSoon {
background: #f2711c;
color: #fff;
}
.wrapperOverdue {
background: #db2828;
color: #fff;
}
.wrapperCompleted {
background: #21ba45;
color: #fff;
}
} }
@@ -1,5 +1,6 @@
:global(#app) { :global(#app) {
.wrapper { .wrapper {
max-width: 100vw;
position: fixed; position: fixed;
width: 100%; width: 100%;
z-index: 1; z-index: 1;
@@ -87,7 +87,12 @@
} }
.userName { .userName {
display: none;
margin-right: 10px; margin-right: 10px;
@media only screen and (min-width: 797px) {
display: block;
}
} }
.wrapper { .wrapper {
+2 -2
View File
@@ -17,7 +17,7 @@ const SIZES = {
const Label = React.memo(({ name, color, size, isDisabled, onClick }) => { const Label = React.memo(({ name, color, size, isDisabled, onClick }) => {
const contentNode = ( const contentNode = (
<div <span
title={name} title={name}
className={classNames( className={classNames(
styles.wrapper, styles.wrapper,
@@ -28,7 +28,7 @@ const Label = React.memo(({ name, color, size, isDisabled, onClick }) => {
)} )}
> >
{name || '\u00A0'} {name || '\u00A0'}
</div> </span>
); );
return onClick ? ( return onClick ? (
@@ -11,17 +11,17 @@
.wrapper { .wrapper {
border-radius: 3px; border-radius: 3px;
box-sizing: border-box;
color: #fff; color: #fff;
font-weight: 400; display: inline-block;
outline: none; font-weight: normal;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2); text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2);
vertical-align: top;
white-space: nowrap; white-space: nowrap;
} }
.wrapperNameless{ .wrapperNameless {
width: 40px; width: 40px;
} }
@@ -80,6 +80,12 @@
&:hover .target { &:hover .target {
opacity: 1; opacity: 1;
} }
@media only screen and (max-width: 797px) {
.target {
opacity: 1;
}
}
} }
.headerEditable { .headerEditable {
@@ -103,6 +109,15 @@
background: rgba(9, 30, 66, 0.13); background: rgba(9, 30, 66, 0.13);
color: #516b7a; color: #516b7a;
} }
@media only screen and (max-width: 797px) {
&:focus,
&:active {
background: rgba(9, 30, 66, 0.13);
color: #516b7a;
}
}
} }
.headerName { .headerName {
+2 -1
View File
@@ -4,6 +4,7 @@ import TextareaAutosize from 'react-textarea-autosize';
import { TextArea } from 'semantic-ui-react'; import { TextArea } from 'semantic-ui-react';
import { useField } from '../../hooks'; import { useField } from '../../hooks';
import { focusEnd } from '../../utils/element-helpers';
import styles from './NameEdit.module.scss'; import styles from './NameEdit.module.scss';
@@ -71,7 +72,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
useEffect(() => { useEffect(() => {
if (isOpened) { if (isOpened) {
field.current.ref.current.select(); focusEnd(field.current.ref.current);
} }
}, [isOpened]); }, [isOpened]);
+6
View File
@@ -18,6 +18,11 @@ const createMessage = (error) => {
} }
switch (error.message) { switch (error.message) {
case 'Invalid credentials':
return {
type: 'error',
content: 'common.invalidCredentials',
};
case 'Invalid email or username': case 'Invalid email or username':
return { return {
type: 'error', type: 'error',
@@ -116,6 +121,7 @@ const Login = React.memo(
useEffect(() => { useEffect(() => {
if (wasSubmitting && !isSubmitting && error) { if (wasSubmitting && !isSubmitting && error) {
switch (error.message) { switch (error.message) {
case 'Invalid credentials':
case 'Invalid email or username': case 'Invalid email or username':
emailOrUsernameField.current.select(); emailOrUsernameField.current.select();
@@ -11,6 +11,10 @@
vertical-align: top; vertical-align: top;
width: 36px; width: 36px;
@media only screen and (max-width: 797px) {
margin-left: 10px;
}
&:hover { &:hover {
background: rgba(0, 0, 0, 0.32); background: rgba(0, 0, 0, 0.32);
} }
+1
View File
@@ -13,6 +13,7 @@ import 'react-datepicker/dist/react-datepicker.css';
import 'photoswipe/dist/photoswipe.css'; import 'photoswipe/dist/photoswipe.css';
import 'easymde/dist/easymde.min.css'; import 'easymde/dist/easymde.min.css';
import '../lib/custom-ui/styles.css'; import '../lib/custom-ui/styles.css';
import '../assets/css/font-awesome.css';
import '../styles.module.scss'; import '../styles.module.scss';
function Root({ store, history }) { function Root({ store, history }) {
@@ -10,13 +10,10 @@
.wrapper { .wrapper {
background: #dce0e4; background: #dce0e4;
border: none;
border-radius: 3px; border-radius: 3px;
color: #6a808b; color: #6a808b;
display: inline-block; display: inline-block;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
outline: none;
text-align: left;
transition: background 0.3s ease; transition: background 0.3s ease;
vertical-align: top; vertical-align: top;
} }
@@ -14,12 +14,10 @@
} }
.wrapper { .wrapper {
border: none;
border-radius: 50%; border-radius: 50%;
color: #fff; color: #fff;
display: inline-block; display: inline-block;
line-height: 1; line-height: 1;
outline: none;
text-align: center; text-align: center;
vertical-align: top; vertical-align: top;
} }
@@ -32,7 +30,6 @@
.wrapperTiny { .wrapperTiny {
font-size: 10px; font-size: 10px;
font-weight: 400;
height: 24px; height: 24px;
line-height: 20px; line-height: 20px;
padding: 2px 0; padding: 2px 0;
@@ -41,7 +38,6 @@
.wrapperSmall { .wrapperSmall {
font-size: 12px; font-size: 12px;
font-weight: 400;
height: 28px; height: 28px;
padding: 8px 0; padding: 8px 0;
width: 28px; width: 28px;
@@ -49,7 +45,6 @@
.wrapperMedium { .wrapperMedium {
font-size: 14px; font-size: 14px;
font-weight: 500;
height: 32px; height: 32px;
padding: 10px 0; padding: 10px 0;
width: 32px; width: 32px;
@@ -57,7 +52,6 @@
.wrapperLarge { .wrapperLarge {
font-size: 14px; font-size: 14px;
font-weight: 500;
height: 36px; height: 36px;
padding: 12px 0 10px; padding: 12px 0 10px;
width: 36px; width: 36px;
@@ -65,7 +59,6 @@
.wrapperMassive { .wrapperMassive {
font-size: 36px; font-size: 36px;
font-weight: 500;
height: 100px; height: 100px;
padding: 32px 0 10px; padding: 32px 0 10px;
width: 100px; width: 100px;
@@ -2,7 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Image, Tab } from 'semantic-ui-react'; import { Image, Tab } from 'semantic-ui-react';
import Config from '../../constants/Config'; import version from '../../version';
import logo from '../../assets/images/logo.png'; import logo from '../../assets/images/logo.png';
@@ -15,7 +15,7 @@ const AboutPane = React.memo(() => {
<Tab.Pane attached={false} className={styles.wrapper}> <Tab.Pane attached={false} className={styles.wrapper}>
<Image centered src={logo} size="large" /> <Image centered src={logo} size="large" />
<div className={styles.version}> <div className={styles.version}>
{t('common.version')} {Config.VERSION} {t('common.version')} {version}
</div> </div>
</Tab.Pane> </Tab.Pane>
); );
-3
View File
@@ -195,9 +195,6 @@ export default {
CARD_UPDATE__SUCCESS: 'CARD_UPDATE__SUCCESS', CARD_UPDATE__SUCCESS: 'CARD_UPDATE__SUCCESS',
CARD_UPDATE__FAILURE: 'CARD_UPDATE__FAILURE', CARD_UPDATE__FAILURE: 'CARD_UPDATE__FAILURE',
CARD_UPDATE_HANDLE: 'CARD_UPDATE_HANDLE', CARD_UPDATE_HANDLE: 'CARD_UPDATE_HANDLE',
CARD_TRANSFER: 'CARD_TRANSFER',
CARD_TRANSFER__SUCCESS: 'CARD_TRANSFER__SUCCESS',
CARD_TRANSFER__FAILURE: 'CARD_TRANSFER__FAILURE',
CARD_DUPLICATE: 'CARD_DUPLICATE', CARD_DUPLICATE: 'CARD_DUPLICATE',
CARD_DUPLICATE__SUCCESS: 'CARD_DUPLICATE__SUCCESS', CARD_DUPLICATE__SUCCESS: 'CARD_DUPLICATE__SUCCESS',
CARD_DUPLICATE__FAILURE: 'CARD_DUPLICATE__FAILURE', CARD_DUPLICATE__FAILURE: 'CARD_DUPLICATE__FAILURE',
+3 -3
View File
@@ -1,12 +1,12 @@
const VERSION = process.env.REACT_APP_VERSION;
const { BASE_URL } = window; const { BASE_URL } = window;
const BASE_PATH = BASE_URL.replace(/^.*\/\/[^/]*(.*)[^?#]*.*$/, '$1'); const BASE_PATH = BASE_URL.replace(/^.*\/\/[^/]*(.*)[^?#]*.*$/, '$1');
const SERVER_BASE_URL = const SERVER_BASE_URL =
process.env.REACT_APP_SERVER_BASE_URL || process.env.REACT_APP_SERVER_BASE_URL ||
(process.env.NODE_ENV === 'production' ? BASE_URL : 'http://localhost:1337'); (process.env.NODE_ENV === 'production' ? BASE_URL : 'http://localhost:1337');
const SERVER_BASE_PATH = SERVER_BASE_URL.replace(/^.*\/\/[^/]*(.*)[^?#]*.*$/, '$1');
const SERVER_HOST_NAME = SERVER_BASE_URL.replace(/^(.*\/\/[^/?#]*).*$/, '$1'); const SERVER_HOST_NAME = SERVER_BASE_URL.replace(/^(.*\/\/[^/?#]*).*$/, '$1');
const ACCESS_TOKEN_KEY = 'accessToken'; const ACCESS_TOKEN_KEY = 'accessToken';
@@ -17,9 +17,9 @@ const POSITION_GAP = 65535;
const ACTIVITIES_LIMIT = 50; const ACTIVITIES_LIMIT = 50;
export default { export default {
VERSION,
BASE_PATH, BASE_PATH,
SERVER_BASE_URL, SERVER_BASE_URL,
SERVER_BASE_PATH,
SERVER_HOST_NAME, SERVER_HOST_NAME,
ACCESS_TOKEN_KEY, ACCESS_TOKEN_KEY,
ACCESS_TOKEN_VERSION_KEY, ACCESS_TOKEN_VERSION_KEY,
+3 -4
View File
@@ -20,10 +20,8 @@ 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, stopwatch, coverUrl, boardId, listId, isPersisted } = selectCardById( const { name, dueDate, isDueDateCompleted, stopwatch, coverUrl, boardId, listId, isPersisted } =
state, selectCardById(state, id);
id,
);
const users = selectUsersByCardId(state, id); const users = selectUsersByCardId(state, id);
const labels = selectLabelsByCardId(state, id); const labels = selectLabelsByCardId(state, id);
@@ -38,6 +36,7 @@ const makeMapStateToProps = () => {
index, index,
name, name,
dueDate, dueDate,
isDueDateCompleted,
stopwatch, stopwatch,
coverUrl, coverUrl,
boardId, boardId,
@@ -21,6 +21,7 @@ const mapStateToProps = (state) => {
name, name,
description, description,
dueDate, dueDate,
isDueDateCompleted,
stopwatch, stopwatch,
isSubscribed, isSubscribed,
isActivitiesFetching, isActivitiesFetching,
@@ -49,6 +50,7 @@ const mapStateToProps = (state) => {
name, name,
description, description,
dueDate, dueDate,
isDueDateCompleted,
stopwatch, stopwatch,
isSubscribed, isSubscribed,
isActivitiesFetching, isActivitiesFetching,
+2 -1
View File
@@ -6,12 +6,13 @@ import entryActions from '../entry-actions';
import Projects from '../components/Projects'; import Projects from '../components/Projects';
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
const { allowAllToCreateProjects } = selectors.selectConfig(state);
const { isAdmin } = selectors.selectCurrentUser(state); const { isAdmin } = selectors.selectCurrentUser(state);
const projects = selectors.selectProjectsForCurrentUser(state); const projects = selectors.selectProjectsForCurrentUser(state);
return { return {
items: projects, items: projects,
canAdd: isAdmin, canAdd: allowAllToCreateProjects || isAdmin,
}; };
}; };
+3 -3
View File
@@ -58,9 +58,9 @@ i18n
.use(initReactI18next) .use(initReactI18next)
.init({ .init({
resources: embeddedLocales, resources: embeddedLocales,
fallbackLng: 'en', fallbackLng: 'en-US',
supportedLngs: languages, supportedLngs: languages,
load: 'languageOnly', load: 'currentOnly',
interpolation: { interpolation: {
escapeValue: false, escapeValue: false,
format(value, format, language) { format(value, format, language) {
@@ -80,7 +80,7 @@ i18n
}); });
i18n.loadCoreLocale = async (language = i18n.resolvedLanguage) => { i18n.loadCoreLocale = async (language = i18n.resolvedLanguage) => {
if (language === 'en') { if (language === i18n.options.fallbackLng[0]) {
return; return;
} }
@@ -64,7 +64,7 @@
.markdown-body b, .markdown-body b,
.markdown-body strong { .markdown-body strong {
font-weight: 600; font-weight: bold; // 600
} }
.markdown-body dfn { .markdown-body dfn {
@@ -73,7 +73,7 @@
.markdown-body h1 { .markdown-body h1 {
margin: .67em 0; margin: .67em 0;
font-weight: 600; font-weight: bold; // 600
padding-bottom: .3em; padding-bottom: .3em;
font-size: 2em; font-size: 2em;
border-bottom: 1px solid hsla(210, 18%, 87%, 1); border-bottom: 1px solid hsla(210, 18%, 87%, 1);
@@ -253,34 +253,34 @@
.markdown-body h6 { .markdown-body h6 {
margin-top: 24px; margin-top: 24px;
margin-bottom: 16px; margin-bottom: 16px;
font-weight: 600; font-weight: bold; // 600
line-height: 1.25; line-height: 1.25;
} }
.markdown-body h2 { .markdown-body h2 {
font-weight: 600; font-weight: bold; // 600
padding-bottom: .3em; padding-bottom: .3em;
font-size: 1.5em; font-size: 1.5em;
border-bottom: 1px solid hsla(210, 18%, 87%, 1); border-bottom: 1px solid hsla(210, 18%, 87%, 1);
} }
.markdown-body h3 { .markdown-body h3 {
font-weight: 600; font-weight: bold; // 600
font-size: 1.25em; font-size: 1.25em;
} }
.markdown-body h4 { .markdown-body h4 {
font-weight: 600; font-weight: bold; // 600
font-size: 1em; font-size: 1em;
} }
.markdown-body h5 { .markdown-body h5 {
font-weight: 600; font-weight: bold; // 600
font-size: .875em; font-size: .875em;
} }
.markdown-body h6 { .markdown-body h6 {
font-weight: 600; font-weight: bold; // 600
font-size: .85em; font-size: .85em;
color: #57606a; color: #57606a;
} }
@@ -481,7 +481,7 @@
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 1em; font-size: 1em;
font-style: normal !important; font-style: normal !important;
font-weight: 400; font-weight: normal; // 400
line-height: 1; line-height: 1;
vertical-align: -0.075em; vertical-align: -0.075em;
} }
@@ -650,7 +650,7 @@
margin-top: 16px; margin-top: 16px;
font-size: 1em; font-size: 1em;
font-style: italic; font-style: italic;
font-weight: 600; font-weight: bold; // 600
} }
.markdown-body dl dd { .markdown-body dl dd {
@@ -659,7 +659,7 @@
} }
.markdown-body table th { .markdown-body table th {
font-weight: 600; font-weight: bold; // 600
} }
.markdown-body table th, .markdown-body table th,
@@ -868,7 +868,7 @@
} }
.markdown-body .csv-data th { .markdown-body .csv-data th {
font-weight: 600; font-weight: bold; // 600
background: #f6f8fa; background: #f6f8fa;
border-top: 0; border-top: 0;
} }
@@ -912,7 +912,7 @@
} }
.markdown-body .task-list-item label { .markdown-body .task-list-item label {
font-weight: 400; font-weight: normal;
} }
.markdown-body .task-list-item.enabled label { .markdown-body .task-list-item.enabled label {
+2568 -2729
View File
File diff suppressed because one or more lines are too long
+252
View File
@@ -0,0 +1,252 @@
import dateFns from 'date-fns/locale/bg';
export default {
dateFns,
format: {
date: 'd.MM.yyyy',
time: 'p',
dateTime: '$t(format:date) $t(format:time)',
longDate: 'd. MMM',
longDateTime: "d. MMMM 'в' p",
fullDate: 'd. MMM. y',
fullDateTime: "d. MMMM. y 'в' p",
},
translation: {
common: {
aboutPlanka: 'За Planka',
account: 'Акаунт',
actions: 'Действия',
addAttachment_title: 'Добавяне на прикачен файл',
addComment: 'Добавяне на коментар',
addManager_title: 'Добавяне на мениджър',
addMember_title: 'Добавяне на член',
addUser_title: 'Добавяне на потребител',
administrator: 'Администратор',
all: 'Всички',
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'Всички промени ще бъдат автоматично запазени<br />след възстановяване на връзката.',
areYouSureYouWantToDeleteThisAttachment:
'Сигурни ли сте, че искате да изтриете този прикачен файл?',
areYouSureYouWantToDeleteThisBoard: 'Сигурни ли сте, че искате да изтриете това табло?',
areYouSureYouWantToDeleteThisCard: 'Сигурни ли сте, че искате да изтриете тази карта?',
areYouSureYouWantToDeleteThisComment: 'Сигурни ли сте, че искате да изтриете този коментар?',
areYouSureYouWantToDeleteThisLabel: 'Сигурни ли сте, че искате да изтриете този етикет?',
areYouSureYouWantToDeleteThisList: 'Сигурни ли сте, че искате да изтриете този списък?',
areYouSureYouWantToDeleteThisProject: 'Сигурни ли сте, че искате да изтриете този проект?',
areYouSureYouWantToDeleteThisTask: 'Сигурни ли сте, че искате да изтриете тази задача?',
areYouSureYouWantToDeleteThisUser: 'Сигурни ли сте, че искате да изтриете този потребител?',
areYouSureYouWantToLeaveBoard: 'Сигурни ли сте, че искате да напуснете таблото?',
areYouSureYouWantToLeaveProject: 'Сигурни ли сте, че искате да напуснете проекта?',
areYouSureYouWantToRemoveThisManagerFromProject:
'Сигурни ли сте, че искате да премахнете този мениджър от проекта?',
areYouSureYouWantToRemoveThisMemberFromBoard:
'Сигурни ли сте, че искате да премахнете този член от таблото?',
attachment: 'Прикачен файл',
attachments: 'Прикачени файлове',
authentication: 'Удостоверяване',
background: 'Фон',
board: 'Табло',
boardNotFound_title: 'Таблото не е намерено',
canComment: 'може да коментира',
canEditContentOfBoard: 'може да редактира съдържание на таблото',
canOnlyViewBoard: 'може само да преглежда таблото',
cardActions_title: 'Действия с карта',
cardNotFound_title: 'Картата не може да бъде открита',
cardOrActionAreDeleted: 'Картата или действието са изтрити.',
color: 'Цвят',
copy_inline: 'копирай',
createBoard_title: 'Създаване на табло',
createLabel_title: 'Създаване на етикет',
createNewOneOrSelectExistingOne: 'Създайте нов или изберете<br/>съществуващ.',
createProject_title: 'Създаване на проект',
createTextFile_title: 'Създаване на текстов файл',
currentPassword: 'Текуща парола',
dangerZone_title: 'Опасна зона',
date: 'Дата',
dueDate: 'Дата на падежа',
dueDate_title: 'Краен срок',
deleteAttachment_title: 'Изтриване на прикачен файл',
deleteBoard_title: 'Изтриване на табло',
deleteCard_title: 'Изтриване на карта',
deleteComment_title: 'Изтриване на коментар',
deleteLabel_title: 'Изтриване на етикета',
deleteList_title: 'Изтриване на списък',
deleteProject_title: 'Изтриване на проект',
deleteTask_title: 'Изтриване на задача',
deleteUser_title: 'Изтриване на потребител',
description: 'Описание',
detectAutomatically: 'Aвтоматично откриване',
dropFileToUpload: 'Пуснете файл за качване',
editor: 'Редактор',
editAttachment_title: 'Редактирай прикачения файл',
editAvatar_title: 'Редактиране на аватар',
editBoard_title: 'Редактиране на табло',
editDueDate_title: 'Редактиране на дата на падеж',
editEmail_title: 'Редактиране на имейл',
editInformation_title: 'Редактиране на информация',
editLabel_title: 'Редактиране на табло',
editPassword_title: 'Редактиране на парола',
editPermissions_title: 'Редактиране на права',
editStopwatch_title: 'Редактиране на Хронометър',
editUsername_title: 'Редактиране на потребителско име',
email: 'Имейл',
emailAlreadyInUse: 'Имейлът вече се използва',
enterCardTitle: 'Въведете заглавие на картата... [Ctrl+Enter] за автоматично отваряне.',
enterDescription: 'Въведете описание...',
enterFilename: 'Въведете име на файла',
enterListTitle: 'Въведете заглавие на списък...',
enterProjectTitle: 'Въведете заглавие на проекта',
enterTaskDescription: 'Въведете описание на задачата...',
filterByLabels_title: 'Филтриране по етикети',
filterByMembers_title: 'Филтриране по членове',
fromComputer_title: 'От компютър',
fromTrello: 'От Trello',
general: 'Общ',
hours: 'Часове',
importBoard_title: 'Импортиране на табло',
invalidCurrentPassword: 'Невалидна текуща парола',
labels: 'Етикети',
language: 'Език',
leaveBoard_title: 'Напуснете таблото',
leaveProject_title: 'Напуснете проекта',
list: 'Списък',
listActions_title: 'Списък с действия',
managers: 'Мениджъри',
managerActions_title: 'Действия с мениджъри',
members: 'Членове',
memberActions_title: 'Действия с членове',
minutes: 'Минути',
moveCard_title: 'Преместване на карта',
name: 'Име',
newestFirst: 'Първо най-новите',
newEmail: 'Нов имейл',
newPassword: 'Нова парола',
newUsername: 'Ново потребителско име',
noConnectionToServer: 'Няма връзка със сървъра',
noBoards: 'Няма табла',
noLists: 'Няма списъци',
noProjects: 'Няма проекти',
notifications: 'Уведомления',
noUnreadNotifications: 'Няма непрочетени известия.',
oldestFirst: 'Първо най-старите',
openBoard_title: 'Отворете табло',
optional_inline: 'по желание',
organization: 'Организация',
phone: 'Телефон',
preferences: 'Предпочитания',
pressPasteShortcutToAddAttachmentFromClipboard:
'Съвет: натиснете Ctrl-V (Cmd-V на Mac), за да добавите прикачен файл от клипборда',
project: 'Проект',
projectNotFound_title: 'Проектът не е намерен',
removeManager_title: 'Премахване на мениджър',
removeMember_title: 'Премахване на член',
searchLabels: 'Търсене на етикети...',
searchMembers: 'Търсене на членове...',
searchUsers: 'Търсене на потребители...',
searchCards: 'Търсене на карти...',
seconds: 'секунди',
selectBoard: 'Изберете табло',
selectList: 'Изберете списък',
selectPermissions_title: 'Изберете правата',
selectProject: 'Изберете проект',
settings: 'Настройки',
sortList_title: 'Сортиране на списък',
stopwatch: 'Хронометър',
subscribeToMyOwnCardsByDefault: 'Абонирайте се за собствените си карти по подразбиране',
taskActions_title: 'Действия със задачи',
tasks: 'Задачи',
thereIsNoPreviewAvailableForThisAttachment: 'Няма наличен преглед за този прикачен файл.',
time: 'Време',
title: 'Заглавие',
userActions_title: 'Потребителски действия',
userAddedThisCardToList: '<0>{{user}}</0><1> добави тази карта в {{list}}</1>',
userLeftNewCommentToCard: '{{user}} остави нов коментар «{{comment}}» в <2>{{card}}</2>',
userMovedCardFromListToList:
'{{user}} премести <2>{{card}}</2> от {{fromList}} към {{toList}}',
userMovedThisCardFromListToList:
'<0>{{user}}</0><1> премести тази карта от {{fromList}} към {{toList}}</1>',
username: 'Потребителско име',
usernameAlreadyInUse: 'Потребителското име вече се използва',
users: 'Потребители',
version: 'Версия',
viewer: 'Зрител',
writeComment: 'Напишете коментар...',
},
action: {
addAnotherCard: 'Добавяне на друга карта',
addAnotherList: 'Добавяне на друг списък',
addAnotherTask: 'Добавяне на друга задача',
addCard: 'Добавяне на карта',
addCard_title: 'Добавяне на карта',
addComment: 'Добавяне на коментар',
addList: 'Добавяне на списък',
addMember: 'Добавяне на член',
addMoreDetailedDescription: 'Добавяне на по-подробно описание',
addTask: 'Добавяне на задача',
addToCard: 'Добавяне към карта',
addUser: 'Добавяне на потребител',
createBoard: 'Създаване на табло',
createFile: 'Създаване на файл',
createLabel: 'Създаване на етикет',
createNewLabel: 'Създаване на нов етикет',
createProject: 'Създаване на проект',
delete: 'Изтриване',
deleteAttachment: 'Изтриване на прикачения файл',
deleteAvatar: 'Изтриване на аватар',
deleteBoard: 'Изтриване на табло',
deleteCard: 'Изтриване на карта',
deleteCard_title: 'Изтриване на карта',
deleteComment: 'Изтриване на коментара',
deleteImage: 'Изтриване на изображение',
deleteLabel: 'Изтриване на етикета',
deleteList: 'Изтриване на списък',
deleteList_title: 'Изтриване на списък',
deleteProject: 'Изтриване на проект',
deleteProject_title: 'Изтриване на проект',
deleteTask: 'Изтриване на задача',
deleteTask_title: 'Изтриване на задача',
deleteUser: 'Изтриване на потребител',
duplicate: 'Направи дубликат',
duplicateCard_title: 'Дублирана карта',
edit: 'Редактиране',
editDueDate_title: 'Редактиране на краен срок',
editDescription_title: 'Редактиране на описание',
editEmail_title: 'Редактиране на имейл',
editInformation_title: 'Редактиране на информация',
editPassword_title: 'Редактиране на парола',
editPermissions: 'Разрешения за редактиране',
editStopwatch_title: 'Редактиране на хронометър',
editTitle_title: 'Редактиране на заглавието',
editUsername_title: 'Редактиране на потребителско име',
hideDetails: 'Скриване на детайлите',
import: 'Импортиране',
leaveBoard: 'Напускане на дъската',
leaveProject: 'Напускане на проекта',
logOut_title: 'Излезте',
makeCover_title: 'Създаване на корица',
move: 'Преместване',
moveCard_title: 'Преместване на карта',
remove: 'Премахване',
removeBackground: 'Премахване на фона',
removeCover_title: 'Премахване на корицата',
removeFromBoard: 'Премахване от борда',
removeFromProject: 'Премахване от проекта',
removeManager: 'Премахване на мениджър',
removeMember: 'Премахване на член',
save: 'Запазване',
showAllAttachments: 'Показване на всички прикачени файлове ({{hidden}} скрити)',
showDetails: 'Показване на подробности',
showFewerAttachments: 'Показване на по-малко прикачени файлове',
sortList_title: 'Списък за сортиране',
start: 'Старт',
stop: 'Стоп',
subscribe: 'Абонирайте се',
unsubscribe: 'Отписване',
uploadNewAvatar: 'Качване на нов аватар',
uploadNewImage: 'Качване на ново изображение',
},
},
};

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