Compare commits
74
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0f584e814 | ||
|
|
135f5eccb5 | ||
|
|
10001db8cf | ||
|
|
7ee351fe30 | ||
|
|
73746b83d1 | ||
|
|
f9a7b38ce9 | ||
|
|
1f261fe7b6 | ||
|
|
80bee30463 | ||
|
|
a9957481b6 | ||
|
|
36b05a5478 | ||
|
|
af91512abf | ||
|
|
8fd0f682d9 | ||
|
|
9699fbe76a | ||
|
|
4176a62f1a | ||
|
|
c12f0844d7 | ||
|
|
e6644eb745 | ||
|
|
b2e1fba9a0 | ||
|
|
818ae4a6df | ||
|
|
0de0e809ac | ||
|
|
da727c0a0b | ||
|
|
4841a65c77 | ||
|
|
0ec4b619d9 | ||
|
|
12f05adde7 | ||
|
|
c594e8bd71 | ||
|
|
f84166406f | ||
|
|
82b2ba298d | ||
|
|
38b6805ba1 | ||
|
|
6a7a582045 | ||
|
|
c53a00b4ba | ||
|
|
52103c775b | ||
|
|
eeb1bd80cc | ||
|
|
e6b8538863 | ||
|
|
9dc38932fb | ||
|
|
e9b4a4adfc | ||
|
|
76c78c7252 | ||
|
|
04f52a63f2 | ||
|
|
6b16a104b1 | ||
|
|
aff853c602 | ||
|
|
ad2966c5d6 | ||
|
|
8d74cc1732 | ||
|
|
ea94fe8bf3 | ||
|
|
6052f8999f | ||
|
|
67d1b1c194 | ||
|
|
b3cb8405f5 | ||
|
|
7acaec6d04 | ||
|
|
72896a66b3 | ||
|
|
4be4783251 | ||
|
|
5c6c6a0bcf | ||
|
|
ba178d66fa | ||
|
|
270ce29f87 | ||
|
|
de1ed3c784 | ||
|
|
caf8790f28 | ||
|
|
81436b373d | ||
|
|
cc1e886a31 | ||
|
|
f0e9fe7904 | ||
|
|
5fcea5d651 | ||
|
|
a963d85a66 | ||
|
|
04b42decef | ||
|
|
c065566c15 | ||
|
|
3779bdb053 | ||
|
|
4124ab17d2 | ||
|
|
0e3bc92a61 | ||
|
|
b8d7e713b9 | ||
|
|
017eee8302 | ||
|
|
1b28892cf5 | ||
|
|
013ea1b869 | ||
|
|
4975fa2eeb | ||
|
|
3e28ad87c7 | ||
|
|
2d6666d693 | ||
|
|
4cbd2f85c7 | ||
|
|
fae9de4e1c | ||
|
|
89c1ed71e1 | ||
|
|
1725f3cb8a | ||
|
|
f7be49e402 |
@@ -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
@@ -1,4 +1,2 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Planka
|
||||
#### Elegant open source project tracking.
|
||||
|
||||
  
