Compare commits
61
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 |
@@ -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?
|
||||
@@ -1 +1,2 @@
|
||||
#!/bin/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.33
|
||||
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.5"
|
||||
appVersion: "1.22.0"
|
||||
|
||||
dependencies:
|
||||
- alias: postgresql
|
||||
|
||||
@@ -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 }}
|
||||
@@ -90,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: ""
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
REACT_APP_VERSION=1.17.5
|
||||
Generated
+2055
-7110
File diff suppressed because it is too large
Load Diff
+12
-11
@@ -65,7 +65,7 @@
|
||||
"dequal": "^2.0.3",
|
||||
"easymde": "^2.18.0",
|
||||
"history": "^5.3.0",
|
||||
"i18next": "^23.11.5",
|
||||
"i18next": "^23.15.1",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"initials": "^3.1.2",
|
||||
"js-cookie": "^3.0.5",
|
||||
@@ -77,18 +77,18 @@
|
||||
"node-sass": "^9.0.0",
|
||||
"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": "^14.1.2",
|
||||
"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.23.1",
|
||||
"react-router-dom": "^6.26.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-simplemde-editor": "^5.2.0",
|
||||
"react-textarea-autosize": "^8.5.3",
|
||||
@@ -107,17 +107,18 @@
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^6.4.5",
|
||||
"@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.2",
|
||||
"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"
|
||||
"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) => ({
|
||||
|
||||
@@ -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 ? (
|
||||
<>
|
||||
<Checkbox
|
||||
checked={isDueDateCompleted}
|
||||
disabled={!canEdit}
|
||||
onChange={handleDueDateCompletionChange}
|
||||
/>
|
||||
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}>
|
||||
<DueDate value={dueDate} />
|
||||
<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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,13 +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_BASE_PATH = SERVER_BASE_URL.replace(/^.*\/\/[^/]*(.*)[^?#]*.*$/, '$1');
|
||||
const SERVER_HOST_NAME = SERVER_BASE_URL.replace(/^(.*\/\/[^/?#]*).*$/, '$1');
|
||||
|
||||
const ACCESS_TOKEN_KEY = 'accessToken';
|
||||
@@ -18,7 +17,6 @@ const POSITION_GAP = 65535;
|
||||
const ACTIVITIES_LIMIT = 50;
|
||||
|
||||
export default {
|
||||
VERSION,
|
||||
BASE_PATH,
|
||||
SERVER_BASE_URL,
|
||||
SERVER_BASE_PATH,
|
||||
|
||||
@@ -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
+2535
-2696
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
import login from './login';
|
||||
|
||||
export default {
|
||||
language: 'bg',
|
||||
language: 'bg-BG',
|
||||
country: 'bg',
|
||||
name: 'Български',
|
||||
embeddedLocale: login,
|
||||
@@ -1,7 +1,7 @@
|
||||
import login from './login';
|
||||
|
||||
export default {
|
||||
language: 'cs',
|
||||
language: 'cs-CZ',
|
||||
country: 'cz',
|
||||
name: 'Čeština',
|
||||
embeddedLocale: login,
|
||||
@@ -1,7 +1,7 @@
|
||||
import login from './login';
|
||||
|
||||
export default {
|
||||
language: 'da',
|
||||
language: 'da-DK',
|
||||
country: 'dk',
|
||||
name: 'Dansk',
|
||||
embeddedLocale: login,
|
||||
@@ -104,6 +104,7 @@ export default {
|
||||
language: 'Sprache',
|
||||
leaveBoard_title: 'Board verlassen',
|
||||
leaveProject_title: 'Projekt verlassen',
|
||||
linkIsCopied: 'Link kopiert',
|
||||
list: 'Listen',
|
||||
listActions_title: 'Aufgaben auflisten',
|
||||
managers: 'Manager',
|
||||
@@ -169,6 +170,7 @@ export default {
|
||||
addTask: 'Aufgabe hinzufügen',
|
||||
addToCard: 'Zu Karte hinzufügen',
|
||||
addUser: 'Benutzer hinzufügen',
|
||||
copyLink_title: 'Link kopieren',
|
||||
createBoard: 'Board erstellen',
|
||||
createFile: 'Datei erstellen',
|
||||
createLabel: 'Label erstellen',
|
||||
@@ -190,6 +192,8 @@ export default {
|
||||
deleteTask: 'Aufgabe löschen',
|
||||
deleteTask_title: 'Aufgabe löschen',
|
||||
deleteUser: 'Benutzer löschen',
|
||||
duplicate: 'Kopieren',
|
||||
duplicateCard_title: 'Karte kopieren',
|
||||
edit: 'Bearbeiten',
|
||||
editDueDate_title: 'Fälligkeitsdatum bearbeiten',
|
||||
editDescription_title: 'Beschreibung ändern',
|
||||
@@ -1,7 +1,7 @@
|
||||
import login from './login';
|
||||
|
||||
export default {
|
||||
language: 'de',
|
||||
language: 'de-DE',
|
||||
country: 'de',
|
||||
name: 'Deutsch',
|
||||
embeddedLocale: login,
|
||||
@@ -106,6 +106,7 @@ export default {
|
||||
language: 'Language',
|
||||
leaveBoard_title: 'Leave Board',
|
||||
leaveProject_title: 'Leave Project',
|
||||
linkIsCopied: 'Link is copied',
|
||||
list: 'List',
|
||||
listActions_title: 'List Actions',
|
||||
managers: 'Managers',
|
||||
@@ -183,6 +184,7 @@ export default {
|
||||
addTask: 'Add task',
|
||||
addToCard: 'Add to card',
|
||||
addUser: 'Add user',
|
||||
copyLink_title: 'Copy Link',
|
||||
createBoard: 'Create board',
|
||||
createFile: 'Create file',
|
||||
createLabel: 'Create label',
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user