|
||||
  
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -15,13 +15,13 @@ 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.29
|
||||
version: 0.2.8
|
||||
|
||||
# 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.17.2"
|
||||
appVersion: "1.22.0"
|
||||
|
||||
dependencies:
|
||||
- alias: postgresql
|
||||
|
||||
@@ -14,11 +14,15 @@ If you want to fully uninstall this chart including the data, follow [these step
|
||||
|
||||
## 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:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Chris-Greaves/planka-helm-chart.git
|
||||
cd planka-helm-chart
|
||||
git clone https://github.com/plankanban/planka.git
|
||||
cd planka/charts/planka
|
||||
helm dependency build
|
||||
export SECRETKEY=$(openssl rand -hex 64)
|
||||
helm install planka . --set secretkey=$SECRETKEY \
|
||||
|
||||
@@ -11,6 +11,10 @@ spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "planka.selectorLabels" . | nindent 6 }}
|
||||
{{- if .Values.persistence.enabled }}
|
||||
strategy:
|
||||
type: Recreate
|
||||
{{- end }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
@@ -64,8 +68,16 @@ spec:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
env:
|
||||
{{- if not .Values.postgresql.enabled }}
|
||||
{{- if .Values.existingDburlSecret }}
|
||||
- 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 }}
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
@@ -82,17 +94,37 @@ spec:
|
||||
value: http://localhost:3000
|
||||
{{- end }}
|
||||
- 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 }}
|
||||
{{- end }}
|
||||
- name: TRUST_PROXY
|
||||
value: "0"
|
||||
- name: DEFAULT_ADMIN_EMAIL
|
||||
value: {{ .Values.admin_email }}
|
||||
- name: DEFAULT_ADMIN_PASSWORD
|
||||
value: {{ .Values.admin_password }}
|
||||
- name: DEFAULT_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
|
||||
value: {{ .Values.admin_username }}
|
||||
- name: DEFAULT_ADMIN_PASSWORD
|
||||
value: {{ .Values.admin_password }}
|
||||
{{- end }}
|
||||
{{ range $k, $v := .Values.env }}
|
||||
- name: {{ $k | quote }}
|
||||
value: {{ $v | quote }}
|
||||
|
||||
@@ -17,6 +17,16 @@ fullnameOverride: ""
|
||||
# Generate a secret using openssl rand -base64 45
|
||||
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`
|
||||
# Defaults to `http://localhost:3000` if ingress is disabled.
|
||||
baseUrl: ""
|
||||
@@ -105,9 +115,15 @@ postgresql:
|
||||
serviceBindings:
|
||||
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:
|
||||
|
||||
## @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
|
||||
persistence:
|
||||
enabled: false
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
REACT_APP_VERSION=1.17.2
|
||||
Generated
+2216
-7883
File diff suppressed because it is too large
Load Diff
+17
-16
@@ -65,8 +65,8 @@
|
||||
"dequal": "^2.0.3",
|
||||
"easymde": "^2.18.0",
|
||||
"history": "^5.3.0",
|
||||
"i18next": "^23.11.2",
|
||||
"i18next-browser-languagedetector": "^7.2.1",
|
||||
"i18next": "^23.15.1",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"initials": "^3.1.2",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jwt-decode": "^4.0.0",
|
||||
@@ -75,20 +75,20 @@
|
||||
"lodash": "^4.17.21",
|
||||
"nanoid": "^5.0.7",
|
||||
"node-sass": "^9.0.0",
|
||||
"photoswipe": "^5.4.3",
|
||||
"photoswipe": "^5.4.4",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react": "18.2.0",
|
||||
"react-app-rewired": "^2.2.1",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-datepicker": "^4.25.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-i18next": "^13.5.0",
|
||||
"react-i18next": "^15.0.2",
|
||||
"react-input-mask": "^2.0.4",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-photoswipe-gallery": "^2.2.7",
|
||||
"react-redux": "^8.1.3",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react-router-dom": "^6.26.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-simplemde-editor": "^5.2.0",
|
||||
"react-textarea-autosize": "^8.5.3",
|
||||
@@ -102,22 +102,23 @@
|
||||
"sails.io.js": "^1.2.1",
|
||||
"semantic-ui-react": "^2.1.5",
|
||||
"socket.io-client": "^2.5.0",
|
||||
"validator": "^13.11.0",
|
||||
"validator": "^13.12.0",
|
||||
"whatwg-fetch": "^3.6.20",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/react": "^14.3.1",
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@testing-library/jest-dom": "^6.5.0",
|
||||
"@testing-library/react": "^15.0.7",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"babel-preset-airbnb": "^5.0.0",
|
||||
"chai": "^4.4.1",
|
||||
"chai": "^4.5.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"react-test-renderer": "^18.2.0"
|
||||
"eslint-plugin-import": "^2.30.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.0",
|
||||
"eslint-plugin-react": "^7.36.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"react-test-renderer": "18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<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="description"
|
||||
|
||||
@@ -57,10 +57,15 @@ updateCard.failure = (id, error) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const handleCardUpdate = (card) => ({
|
||||
const handleCardUpdate = (card, isFetched, cardMemberships, cardLabels, tasks, attachments) => ({
|
||||
type: ActionTypes.CARD_UPDATE_HANDLE,
|
||||
payload: {
|
||||
card,
|
||||
isFetched,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
tasks,
|
||||
attachments,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import http from './http';
|
||||
import socket from './socket';
|
||||
|
||||
/* 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) =>
|
||||
http.post('/access-tokens/exchange-using-oidc', data, headers);
|
||||
http.post('/access-tokens/exchange-using-oidc?withHttpOnlyToken=true', data, headers);
|
||||
|
||||
const deleteCurrentAccessToken = (headers) =>
|
||||
socket.delete('/access-tokens/me', undefined, headers);
|
||||
const deleteCurrentAccessToken = (headers) => http.delete('/access-tokens/me', undefined, headers);
|
||||
|
||||
export default {
|
||||
createAccessToken,
|
||||
|
||||
@@ -5,7 +5,7 @@ import Config from '../constants/Config';
|
||||
const http = {};
|
||||
|
||||
// TODO: add all methods
|
||||
['GET', 'POST'].forEach((method) => {
|
||||
['GET', 'POST', 'DELETE'].forEach((method) => {
|
||||
http[method.toLowerCase()] = (url, data, headers) => {
|
||||
const formData =
|
||||
data &&
|
||||
@@ -19,6 +19,7 @@ const http = {};
|
||||
method,
|
||||
headers,
|
||||
body: formData,
|
||||
credentials: 'include',
|
||||
})
|
||||
.then((response) =>
|
||||
response.json().then((body) => ({
|
||||
|
||||
@@ -13,7 +13,7 @@ io.sails.environment = process.env.NODE_ENV;
|
||||
|
||||
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
|
||||
|
||||
['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => {
|
||||
|
||||
+2337
File diff suppressed because it is too large
Load Diff
+4
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) {
|
||||
.wrapper {
|
||||
height: 100%;
|
||||
max-height: 100vh;
|
||||
max-width: 100vw;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
|
||||
@@ -48,16 +48,6 @@
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.panel {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.panelItem {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: flex-start;
|
||||
margin: 20px 20px;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,10 @@
|
||||
transition: width 0.2s ease;
|
||||
width: 280px;
|
||||
|
||||
@media only screen and (max-width: 797px) {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ const Card = React.memo(
|
||||
index,
|
||||
name,
|
||||
dueDate,
|
||||
isDueDateCompleted,
|
||||
stopwatch,
|
||||
coverUrl,
|
||||
boardId,
|
||||
@@ -120,7 +121,7 @@ const Card = React.memo(
|
||||
)}
|
||||
{dueDate && (
|
||||
<span className={classNames(styles.attachment, styles.attachmentLeft)}>
|
||||
<DueDate value={dueDate} size="tiny" />
|
||||
<DueDate value={dueDate} isCompleted={isDueDateCompleted} size="tiny" />
|
||||
</span>
|
||||
)}
|
||||
{stopwatch && (
|
||||
@@ -221,6 +222,7 @@ Card.propTypes = {
|
||||
index: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
dueDate: PropTypes.instanceOf(Date),
|
||||
isDueDateCompleted: PropTypes.bool,
|
||||
stopwatch: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||
coverUrl: PropTypes.string,
|
||||
boardId: PropTypes.string.isRequired,
|
||||
@@ -255,6 +257,7 @@ Card.propTypes = {
|
||||
|
||||
Card.defaultProps = {
|
||||
dueDate: undefined,
|
||||
isDueDateCompleted: undefined,
|
||||
stopwatch: undefined,
|
||||
coverUrl: undefined,
|
||||
};
|
||||
|
||||
@@ -21,6 +21,14 @@
|
||||
background: #ebeef0;
|
||||
color: #516b7a;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 797px) {
|
||||
&:focus,
|
||||
&:active {
|
||||
background: #ebeef0;
|
||||
color: #516b7a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.attachment {
|
||||
@@ -55,6 +63,12 @@
|
||||
box-shadow: 0 1px 0 #ccc;
|
||||
position: relative;
|
||||
|
||||
@media only screen and (max-width: 797px) {
|
||||
.target {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #f5f6f7;
|
||||
border-bottom-color: rgba(9, 30, 66, 0.25);
|
||||
|
||||
@@ -5,6 +5,7 @@ import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||
|
||||
import { useClosableForm, useField } from '../../hooks';
|
||||
import { focusEnd } from '../../utils/element-helpers';
|
||||
|
||||
import styles from './NameEdit.module.scss';
|
||||
|
||||
@@ -79,7 +80,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpened) {
|
||||
field.current.ref.current.focus();
|
||||
focusEnd(field.current.ref.current);
|
||||
}
|
||||
}, [isOpened]);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||
|
||||
import { useForm } from '../../../hooks';
|
||||
import { focusEnd } from '../../../utils/element-helpers';
|
||||
|
||||
import styles from './CommentEdit.module.scss';
|
||||
|
||||
@@ -70,7 +71,7 @@ const CommentEdit = React.forwardRef(({ children, defaultData, onUpdate }, ref)
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpened) {
|
||||
textField.current.ref.current.focus();
|
||||
focusEnd(textField.current.ref.current);
|
||||
}
|
||||
}, [isOpened]);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.dropzone {
|
||||
background: white;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
height: 100%;
|
||||
line-height: 30px;
|
||||
opacity: 0.7;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
.contentError {
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
height: 20px;
|
||||
width: 470px;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
color: #5e6c84;
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
height: 100%;
|
||||
line-height: 80px;
|
||||
overflow: hidden;
|
||||
@@ -50,7 +50,7 @@
|
||||
.name {
|
||||
color: #17394d;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
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 { Markdown } from '../../lib/custom-ui';
|
||||
|
||||
@@ -32,6 +32,7 @@ const CardModal = React.memo(
|
||||
name,
|
||||
description,
|
||||
dueDate,
|
||||
isDueDateCompleted,
|
||||
stopwatch,
|
||||
isSubscribed,
|
||||
isActivitiesFetching,
|
||||
@@ -81,6 +82,7 @@ const CardModal = React.memo(
|
||||
onClose,
|
||||
}) => {
|
||||
const [t] = useTranslation();
|
||||
const [isLinkCopied, setIsLinkCopied] = useState(false);
|
||||
|
||||
const isGalleryOpened = useRef(false);
|
||||
|
||||
@@ -117,6 +119,12 @@ const CardModal = React.memo(
|
||||
[onUpdate],
|
||||
);
|
||||
|
||||
const handleDueDateCompletionChange = useCallback(() => {
|
||||
onUpdate({
|
||||
isDueDateCompleted: !isDueDateCompleted,
|
||||
});
|
||||
}, [isDueDateCompleted, onUpdate]);
|
||||
|
||||
const handleStopwatchUpdate = useCallback(
|
||||
(newStopwatch) => {
|
||||
onUpdate({
|
||||
@@ -146,6 +154,14 @@ const CardModal = React.memo(
|
||||
onClose();
|
||||
}, [onDuplicate, onClose]);
|
||||
|
||||
const handleCopyLinkClick = useCallback(() => {
|
||||
navigator.clipboard.writeText(window.location.href);
|
||||
setIsLinkCopied(true);
|
||||
setTimeout(() => {
|
||||
setIsLinkCopied(false);
|
||||
}, 5000);
|
||||
}, []);
|
||||
|
||||
const handleGalleryOpen = useCallback(() => {
|
||||
isGalleryOpened.current = true;
|
||||
}, []);
|
||||
@@ -291,13 +307,24 @@ const CardModal = React.memo(
|
||||
context: 'title',
|
||||
})}
|
||||
</div>
|
||||
<span className={styles.attachment}>
|
||||
<span className={classNames(styles.attachment, styles.attachmentDueDate)}>
|
||||
{canEdit ? (
|
||||
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}>
|
||||
<DueDate value={dueDate} />
|
||||
</DueDateEditPopup>
|
||||
<>
|
||||
<Checkbox
|
||||
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>
|
||||
</div>
|
||||
@@ -506,6 +533,19 @@ const CardModal = React.memo(
|
||||
<Icon name="copy outline" className={styles.actionIcon} />
|
||||
{t('action.duplicate')}
|
||||
</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
|
||||
title="common.deleteCard"
|
||||
content="common.areYouSureYouWantToDeleteThisCard"
|
||||
@@ -540,6 +580,7 @@ CardModal.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
description: PropTypes.string,
|
||||
dueDate: PropTypes.instanceOf(Date),
|
||||
isDueDateCompleted: PropTypes.bool,
|
||||
stopwatch: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||
isSubscribed: PropTypes.bool.isRequired,
|
||||
isActivitiesFetching: PropTypes.bool.isRequired,
|
||||
@@ -594,6 +635,7 @@ CardModal.propTypes = {
|
||||
CardModal.defaultProps = {
|
||||
description: undefined,
|
||||
dueDate: undefined,
|
||||
isDueDateCompleted: false,
|
||||
stopwatch: undefined,
|
||||
};
|
||||
|
||||
|
||||
@@ -20,18 +20,26 @@
|
||||
|
||||
.actionIcon {
|
||||
color: #17394d;
|
||||
display: inline;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-bottom: 24px;
|
||||
|
||||
@media only screen and (max-width: 797px) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 425px) {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.actionsTitle {
|
||||
color: #8c8c8c;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-weight: normal;
|
||||
letter-spacing: 0.04em;
|
||||
margin-top: 16px;
|
||||
text-transform: uppercase;
|
||||
@@ -50,6 +58,12 @@
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.attachmentDueDate {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.attachments {
|
||||
display: inline-block;
|
||||
margin: 0 8px 8px 0;
|
||||
@@ -63,6 +77,11 @@
|
||||
|
||||
.contentPadding {
|
||||
padding: 8px 8px 0 16px;
|
||||
|
||||
@media only screen and (max-width: 797px) {
|
||||
padding-right: 16px;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cursorPointer {
|
||||
@@ -139,7 +158,7 @@
|
||||
.headerTitle {
|
||||
color: #17394d;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
@@ -155,6 +174,11 @@
|
||||
|
||||
.modalPadding {
|
||||
padding: 0px;
|
||||
|
||||
@media only screen and (max-width: 797px) {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.moduleHeader {
|
||||
@@ -185,6 +209,15 @@
|
||||
|
||||
.sidebarPadding {
|
||||
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 {
|
||||
|
||||
@@ -57,6 +57,7 @@ const DescriptionEdit = React.forwardRef(({ children, defaultValue, onUpdate },
|
||||
|
||||
const mdEditorOptions = useMemo(
|
||||
() => ({
|
||||
autoDownloadFontAwesome: false,
|
||||
autofocus: true,
|
||||
spellChecker: false,
|
||||
status: false,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
box-shadow: none;
|
||||
color: #17394d;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
margin: -5px;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -5,6 +5,7 @@ import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||
|
||||
import { useField } from '../../../hooks';
|
||||
import { focusEnd } from '../../../utils/element-helpers';
|
||||
|
||||
import styles from './NameEdit.module.scss';
|
||||
|
||||
@@ -65,7 +66,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpened) {
|
||||
field.current.ref.current.focus();
|
||||
focusEnd(field.current.ref.current);
|
||||
}
|
||||
}, [isOpened]);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
border-radius: 4px;
|
||||
bottom: 20px;
|
||||
box-shadow: #b04632 0 1px 0;
|
||||
max-width: calc(100% - 40px);
|
||||
padding: 12px 18px;
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import upperFirst from 'lodash/upperFirst';
|
||||
import React from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Icon } from 'semantic-ui-react';
|
||||
import { useForceUpdate } from '../../lib/hooks';
|
||||
|
||||
import getDateFormat from '../../utils/get-date-format';
|
||||
|
||||
@@ -14,6 +16,12 @@ const SIZES = {
|
||||
MEDIUM: 'medium',
|
||||
};
|
||||
|
||||
const STATUSES = {
|
||||
DUE_SOON: 'dueSoon',
|
||||
OVERDUE: 'overdue',
|
||||
COMPLETED: 'completed',
|
||||
};
|
||||
|
||||
const LONG_DATE_FORMAT_BY_SIZE = {
|
||||
tiny: 'longDate',
|
||||
small: 'longDate',
|
||||
@@ -26,8 +34,47 @@ const FULL_DATE_FORMAT_BY_SIZE = {
|
||||
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 forceUpdate = useForceUpdate();
|
||||
|
||||
const statusRef = useRef(null);
|
||||
statusRef.current = getStatus(value, isCompleted);
|
||||
|
||||
const intervalRef = useRef(null);
|
||||
|
||||
const dateFormat = getDateFormat(
|
||||
value,
|
||||
@@ -35,11 +82,34 @@ const DueDate = React.memo(({ value, size, isDisabled, onClick }) => {
|
||||
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 = (
|
||||
<span
|
||||
className={classNames(
|
||||
styles.wrapper,
|
||||
styles[`wrapper${upperFirst(size)}`],
|
||||
!withStatusIcon && statusRef.current && styles[`wrapper${upperFirst(statusRef.current)}`],
|
||||
onClick && styles.wrapperHoverable,
|
||||
)}
|
||||
>
|
||||
@@ -47,6 +117,10 @@ const DueDate = React.memo(({ value, size, isDisabled, onClick }) => {
|
||||
value,
|
||||
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>
|
||||
);
|
||||
|
||||
@@ -62,14 +136,19 @@ const DueDate = React.memo(({ value, size, isDisabled, onClick }) => {
|
||||
DueDate.propTypes = {
|
||||
value: PropTypes.instanceOf(Date).isRequired,
|
||||
size: PropTypes.oneOf(Object.values(SIZES)),
|
||||
isCompleted: PropTypes.bool.isRequired,
|
||||
isDisabled: PropTypes.bool,
|
||||
withStatusIcon: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
onCompletionToggle: PropTypes.func,
|
||||
};
|
||||
|
||||
DueDate.defaultProps = {
|
||||
size: SIZES.MEDIUM,
|
||||
isDisabled: false,
|
||||
withStatusIcon: false,
|
||||
onClick: undefined,
|
||||
onCompletionToggle: undefined,
|
||||
};
|
||||
|
||||
export default DueDate;
|
||||
|
||||
@@ -8,16 +8,17 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.statusIcon {
|
||||
line-height: 1;
|
||||
margin: 0 0 0 8px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
background: #dce0e4;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
color: #6a808b;
|
||||
display: inline-block;
|
||||
outline: none;
|
||||
text-align: left;
|
||||
transition: background 0.3s ease;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.wrapperHoverable:hover {
|
||||
@@ -43,4 +44,21 @@
|
||||
line-height: 20px;
|
||||
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) {
|
||||
.wrapper {
|
||||
max-width: 100vw;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
|
||||
@@ -87,7 +87,12 @@
|
||||
}
|
||||
|
||||
.userName {
|
||||
display: none;
|
||||
margin-right: 10px;
|
||||
|
||||
@media only screen and (min-width: 797px) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
|
||||
@@ -17,7 +17,7 @@ const SIZES = {
|
||||
|
||||
const Label = React.memo(({ name, color, size, isDisabled, onClick }) => {
|
||||
const contentNode = (
|
||||
<div
|
||||
<span
|
||||
title={name}
|
||||
className={classNames(
|
||||
styles.wrapper,
|
||||
@@ -28,7 +28,7 @@ const Label = React.memo(({ name, color, size, isDisabled, onClick }) => {
|
||||
)}
|
||||
>
|
||||
{name || '\u00A0'}
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
|
||||
return onClick ? (
|
||||
|
||||
@@ -11,17 +11,17 @@
|
||||
|
||||
.wrapper {
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
font-weight: 400;
|
||||
outline: none;
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2);
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wrapperNameless{
|
||||
.wrapperNameless {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,12 @@
|
||||
&:hover .target {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 797px) {
|
||||
.target {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.headerEditable {
|
||||
@@ -103,6 +109,15 @@
|
||||
background: rgba(9, 30, 66, 0.13);
|
||||
color: #516b7a;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 797px) {
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
background: rgba(9, 30, 66, 0.13);
|
||||
color: #516b7a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.headerName {
|
||||
|
||||
@@ -4,6 +4,7 @@ import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { TextArea } from 'semantic-ui-react';
|
||||
|
||||
import { useField } from '../../hooks';
|
||||
import { focusEnd } from '../../utils/element-helpers';
|
||||
|
||||
import styles from './NameEdit.module.scss';
|
||||
|
||||
@@ -71,7 +72,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpened) {
|
||||
field.current.ref.current.select();
|
||||
focusEnd(field.current.ref.current);
|
||||
}
|
||||
}, [isOpened]);
|
||||
|
||||
|
||||
@@ -18,6 +18,11 @@ const createMessage = (error) => {
|
||||
}
|
||||
|
||||
switch (error.message) {
|
||||
case 'Invalid credentials':
|
||||
return {
|
||||
type: 'error',
|
||||
content: 'common.invalidCredentials',
|
||||
};
|
||||
case 'Invalid email or username':
|
||||
return {
|
||||
type: 'error',
|
||||
@@ -116,6 +121,7 @@ const Login = React.memo(
|
||||
useEffect(() => {
|
||||
if (wasSubmitting && !isSubmitting && error) {
|
||||
switch (error.message) {
|
||||
case 'Invalid credentials':
|
||||
case 'Invalid email or username':
|
||||
emailOrUsernameField.current.select();
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
vertical-align: top;
|
||||
width: 36px;
|
||||
|
||||
@media only screen and (max-width: 797px) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.32);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'react-datepicker/dist/react-datepicker.css';
|
||||
import 'photoswipe/dist/photoswipe.css';
|
||||
import 'easymde/dist/easymde.min.css';
|
||||
import '../lib/custom-ui/styles.css';
|
||||
import '../assets/css/font-awesome.css';
|
||||
import '../styles.module.scss';
|
||||
|
||||
function Root({ store, history }) {
|
||||
|
||||
@@ -10,13 +10,10 @@
|
||||
|
||||
.wrapper {
|
||||
background: #dce0e4;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
color: #6a808b;
|
||||
display: inline-block;
|
||||
font-variant-numeric: tabular-nums;
|
||||
outline: none;
|
||||
text-align: left;
|
||||
transition: background 0.3s ease;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
@@ -14,12 +14,10 @@
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
}
|
||||
@@ -32,7 +30,6 @@
|
||||
|
||||
.wrapperTiny {
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
height: 24px;
|
||||
line-height: 20px;
|
||||
padding: 2px 0;
|
||||
@@ -41,7 +38,6 @@
|
||||
|
||||
.wrapperSmall {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
height: 28px;
|
||||
padding: 8px 0;
|
||||
width: 28px;
|
||||
@@ -49,7 +45,6 @@
|
||||
|
||||
.wrapperMedium {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
height: 32px;
|
||||
padding: 10px 0;
|
||||
width: 32px;
|
||||
@@ -57,7 +52,6 @@
|
||||
|
||||
.wrapperLarge {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
height: 36px;
|
||||
padding: 12px 0 10px;
|
||||
width: 36px;
|
||||
@@ -65,7 +59,6 @@
|
||||
|
||||
.wrapperMassive {
|
||||
font-size: 36px;
|
||||
font-weight: 500;
|
||||
height: 100px;
|
||||
padding: 32px 0 10px;
|
||||
width: 100px;
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Image, Tab } from 'semantic-ui-react';
|
||||
|
||||
import Config from '../../constants/Config';
|
||||
import version from '../../version';
|
||||
|
||||
import logo from '../../assets/images/logo.png';
|
||||
|
||||
@@ -15,7 +15,7 @@ const AboutPane = React.memo(() => {
|
||||
<Tab.Pane attached={false} className={styles.wrapper}>
|
||||
<Image centered src={logo} size="large" />
|
||||
<div className={styles.version}>
|
||||
{t('common.version')} {Config.VERSION}
|
||||
{t('common.version')} {version}
|
||||
</div>
|
||||
</Tab.Pane>
|
||||
);
|
||||
|
||||
@@ -195,9 +195,6 @@ export default {
|
||||
CARD_UPDATE__SUCCESS: 'CARD_UPDATE__SUCCESS',
|
||||
CARD_UPDATE__FAILURE: 'CARD_UPDATE__FAILURE',
|
||||
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__SUCCESS: 'CARD_DUPLICATE__SUCCESS',
|
||||
CARD_DUPLICATE__FAILURE: 'CARD_DUPLICATE__FAILURE',
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
const VERSION = process.env.REACT_APP_VERSION;
|
||||
|
||||
const { BASE_URL } = window;
|
||||
|
||||
const BASE_PATH = BASE_URL.replace(/^.*\/\/[^/]*(.*)[^?#]*.*$/, '$1');
|
||||
|
||||
const SERVER_BASE_URL =
|
||||
process.env.REACT_APP_SERVER_BASE_URL ||
|
||||
(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 ACCESS_TOKEN_KEY = 'accessToken';
|
||||
@@ -17,9 +17,9 @@ const POSITION_GAP = 65535;
|
||||
const ACTIVITIES_LIMIT = 50;
|
||||
|
||||
export default {
|
||||
VERSION,
|
||||
BASE_PATH,
|
||||
SERVER_BASE_URL,
|
||||
SERVER_BASE_PATH,
|
||||
SERVER_HOST_NAME,
|
||||
ACCESS_TOKEN_KEY,
|
||||
ACCESS_TOKEN_VERSION_KEY,
|
||||
|
||||
@@ -20,10 +20,8 @@ const makeMapStateToProps = () => {
|
||||
const allLabels = selectors.selectLabelsForCurrentBoard(state);
|
||||
const currentUserMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
|
||||
|
||||
const { name, dueDate, stopwatch, coverUrl, boardId, listId, isPersisted } = selectCardById(
|
||||
state,
|
||||
id,
|
||||
);
|
||||
const { name, dueDate, isDueDateCompleted, stopwatch, coverUrl, boardId, listId, isPersisted } =
|
||||
selectCardById(state, id);
|
||||
|
||||
const users = selectUsersByCardId(state, id);
|
||||
const labels = selectLabelsByCardId(state, id);
|
||||
@@ -38,6 +36,7 @@ const makeMapStateToProps = () => {
|
||||
index,
|
||||
name,
|
||||
dueDate,
|
||||
isDueDateCompleted,
|
||||
stopwatch,
|
||||
coverUrl,
|
||||
boardId,
|
||||
|
||||
@@ -21,6 +21,7 @@ const mapStateToProps = (state) => {
|
||||
name,
|
||||
description,
|
||||
dueDate,
|
||||
isDueDateCompleted,
|
||||
stopwatch,
|
||||
isSubscribed,
|
||||
isActivitiesFetching,
|
||||
@@ -49,6 +50,7 @@ const mapStateToProps = (state) => {
|
||||
name,
|
||||
description,
|
||||
dueDate,
|
||||
isDueDateCompleted,
|
||||
stopwatch,
|
||||
isSubscribed,
|
||||
isActivitiesFetching,
|
||||
|
||||
@@ -6,12 +6,13 @@ import entryActions from '../entry-actions';
|
||||
import Projects from '../components/Projects';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { allowAllToCreateProjects } = selectors.selectConfig(state);
|
||||
const { isAdmin } = selectors.selectCurrentUser(state);
|
||||
const projects = selectors.selectProjectsForCurrentUser(state);
|
||||
|
||||
return {
|
||||
items: projects,
|
||||
canAdd: isAdmin,
|
||||
canAdd: allowAllToCreateProjects || isAdmin,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
+3
-3
@@ -58,9 +58,9 @@ i18n
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources: embeddedLocales,
|
||||
fallbackLng: 'en',
|
||||
fallbackLng: 'en-US',
|
||||
supportedLngs: languages,
|
||||
load: 'languageOnly',
|
||||
load: 'currentOnly',
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
format(value, format, language) {
|
||||
@@ -80,7 +80,7 @@ i18n
|
||||
});
|
||||
|
||||
i18n.loadCoreLocale = async (language = i18n.resolvedLanguage) => {
|
||||
if (language === 'en') {
|
||||
if (language === i18n.options.fallbackLng[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -64,7 +64,7 @@
|
||||
|
||||
.markdown-body b,
|
||||
.markdown-body strong {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
}
|
||||
|
||||
.markdown-body dfn {
|
||||
@@ -73,7 +73,7 @@
|
||||
|
||||
.markdown-body h1 {
|
||||
margin: .67em 0;
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
padding-bottom: .3em;
|
||||
font-size: 2em;
|
||||
border-bottom: 1px solid hsla(210, 18%, 87%, 1);
|
||||
@@ -253,34 +253,34 @@
|
||||
.markdown-body h6 {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
padding-bottom: .3em;
|
||||
font-size: 1.5em;
|
||||
border-bottom: 1px solid hsla(210, 18%, 87%, 1);
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.markdown-body h4 {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.markdown-body h5 {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
font-size: .875em;
|
||||
}
|
||||
|
||||
.markdown-body h6 {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
font-size: .85em;
|
||||
color: #57606a;
|
||||
}
|
||||
@@ -481,7 +481,7 @@
|
||||
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 1em;
|
||||
font-style: normal !important;
|
||||
font-weight: 400;
|
||||
font-weight: normal; // 400
|
||||
line-height: 1;
|
||||
vertical-align: -0.075em;
|
||||
}
|
||||
@@ -650,7 +650,7 @@
|
||||
margin-top: 16px;
|
||||
font-size: 1em;
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
}
|
||||
|
||||
.markdown-body dl dd {
|
||||
@@ -659,7 +659,7 @@
|
||||
}
|
||||
|
||||
.markdown-body table th {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
}
|
||||
|
||||
.markdown-body table th,
|
||||
@@ -868,7 +868,7 @@
|
||||
}
|
||||
|
||||
.markdown-body .csv-data th {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
background: #f6f8fa;
|
||||
border-top: 0;
|
||||
}
|
||||
@@ -912,7 +912,7 @@
|
||||
}
|
||||
|
||||
.markdown-body .task-list-item label {
|
||||
font-weight: 400;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.markdown-body .task-list-item.enabled label {
|
||||
|
||||
Vendored
+2568
-2729
File diff suppressed because one or more lines are too long
@@ -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
Reference in New Issue
Block a user