Merge pull request #1685 from symonbaikov/codex/icon-button-tooltips
feat: add tooltips for icon buttons
Conflicts came from the branch predating the Pro banner work and the deleted
API key step. Resolved by keeping master's `onEdit` on the user actions popup
and wrapping it in the tooltip, and by leaving `ApiKeyStep.jsx` deleted.
The `switchToView` key is dropped in favour of one complete string per view.
Interpolating a view name into "Switch to {{view}} view" required
`t('common.' + view).toLowerCase()` at the call site, and lowercasing a
translated string is wrong wherever case carries meaning: German capitalises
nouns, and Turkish turns I into a dotless ı. The five replacements need no
interpolation and no lowercasing.
This commit is contained in:
@@ -9,6 +9,7 @@ import classNames from 'classnames';
|
|||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Icon, Label, Loader } from 'semantic-ui-react';
|
import { Button, Icon, Label, Loader } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import entryActions from '../../../entry-actions';
|
import entryActions from '../../../entry-actions';
|
||||||
@@ -161,9 +162,11 @@ const ItemContent = React.forwardRef(({ id, onOpen }, ref) => {
|
|||||||
</div>
|
</div>
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
<EditPopup attachmentId={id}>
|
<EditPopup attachmentId={id}>
|
||||||
|
<Tooltip content={t('action.editAttachment', { context: 'title' })}>
|
||||||
<Button className={styles.editButton}>
|
<Button className={styles.editButton}>
|
||||||
<Icon fitted name="pencil" size="small" />
|
<Icon fitted name="pencil" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</EditPopup>
|
</EditPopup>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+5
@@ -8,7 +8,9 @@ import ReactDOM from 'react-dom';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Draggable } from 'react-beautiful-dnd';
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Icon } from 'semantic-ui-react';
|
import { Button, Icon } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
|
|
||||||
@@ -18,6 +20,7 @@ const CustomField = React.memo(({ id, index, onEdit }) => {
|
|||||||
const selectCustomFieldById = useMemo(() => selectors.makeSelectCustomFieldById(), []);
|
const selectCustomFieldById = useMemo(() => selectors.makeSelectCustomFieldById(), []);
|
||||||
|
|
||||||
const customField = useSelector((state) => selectCustomFieldById(state, id));
|
const customField = useSelector((state) => selectCustomFieldById(state, id));
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const handleEditClick = useCallback(() => {
|
const handleEditClick = useCallback(() => {
|
||||||
onEdit(id);
|
onEdit(id);
|
||||||
@@ -36,6 +39,7 @@ const CustomField = React.memo(({ id, index, onEdit }) => {
|
|||||||
{customField.showOnFrontOfCard && <Icon name="pin" className={styles.nameIcon} />}
|
{customField.showOnFrontOfCard && <Icon name="pin" className={styles.nameIcon} />}
|
||||||
{customField.name}
|
{customField.name}
|
||||||
</span>
|
</span>
|
||||||
|
<Tooltip content={t('action.edit', { context: 'title' })}>
|
||||||
<Button
|
<Button
|
||||||
icon="pencil"
|
icon="pencil"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -44,6 +48,7 @@ const CustomField = React.memo(({ id, index, onEdit }) => {
|
|||||||
className={styles.editButton}
|
className={styles.editButton}
|
||||||
onClick={handleEditClick}
|
onClick={handleEditClick}
|
||||||
/>
|
/>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import groupBy from 'lodash/groupBy';
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button } from 'semantic-ui-react';
|
import { Button } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
import { usePopup } from '../../../lib/popup';
|
import { usePopup } from '../../../lib/popup';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
@@ -20,6 +22,7 @@ import styles from './BoardMemberships.module.scss';
|
|||||||
|
|
||||||
const BoardMemberships = React.memo(() => {
|
const BoardMemberships = React.memo(() => {
|
||||||
const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard);
|
const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard);
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const canAdd = useSelector((state) => {
|
const canAdd = useSelector((state) => {
|
||||||
const user = selectors.selectCurrentUser(state);
|
const user = selectors.selectCurrentUser(state);
|
||||||
@@ -57,7 +60,9 @@ const BoardMemberships = React.memo(() => {
|
|||||||
)}
|
)}
|
||||||
{canAdd && (
|
{canAdd && (
|
||||||
<AddPopup>
|
<AddPopup>
|
||||||
|
<Tooltip content={t('action.addMember')}>
|
||||||
<Button icon="add user" className={classNames(styles.segment, styles.addButton)} />
|
<Button icon="add user" className={classNames(styles.segment, styles.addButton)} />
|
||||||
|
</Tooltip>
|
||||||
</AddPopup>
|
</AddPopup>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useInView } from 'react-intersection-observer';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Icon, Loader } from 'semantic-ui-react';
|
import { Button, Icon, Loader } from 'semantic-ui-react';
|
||||||
import { useWindowWidth } from '../../../lib/hooks';
|
import { useWindowWidth } from '../../../lib/hooks';
|
||||||
import { Masonry } from '../../../lib/custom-ui';
|
import { Masonry, Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import { BoardMembershipRoles } from '../../../constants/Enums';
|
import { BoardMembershipRoles } from '../../../constants/Enums';
|
||||||
@@ -81,6 +81,7 @@ const GridView = React.memo(
|
|||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
{onCardPaste && clipboard && canPasteCard && (
|
{onCardPaste && clipboard && canPasteCard && (
|
||||||
|
<Tooltip content={t('action.paste')}>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={!onCardCreate}
|
disabled={!onCardCreate}
|
||||||
@@ -89,6 +90,7 @@ const GridView = React.memo(
|
|||||||
>
|
>
|
||||||
<Icon fitted name="paste" />
|
<Icon fitted name="paste" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { shallowEqual, useSelector } from 'react-redux';
|
|||||||
import { useInView } from 'react-intersection-observer';
|
import { useInView } from 'react-intersection-observer';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Icon, Loader } from 'semantic-ui-react';
|
import { Button, Icon, Loader } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import { BoardMembershipRoles } from '../../../constants/Enums';
|
import { BoardMembershipRoles } from '../../../constants/Enums';
|
||||||
@@ -74,6 +75,7 @@ const ListView = React.memo(
|
|||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
{onCardPaste && clipboard && canPasteCard && (
|
{onCardPaste && clipboard && canPasteCard && (
|
||||||
|
<Tooltip content={t('action.paste')}>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={!onCardCreate}
|
disabled={!onCardCreate}
|
||||||
@@ -82,6 +84,7 @@ const ListView = React.memo(
|
|||||||
>
|
>
|
||||||
<Icon fitted name="paste" />
|
<Icon fitted name="paste" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Icon } from 'semantic-ui-react';
|
import { Icon } from 'semantic-ui-react';
|
||||||
import { useDidUpdate } from '../../../lib/hooks';
|
import { useDidUpdate } from '../../../lib/hooks';
|
||||||
import { usePopup } from '../../../lib/popup';
|
import { usePopup } from '../../../lib/popup';
|
||||||
import { Input } from '../../../lib/custom-ui';
|
import { Input, Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import entryActions from '../../../entry-actions';
|
import entryActions from '../../../entry-actions';
|
||||||
@@ -162,11 +162,13 @@ const Filters = React.memo(() => {
|
|||||||
</button>
|
</button>
|
||||||
</BoardMembershipsPopup>
|
</BoardMembershipsPopup>
|
||||||
{userIds.length === 0 && withCurrentUserSelector && (
|
{userIds.length === 0 && withCurrentUserSelector && (
|
||||||
|
<Tooltip content={t('action.filterByCurrentUser')}>
|
||||||
<button type="button" className={styles.filterButton} onClick={handleCurrentUserSelect}>
|
<button type="button" className={styles.filterButton} onClick={handleCurrentUserSelect}>
|
||||||
<span className={styles.filterLabel}>
|
<span className={styles.filterLabel}>
|
||||||
<Icon fitted name="target" className={styles.filterLabelIcon} />
|
<Icon fitted name="target" className={styles.filterLabelIcon} />
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{userIds.map((userId) => (
|
{userIds.map((userId) => (
|
||||||
<span key={userId} className={styles.filterItem}>
|
<span key={userId} className={styles.filterItem}>
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Icon } from 'semantic-ui-react';
|
import { Icon } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../../lib/custom-ui';
|
||||||
import { usePopup } from '../../../../lib/popup';
|
import { usePopup } from '../../../../lib/popup';
|
||||||
|
|
||||||
import selectors from '../../../../selectors';
|
import selectors from '../../../../selectors';
|
||||||
@@ -16,10 +18,20 @@ import ActionsStep from './ActionsStep';
|
|||||||
|
|
||||||
import styles from './RightSide.module.scss';
|
import styles from './RightSide.module.scss';
|
||||||
|
|
||||||
|
// One complete sentence per view rather than a view name interpolated into a
|
||||||
|
// frame. A name that has to fit a slot has to be inflected to fit it, and the
|
||||||
|
// frame is not the same shape in every language.
|
||||||
|
const TOOLTIP_BY_VIEW = {
|
||||||
|
[BoardViews.KANBAN]: 'action.switchToKanbanView',
|
||||||
|
[BoardViews.GRID]: 'action.switchToGridView',
|
||||||
|
[BoardViews.LIST]: 'action.switchToListView',
|
||||||
|
};
|
||||||
|
|
||||||
const RightSide = React.memo(() => {
|
const RightSide = React.memo(() => {
|
||||||
const board = useSelector(selectors.selectCurrentBoard);
|
const board = useSelector(selectors.selectCurrentBoard);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const handleSelectViewClick = useCallback(
|
const handleSelectViewClick = useCallback(
|
||||||
({ currentTarget: { value: view } }) => {
|
({ currentTarget: { value: view } }) => {
|
||||||
@@ -40,8 +52,8 @@ const RightSide = React.memo(() => {
|
|||||||
<div className={styles.action}>
|
<div className={styles.action}>
|
||||||
<div className={styles.buttonGroup}>
|
<div className={styles.buttonGroup}>
|
||||||
{views.map((view) => (
|
{views.map((view) => (
|
||||||
|
<Tooltip key={view} content={t(TOOLTIP_BY_VIEW[view])} disabled={view === board.view}>
|
||||||
<button
|
<button
|
||||||
key={view}
|
|
||||||
type="button"
|
type="button"
|
||||||
value={view}
|
value={view}
|
||||||
disabled={view === board.view}
|
disabled={view === board.view}
|
||||||
@@ -50,14 +62,17 @@ const RightSide = React.memo(() => {
|
|||||||
>
|
>
|
||||||
<Icon fitted name={BoardViewIcons[view]} />
|
<Icon fitted name={BoardViewIcons[view]} />
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.action}>
|
<div className={styles.action}>
|
||||||
<ActionsPopup>
|
<ActionsPopup>
|
||||||
|
<Tooltip content={t('common.openBoardActions')}>
|
||||||
<button type="button" className={styles.button}>
|
<button type="button" className={styles.button}>
|
||||||
<Icon fitted name="ellipsis vertical" />
|
<Icon fitted name="ellipsis vertical" />
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</ActionsPopup>
|
</ActionsPopup>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
import React, { useCallback, useRef } from 'react';
|
import React, { useCallback, useRef } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button } from 'semantic-ui-react';
|
import { Button } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
import { closePopup, usePopup } from '../../../lib/popup';
|
import { closePopup, usePopup } from '../../../lib/popup';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
@@ -32,6 +34,7 @@ const Boards = React.memo(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const tabsWrapperRef = useRef(null);
|
const tabsWrapperRef = useRef(null);
|
||||||
|
|
||||||
@@ -75,7 +78,9 @@ const Boards = React.memo(() => {
|
|||||||
{placeholder}
|
{placeholder}
|
||||||
{canAdd && (
|
{canAdd && (
|
||||||
<AddBoardPopup>
|
<AddBoardPopup>
|
||||||
|
<Tooltip content={t('action.createBoard')}>
|
||||||
<Button icon="plus" className={styles.addButton} />
|
<Button icon="plus" className={styles.addButton} />
|
||||||
|
</Tooltip>
|
||||||
</AddBoardPopup>
|
</AddBoardPopup>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import classNames from 'classnames';
|
|||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import { Draggable } from 'react-beautiful-dnd';
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Icon } from 'semantic-ui-react';
|
import { Button, Icon } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import entryActions from '../../../entry-actions';
|
import entryActions from '../../../entry-actions';
|
||||||
@@ -40,6 +42,7 @@ const Item = React.memo(({ id, index }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const handleEditClick = useCallback(() => {
|
const handleEditClick = useCallback(() => {
|
||||||
dispatch(entryActions.openBoardSettingsModal(id));
|
dispatch(entryActions.openBoardSettingsModal(id));
|
||||||
@@ -64,9 +67,11 @@ const Item = React.memo(({ id, index }) => {
|
|||||||
<span className={styles.name}>{board.name}</span>
|
<span className={styles.name}>{board.name}</span>
|
||||||
</Link>
|
</Link>
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
|
<Tooltip content={t('action.edit', { context: 'title' })}>
|
||||||
<Button className={styles.editButton} onClick={handleEditClick}>
|
<Button className={styles.editButton} onClick={handleEditClick}>
|
||||||
<Icon fitted name="pencil" size="small" />
|
<Icon fitted name="pencil" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import React, { useCallback, useContext, useMemo, useRef, useState } from 'react
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Icon } from 'semantic-ui-react';
|
import { Button, Icon } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
import { push } from '../../../lib/redux-router';
|
import { push } from '../../../lib/redux-router';
|
||||||
import { closePopup, usePopup } from '../../../lib/popup';
|
import { closePopup, usePopup } from '../../../lib/popup';
|
||||||
|
|
||||||
@@ -57,6 +59,7 @@ const Card = React.memo(({ id, isInline }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const [t] = useTranslation();
|
||||||
const [isEditNameOpened, setIsEditNameOpened] = useState(false);
|
const [isEditNameOpened, setIsEditNameOpened] = useState(false);
|
||||||
const [, , handleCardMouseEnter, handleCardMouseLeave] = useContext(BoardShortcutsContext);
|
const [, , handleCardMouseEnter, handleCardMouseLeave] = useContext(BoardShortcutsContext);
|
||||||
|
|
||||||
@@ -151,9 +154,11 @@ const Card = React.memo(({ id, isInline }) => {
|
|||||||
</div>
|
</div>
|
||||||
{canUseActions && (
|
{canUseActions && (
|
||||||
<CardActionsPopup ref={actionsPopupRef} cardId={id} onNameEdit={handleNameEdit}>
|
<CardActionsPopup ref={actionsPopupRef} cardId={id} onNameEdit={handleNameEdit}>
|
||||||
|
<Tooltip content={t('common.openCardActions')}>
|
||||||
<Button className={styles.actionsButton}>
|
<Button className={styles.actionsButton}>
|
||||||
<Icon fitted name="pencil" size="small" />
|
<Icon fitted name="pencil" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</CardActionsPopup>
|
</CardActionsPopup>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Icon } from 'semantic-ui-react';
|
import { Icon } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
import { push } from '../../../lib/redux-router';
|
import { push } from '../../../lib/redux-router';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
@@ -45,6 +47,7 @@ const CardModal = React.memo(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
dispatch(push(Paths.BOARDS.replace(':id', card.boardId)));
|
dispatch(push(Paths.BOARDS.replace(':id', card.boardId)));
|
||||||
@@ -102,9 +105,11 @@ const CardModal = React.memo(() => {
|
|||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
>
|
>
|
||||||
{prevCardId && (
|
{prevCardId && (
|
||||||
|
<Tooltip content={t('action.goBack')} position="right center">
|
||||||
<button type="button" className={styles.prevButton} onClick={handlePrevClick}>
|
<button type="button" className={styles.prevButton} onClick={handlePrevClick}>
|
||||||
<Icon fitted name="arrow left" size="large" className={styles.prevButtonIcon} />
|
<Icon fitted name="arrow left" size="large" className={styles.prevButtonIcon} />
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{canEdit ? (
|
{canEdit ? (
|
||||||
<AddAttachmentZone>
|
<AddAttachmentZone>
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import React, { useMemo } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Icon } from 'semantic-ui-react';
|
import { Button, Icon } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../../selectors';
|
import selectors from '../../../../selectors';
|
||||||
import { usePopupInClosableContext } from '../../../../hooks';
|
import { usePopupInClosableContext } from '../../../../hooks';
|
||||||
@@ -23,6 +25,7 @@ const Item = React.memo(({ id, dragHandleProps }) => {
|
|||||||
const selectListById = useMemo(() => selectors.makeSelectListById(), []);
|
const selectListById = useMemo(() => selectors.makeSelectListById(), []);
|
||||||
|
|
||||||
const customFieldGroup = useSelector((state) => selectCustomFieldGroupById(state, id));
|
const customFieldGroup = useSelector((state) => selectCustomFieldGroupById(state, id));
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const canEdit = useSelector((state) => {
|
const canEdit = useSelector((state) => {
|
||||||
if (customFieldGroup.boardId) {
|
if (customFieldGroup.boardId) {
|
||||||
@@ -51,9 +54,11 @@ const Item = React.memo(({ id, dragHandleProps }) => {
|
|||||||
<div className={classNames(styles.moduleHeader, canEdit && styles.moduleHeaderEditable)}>
|
<div className={classNames(styles.moduleHeader, canEdit && styles.moduleHeaderEditable)}>
|
||||||
{customFieldGroup.isPersisted && canEdit && (
|
{customFieldGroup.isPersisted && canEdit && (
|
||||||
<CustomFieldGroupPopup id={customFieldGroup.id}>
|
<CustomFieldGroupPopup id={customFieldGroup.id}>
|
||||||
|
<Tooltip content={t('action.editGroup')}>
|
||||||
<Button className={styles.editButton}>
|
<Button className={styles.editButton}>
|
||||||
<Icon fitted name="pencil" size="small" />
|
<Icon fitted name="pencil" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</CustomFieldGroupPopup>
|
</CustomFieldGroupPopup>
|
||||||
)}
|
)}
|
||||||
<span className={styles.moduleHeaderTitle}>{customFieldGroup.name}</span>
|
<span className={styles.moduleHeaderTitle}>{customFieldGroup.name}</span>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Checkbox, Grid, Icon } from 'semantic-ui-react';
|
import { Button, Checkbox, Grid, Icon } from 'semantic-ui-react';
|
||||||
import { useDidUpdate } from '../../../lib/hooks';
|
import { useDidUpdate } from '../../../lib/hooks';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import entryActions from '../../../entry-actions';
|
import entryActions from '../../../entry-actions';
|
||||||
@@ -361,12 +362,14 @@ const ProjectContent = React.memo(() => {
|
|||||||
onUserSelect={handleUserSelect}
|
onUserSelect={handleUserSelect}
|
||||||
onUserDeselect={handleUserDeselect}
|
onUserDeselect={handleUserDeselect}
|
||||||
>
|
>
|
||||||
|
<Tooltip content={t('action.addMember')}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={classNames(styles.attachment, styles.dueDate)}
|
className={classNames(styles.attachment, styles.dueDate)}
|
||||||
>
|
>
|
||||||
<Icon name="add" size="small" className={styles.addAttachment} />
|
<Icon name="add" size="small" className={styles.addAttachment} />
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</BoardMembershipsPopup>
|
</BoardMembershipsPopup>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -401,12 +404,14 @@ const ProjectContent = React.memo(() => {
|
|||||||
onSelect={handleLabelSelect}
|
onSelect={handleLabelSelect}
|
||||||
onDeselect={handleLabelDeselect}
|
onDeselect={handleLabelDeselect}
|
||||||
>
|
>
|
||||||
|
<Tooltip content={t('action.addLabel')}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={classNames(styles.attachment, styles.dueDate)}
|
className={classNames(styles.attachment, styles.dueDate)}
|
||||||
>
|
>
|
||||||
<Icon name="add" size="small" className={styles.addAttachment} />
|
<Icon name="add" size="small" className={styles.addAttachment} />
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</LabelsPopup>
|
</LabelsPopup>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -465,6 +470,7 @@ const ProjectContent = React.memo(() => {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{canEditStopwatch && (
|
{canEditStopwatch && (
|
||||||
|
<Tooltip content={t(card.stopwatch.startedAt ? 'action.stop' : 'action.start')}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={classNames(styles.attachment, styles.dueDate)}
|
className={classNames(styles.attachment, styles.dueDate)}
|
||||||
@@ -476,6 +482,7 @@ const ProjectContent = React.memo(() => {
|
|||||||
className={styles.addAttachment}
|
className={styles.addAttachment}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -507,9 +514,11 @@ const ProjectContent = React.memo(() => {
|
|||||||
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
|
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
|
||||||
jsx-a11y/no-static-element-interactions */
|
jsx-a11y/no-static-element-interactions */
|
||||||
<div className={styles.cursorPointer} onClick={handleEditDescriptionClick}>
|
<div className={styles.cursorPointer} onClick={handleEditDescriptionClick}>
|
||||||
|
<Tooltip content={t('action.editDescription', { context: 'title' })}>
|
||||||
<Button className={styles.editButton}>
|
<Button className={styles.editButton}>
|
||||||
<Icon fitted name="pencil" size="small" />
|
<Icon fitted name="pencil" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
<ExpandableMarkdown>{card.description}</ExpandableMarkdown>
|
<ExpandableMarkdown>{card.description}</ExpandableMarkdown>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -554,6 +563,7 @@ const ProjectContent = React.memo(() => {
|
|||||||
<div className={classNames(styles.text, styles.textList)}>{t('common.list')}</div>
|
<div className={classNames(styles.text, styles.textList)}>{t('common.list')}</div>
|
||||||
{canUseLists ? (
|
{canUseLists ? (
|
||||||
<ListsPopup currentId={list.id} onSelect={handleListSelect}>
|
<ListsPopup currentId={list.id} onSelect={handleListSelect}>
|
||||||
|
<Tooltip content={t('common.selectList')}>
|
||||||
<button type="button" className={styles.listButton}>
|
<button type="button" className={styles.listButton}>
|
||||||
<span className={classNames(styles.list, styles.listHoverable)}>
|
<span className={classNames(styles.list, styles.listHoverable)}>
|
||||||
<Icon name="columns" size="small" className={styles.listIcon} />
|
<Icon name="columns" size="small" className={styles.listIcon} />
|
||||||
@@ -562,6 +572,7 @@ const ProjectContent = React.memo(() => {
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</ListsPopup>
|
</ListsPopup>
|
||||||
) : (
|
) : (
|
||||||
<span className={styles.list}>
|
<span className={styles.list}>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Gallery, Item as GalleryItem } from 'react-photoswipe-gallery';
|
import { Gallery, Item as GalleryItem } from 'react-photoswipe-gallery';
|
||||||
import { Button, Grid, Icon } from 'semantic-ui-react';
|
import { Button, Grid, Icon } from 'semantic-ui-react';
|
||||||
import { useDidUpdate } from '../../../lib/hooks';
|
import { useDidUpdate } from '../../../lib/hooks';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import entryActions from '../../../entry-actions';
|
import entryActions from '../../../entry-actions';
|
||||||
@@ -383,12 +384,14 @@ const StoryContent = React.memo(() => {
|
|||||||
onSelect={handleLabelSelect}
|
onSelect={handleLabelSelect}
|
||||||
onDeselect={handleLabelDeselect}
|
onDeselect={handleLabelDeselect}
|
||||||
>
|
>
|
||||||
|
<Tooltip content={t('action.addLabel')}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={classNames(styles.attachment, styles.dueDate)}
|
className={classNames(styles.attachment, styles.dueDate)}
|
||||||
>
|
>
|
||||||
<Icon name="add" size="small" className={styles.addAttachment} />
|
<Icon name="add" size="small" className={styles.addAttachment} />
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</LabelsPopup>
|
</LabelsPopup>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -419,9 +422,11 @@ const StoryContent = React.memo(() => {
|
|||||||
className={classNames(styles.descriptionText, styles.cursorPointer)}
|
className={classNames(styles.descriptionText, styles.cursorPointer)}
|
||||||
onClick={handleEditDescriptionClick}
|
onClick={handleEditDescriptionClick}
|
||||||
>
|
>
|
||||||
|
<Tooltip content={t('action.editDescription', { context: 'title' })}>
|
||||||
<Button className={styles.editButton}>
|
<Button className={styles.editButton}>
|
||||||
<Icon fitted name="pencil" size="small" />
|
<Icon fitted name="pencil" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
<Markdown>{card.description}</Markdown>
|
<Markdown>{card.description}</Markdown>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -477,6 +482,7 @@ const StoryContent = React.memo(() => {
|
|||||||
<div className={classNames(styles.text, styles.textList)}>{t('common.list')}</div>
|
<div className={classNames(styles.text, styles.textList)}>{t('common.list')}</div>
|
||||||
{canUseLists ? (
|
{canUseLists ? (
|
||||||
<ListsPopup currentId={list.id} onSelect={handleListSelect}>
|
<ListsPopup currentId={list.id} onSelect={handleListSelect}>
|
||||||
|
<Tooltip content={t('common.selectList')}>
|
||||||
<button type="button" className={styles.listButton}>
|
<button type="button" className={styles.listButton}>
|
||||||
<span className={classNames(styles.list, styles.listHoverable)}>
|
<span className={classNames(styles.list, styles.listHoverable)}>
|
||||||
<Icon name="columns" size="small" className={styles.listIcon} />
|
<Icon name="columns" size="small" className={styles.listIcon} />
|
||||||
@@ -485,6 +491,7 @@ const StoryContent = React.memo(() => {
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</ListsPopup>
|
</ListsPopup>
|
||||||
) : (
|
) : (
|
||||||
<span className={styles.list}>
|
<span className={styles.list}>
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import PropTypes from 'prop-types';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Draggable } from 'react-beautiful-dnd';
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Icon } from 'semantic-ui-react';
|
import { Button, Icon } from 'semantic-ui-react';
|
||||||
import { useToggle } from '../../../../lib/hooks';
|
import { useToggle } from '../../../../lib/hooks';
|
||||||
|
import { Tooltip } from '../../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../../selectors';
|
import selectors from '../../../../selectors';
|
||||||
import { usePopupInClosableContext } from '../../../../hooks';
|
import { usePopupInClosableContext } from '../../../../hooks';
|
||||||
@@ -24,6 +26,7 @@ const Item = React.memo(({ id, index }) => {
|
|||||||
const selectTaskListById = useMemo(() => selectors.makeSelectTaskListById(), []);
|
const selectTaskListById = useMemo(() => selectors.makeSelectTaskListById(), []);
|
||||||
|
|
||||||
const taskList = useSelector((state) => selectTaskListById(state, id));
|
const taskList = useSelector((state) => selectTaskListById(state, id));
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const canEdit = useSelector((state) => {
|
const canEdit = useSelector((state) => {
|
||||||
const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
|
const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
|
||||||
@@ -67,6 +70,11 @@ const Item = React.memo(({ id, index }) => {
|
|||||||
{taskList.isPersisted && withActions && (
|
{taskList.isPersisted && withActions && (
|
||||||
<div className={classNames(styles.actions)}>
|
<div className={classNames(styles.actions)}>
|
||||||
{taskList.hideCompletedTasks && (
|
{taskList.hideCompletedTasks && (
|
||||||
|
<Tooltip
|
||||||
|
content={t(
|
||||||
|
isCompletedVisible ? 'common.hideCompletedTasks' : 'action.showActive',
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
onClick={handleToggleCompletedVisibleClick}
|
onClick={handleToggleCompletedVisibleClick}
|
||||||
@@ -77,12 +85,15 @@ const Item = React.memo(({ id, index }) => {
|
|||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
<EditPopup taskListId={taskList.id}>
|
<EditPopup taskListId={taskList.id}>
|
||||||
|
<Tooltip content={t('action.edit', { context: 'title' })}>
|
||||||
<Button className={styles.button}>
|
<Button className={styles.button}>
|
||||||
<Icon fitted name="pencil" size="small" />
|
<Icon fitted name="pencil" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</EditPopup>
|
</EditPopup>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useDispatch, useSelector } from 'react-redux';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Icon, Table } from 'semantic-ui-react';
|
import { Button, Icon, Table } from 'semantic-ui-react';
|
||||||
import { useEventCallback } from '../../../../lib/hooks';
|
import { useEventCallback } from '../../../../lib/hooks';
|
||||||
|
import { Tooltip } from '../../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../../selectors';
|
import selectors from '../../../../selectors';
|
||||||
import entryActions from '../../../../entry-actions';
|
import entryActions from '../../../../entry-actions';
|
||||||
@@ -92,9 +93,11 @@ const Item = React.memo(({ id, onEdit }) => {
|
|||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell textAlign="right">
|
<Table.Cell textAlign="right">
|
||||||
<ActionsPopup userId={id} onEdit={onEdit}>
|
<ActionsPopup userId={id} onEdit={onEdit}>
|
||||||
|
<Tooltip content={t('common.userActions', { context: 'title' })}>
|
||||||
<Button className={styles.button}>
|
<Button className={styles.button}>
|
||||||
<Icon fitted name="pencil" />
|
<Icon fitted name="pencil" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</ActionsPopup>
|
</ActionsPopup>
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import React, { useCallback } from 'react';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Icon, Menu } from 'semantic-ui-react';
|
import { Button, Icon, Menu } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
import { usePopup } from '../../../lib/popup';
|
import { usePopup } from '../../../lib/popup';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
@@ -72,6 +74,7 @@ const Header = React.memo(() => {
|
|||||||
}, shallowEqual);
|
}, shallowEqual);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const handleToggleFavoritesClick = useCallback(() => {
|
const handleToggleFavoritesClick = useCallback(() => {
|
||||||
dispatch(entryActions.toggleFavorites(!isFavoritesEnabled));
|
dispatch(entryActions.toggleFavorites(!isFavoritesEnabled));
|
||||||
@@ -102,6 +105,7 @@ const Header = React.memo(() => {
|
|||||||
<Menu inverted size="large" className={styles.menu}>
|
<Menu inverted size="large" className={styles.menu}>
|
||||||
{project && (
|
{project && (
|
||||||
<Menu.Menu position="left">
|
<Menu.Menu position="left">
|
||||||
|
<Tooltip content={t('action.returnToProjects')}>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
as={Link}
|
as={Link}
|
||||||
to={Paths.ROOT}
|
to={Paths.ROOT}
|
||||||
@@ -109,18 +113,24 @@ const Header = React.memo(() => {
|
|||||||
>
|
>
|
||||||
<Icon fitted name="arrow left" />
|
<Icon fitted name="arrow left" />
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
</Tooltip>
|
||||||
<Menu.Item className={classNames(styles.item, styles.title)}>
|
<Menu.Item className={classNames(styles.item, styles.title)}>
|
||||||
{project.name}
|
{project.name}
|
||||||
{canEditProject && (
|
{canEditProject && (
|
||||||
|
<Tooltip content={t('common.openProjectSettings')}>
|
||||||
<Button className={styles.editButton} onClick={handleProjectSettingsClick}>
|
<Button className={styles.editButton} onClick={handleProjectSettingsClick}>
|
||||||
<Icon fitted name="pencil" size="small" />
|
<Icon fitted name="pencil" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu.Menu>
|
</Menu.Menu>
|
||||||
)}
|
)}
|
||||||
<Menu.Menu position="right">
|
<Menu.Menu position="right">
|
||||||
{withFavoritesToggler && (
|
{withFavoritesToggler && (
|
||||||
|
<Tooltip
|
||||||
|
content={t(isFavoritesEnabled ? 'action.hideProjectList' : 'action.showProjectList')}
|
||||||
|
>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
className={classNames(styles.item, styles.itemHoverable)}
|
className={classNames(styles.item, styles.itemHoverable)}
|
||||||
onClick={handleToggleFavoritesClick}
|
onClick={handleToggleFavoritesClick}
|
||||||
@@ -131,8 +141,12 @@ const Header = React.memo(() => {
|
|||||||
className={classNames(isFavoritesEnabled && styles.itemIconEnabled)}
|
className={classNames(isFavoritesEnabled && styles.itemIconEnabled)}
|
||||||
/>
|
/>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{withEditModeToggler && (
|
{withEditModeToggler && (
|
||||||
|
<Tooltip
|
||||||
|
content={t(isEditModeEnabled ? 'action.disableEditMode' : 'action.enableEditMode')}
|
||||||
|
>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
className={classNames(styles.item, styles.itemHoverable)}
|
className={classNames(styles.item, styles.itemHoverable)}
|
||||||
onClick={handleToggleEditModeClick}
|
onClick={handleToggleEditModeClick}
|
||||||
@@ -143,14 +157,17 @@ const Header = React.memo(() => {
|
|||||||
className={classNames(isEditModeEnabled && styles.itemIconEnabled)}
|
className={classNames(isEditModeEnabled && styles.itemIconEnabled)}
|
||||||
/>
|
/>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<NotificationsPopup>
|
<NotificationsPopup>
|
||||||
|
<Tooltip content={t('common.openNotifications')}>
|
||||||
<Menu.Item className={classNames(styles.item, styles.itemHoverable)}>
|
<Menu.Item className={classNames(styles.item, styles.itemHoverable)}>
|
||||||
<Icon fitted name="bell" />
|
<Icon fitted name="bell" />
|
||||||
{notificationIds.length > 0 && (
|
{notificationIds.length > 0 && (
|
||||||
<span className={styles.notification}>{notificationIds.length}</span>
|
<span className={styles.notification}>{notificationIds.length}</span>
|
||||||
)}
|
)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
</Tooltip>
|
||||||
</NotificationsPopup>
|
</NotificationsPopup>
|
||||||
<UserActionsPopup>
|
<UserActionsPopup>
|
||||||
<Menu.Item className={classNames(styles.item, styles.itemHoverable)}>
|
<Menu.Item className={classNames(styles.item, styles.itemHoverable)}>
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Icon } from 'semantic-ui-react';
|
import { Icon } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../../lib/custom-ui';
|
||||||
import { usePopup } from '../../../../lib/popup';
|
import { usePopup } from '../../../../lib/popup';
|
||||||
|
|
||||||
import selectors from '../../../../selectors';
|
import selectors from '../../../../selectors';
|
||||||
@@ -17,12 +19,18 @@ import SelectOrderStep from './SelectOrderStep';
|
|||||||
|
|
||||||
import styles from './RightSide.module.scss';
|
import styles from './RightSide.module.scss';
|
||||||
|
|
||||||
|
const TOOLTIP_BY_VIEW = {
|
||||||
|
[HomeViews.GRID_PROJECTS]: 'action.switchToGridProjectsView',
|
||||||
|
[HomeViews.GROUPED_PROJECTS]: 'action.switchToGroupedProjectsView',
|
||||||
|
};
|
||||||
|
|
||||||
const RightSide = React.memo(() => {
|
const RightSide = React.memo(() => {
|
||||||
const currentView = useSelector(selectors.selectHomeView); // TODO: rename?
|
const currentView = useSelector(selectors.selectHomeView); // TODO: rename?
|
||||||
const currentOrder = useSelector(selectors.selectProjectsOrder); // TODO: rename?
|
const currentOrder = useSelector(selectors.selectProjectsOrder); // TODO: rename?
|
||||||
const isHiddenVisible = useSelector(selectors.selectIsHiddenProjectsVisible);
|
const isHiddenVisible = useSelector(selectors.selectIsHiddenProjectsVisible);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const handleSelectViewClick = useCallback(
|
const handleSelectViewClick = useCallback(
|
||||||
({ currentTarget: { value: view } }) => {
|
({ currentTarget: { value: view } }) => {
|
||||||
@@ -47,6 +55,9 @@ const RightSide = React.memo(() => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.action}>
|
<div className={styles.action}>
|
||||||
|
<Tooltip
|
||||||
|
content={t(isHiddenVisible ? 'action.hideHiddenProjects' : 'common.showHiddenProjects')}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={classNames(styles.button)}
|
className={classNames(styles.button)}
|
||||||
@@ -54,19 +65,22 @@ const RightSide = React.memo(() => {
|
|||||||
>
|
>
|
||||||
<Icon fitted name={isHiddenVisible ? 'eye slash' : 'eye'} />
|
<Icon fitted name={isHiddenVisible ? 'eye slash' : 'eye'} />
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.action}>
|
<div className={styles.action}>
|
||||||
<SelectOrderPopup value={currentOrder} onSelect={handleOrderSelect}>
|
<SelectOrderPopup value={currentOrder} onSelect={handleOrderSelect}>
|
||||||
|
<Tooltip content={t('common.selectOrder', { context: 'title' })}>
|
||||||
<button type="button" className={styles.button}>
|
<button type="button" className={styles.button}>
|
||||||
<Icon fitted name={ProjectOrderIcons[currentOrder]} />
|
<Icon fitted name={ProjectOrderIcons[currentOrder]} />
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</SelectOrderPopup>
|
</SelectOrderPopup>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.action}>
|
<div className={styles.action}>
|
||||||
<div className={styles.buttonGroup}>
|
<div className={styles.buttonGroup}>
|
||||||
{[HomeViews.GRID_PROJECTS, HomeViews.GROUPED_PROJECTS].map((view) => (
|
{[HomeViews.GRID_PROJECTS, HomeViews.GROUPED_PROJECTS].map((view) => (
|
||||||
|
<Tooltip key={view} content={t(TOOLTIP_BY_VIEW[view])} disabled={view === currentView}>
|
||||||
<button
|
<button
|
||||||
key={view}
|
|
||||||
type="button"
|
type="button"
|
||||||
value={view}
|
value={view}
|
||||||
disabled={view === currentView}
|
disabled={view === currentView}
|
||||||
@@ -75,6 +89,7 @@ const RightSide = React.memo(() => {
|
|||||||
>
|
>
|
||||||
<Icon fitted name={HomeViewIcons[view]} />
|
<Icon fitted name={HomeViewIcons[view]} />
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import ReactDOM from 'react-dom';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Draggable } from 'react-beautiful-dnd';
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Icon } from 'semantic-ui-react';
|
import { Button, Icon } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
|
|
||||||
@@ -18,6 +20,7 @@ const CustomField = React.memo(({ id, index, onEdit }) => {
|
|||||||
const selectCustomFieldById = useMemo(() => selectors.makeSelectCustomFieldById(), []);
|
const selectCustomFieldById = useMemo(() => selectors.makeSelectCustomFieldById(), []);
|
||||||
|
|
||||||
const customField = useSelector((state) => selectCustomFieldById(state, id));
|
const customField = useSelector((state) => selectCustomFieldById(state, id));
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const handleEditClick = useCallback(() => {
|
const handleEditClick = useCallback(() => {
|
||||||
onEdit(id);
|
onEdit(id);
|
||||||
@@ -36,6 +39,7 @@ const CustomField = React.memo(({ id, index, onEdit }) => {
|
|||||||
{customField.showOnFrontOfCard && <Icon name="pin" className={styles.nameIcon} />}
|
{customField.showOnFrontOfCard && <Icon name="pin" className={styles.nameIcon} />}
|
||||||
{customField.name}
|
{customField.name}
|
||||||
</span>
|
</span>
|
||||||
|
<Tooltip content={t('action.edit', { context: 'title' })}>
|
||||||
<Button
|
<Button
|
||||||
icon="pencil"
|
icon="pencil"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -44,6 +48,7 @@ const CustomField = React.memo(({ id, index, onEdit }) => {
|
|||||||
className={styles.editButton}
|
className={styles.editButton}
|
||||||
onClick={handleEditClick}
|
onClick={handleEditClick}
|
||||||
/>
|
/>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import ReactDOM from 'react-dom';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Draggable } from 'react-beautiful-dnd';
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button } from 'semantic-ui-react';
|
import { Button } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
|
|
||||||
@@ -18,6 +20,7 @@ const Item = React.memo(({ id, index, onEdit }) => {
|
|||||||
const selectCustomFieldGroupById = useMemo(() => selectors.makeSelectCustomFieldGroupById(), []);
|
const selectCustomFieldGroupById = useMemo(() => selectors.makeSelectCustomFieldGroupById(), []);
|
||||||
|
|
||||||
const customFieldGroup = useSelector((state) => selectCustomFieldGroupById(state, id));
|
const customFieldGroup = useSelector((state) => selectCustomFieldGroupById(state, id));
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const handleEditClick = useCallback(() => {
|
const handleEditClick = useCallback(() => {
|
||||||
onEdit(id);
|
onEdit(id);
|
||||||
@@ -35,6 +38,7 @@ const Item = React.memo(({ id, index, onEdit }) => {
|
|||||||
>
|
>
|
||||||
{customFieldGroup.name}
|
{customFieldGroup.name}
|
||||||
</span>
|
</span>
|
||||||
|
<Tooltip content={t('action.editGroup')}>
|
||||||
<Button
|
<Button
|
||||||
icon="pencil"
|
icon="pencil"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -43,6 +47,7 @@ const Item = React.memo(({ id, index, onEdit }) => {
|
|||||||
className={styles.editButton}
|
className={styles.editButton}
|
||||||
onClick={handleEditClick}
|
onClick={handleEditClick}
|
||||||
/>
|
/>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Icon } from 'semantic-ui-react';
|
import { Button, Icon } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import entryActions from '../../../entry-actions';
|
import entryActions from '../../../entry-actions';
|
||||||
@@ -49,6 +51,7 @@ const CustomField = React.memo(({ id, customFieldGroupId }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const [t] = useTranslation();
|
||||||
const [isCopied, setIsCopied] = useState(false);
|
const [isCopied, setIsCopied] = useState(false);
|
||||||
|
|
||||||
const handleValueUpdate = useCallback(
|
const handleValueUpdate = useCallback(
|
||||||
@@ -95,9 +98,11 @@ const CustomField = React.memo(({ id, customFieldGroupId }) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{customFieldValue && customFieldValue.content && (
|
{customFieldValue && customFieldValue.content && (
|
||||||
|
<Tooltip content={t('action.copy')}>
|
||||||
<Button className={styles.copyButton} onClick={handleCopyClick}>
|
<Button className={styles.copyButton} onClick={handleCopyClick}>
|
||||||
<Icon fitted name={isCopied ? 'check' : 'copy'} />
|
<Icon fitted name={isCopied ? 'check' : 'copy'} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import PropTypes from 'prop-types';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Draggable } from 'react-beautiful-dnd';
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button } from 'semantic-ui-react';
|
import { Button } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import { BoardMembershipRoles } from '../../../constants/Enums';
|
import { BoardMembershipRoles } from '../../../constants/Enums';
|
||||||
@@ -23,6 +25,7 @@ const Item = React.memo(({ id, index, isActive, onSelect, onDeselect, onEdit })
|
|||||||
const selectLabelById = useMemo(() => selectors.makeSelectLabelById(), []);
|
const selectLabelById = useMemo(() => selectors.makeSelectLabelById(), []);
|
||||||
|
|
||||||
const label = useSelector((state) => selectLabelById(state, id));
|
const label = useSelector((state) => selectLabelById(state, id));
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const canEdit = useSelector((state) => {
|
const canEdit = useSelector((state) => {
|
||||||
const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
|
const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
|
||||||
@@ -63,6 +66,7 @@ const Item = React.memo(({ id, index, isActive, onSelect, onDeselect, onEdit })
|
|||||||
{label.name}
|
{label.name}
|
||||||
</span>
|
</span>
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
|
<Tooltip content={t('action.edit', { context: 'title' })}>
|
||||||
<Button
|
<Button
|
||||||
icon="pencil"
|
icon="pencil"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -71,6 +75,7 @@ const Item = React.memo(({ id, index, isActive, onSelect, onDeselect, onEdit })
|
|||||||
className={styles.editButton}
|
className={styles.editButton}
|
||||||
onClick={handleEditClick}
|
onClick={handleEditClick}
|
||||||
/>
|
/>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Draggable, Droppable } from 'react-beautiful-dnd';
|
import { Draggable, Droppable } from 'react-beautiful-dnd';
|
||||||
import { Button, Icon } from 'semantic-ui-react';
|
import { Button, Icon } from 'semantic-ui-react';
|
||||||
import { useDidUpdate, useToggle, useTransitioning } from '../../../lib/hooks';
|
import { useDidUpdate, useToggle, useTransitioning } from '../../../lib/hooks';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
import { usePopup } from '../../../lib/popup';
|
import { usePopup } from '../../../lib/popup';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
@@ -240,16 +241,20 @@ const List = React.memo(({ id, index }) => {
|
|||||||
{list.isPersisted &&
|
{list.isPersisted &&
|
||||||
(canEdit ? (
|
(canEdit ? (
|
||||||
<ActionsPopup listId={id} onNameEdit={handleNameEdit} onCardAdd={handleCardAdd}>
|
<ActionsPopup listId={id} onNameEdit={handleNameEdit} onCardAdd={handleCardAdd}>
|
||||||
|
<Tooltip content={t('common.openListActions')}>
|
||||||
<Button className={styles.headerButton}>
|
<Button className={styles.headerButton}>
|
||||||
<Icon fitted name="pencil" size="small" />
|
<Icon fitted name="pencil" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</ActionsPopup>
|
</ActionsPopup>
|
||||||
) : (
|
) : (
|
||||||
canArchiveCards && (
|
canArchiveCards && (
|
||||||
<ArchiveCardsPopup listId={id}>
|
<ArchiveCardsPopup listId={id}>
|
||||||
|
<Tooltip content={t('action.archiveCards')}>
|
||||||
<Button className={styles.headerButton}>
|
<Button className={styles.headerButton}>
|
||||||
<Icon fitted name="archive" size="small" />
|
<Icon fitted name="archive" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</ArchiveCardsPopup>
|
</ArchiveCardsPopup>
|
||||||
)
|
)
|
||||||
))}
|
))}
|
||||||
@@ -275,6 +280,7 @@ const List = React.memo(({ id, index }) => {
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
{clipboard && canPasteCard && (
|
{clipboard && canPasteCard && (
|
||||||
|
<Tooltip content={t('action.paste')}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={!list.isPersisted}
|
disabled={!list.isPersisted}
|
||||||
@@ -283,6 +289,7 @@ const List = React.memo(({ id, index }) => {
|
|||||||
>
|
>
|
||||||
<Icon name="paste" />
|
<Icon name="paste" />
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,8 +6,10 @@
|
|||||||
import React, { useCallback, useMemo, useRef } from 'react';
|
import React, { useCallback, useMemo, useRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Dropdown, Form, Icon, Input } from 'semantic-ui-react';
|
import { Button, Dropdown, Form, Icon, Input } from 'semantic-ui-react';
|
||||||
import { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';
|
import { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import entryActions from '../../../entry-actions';
|
import entryActions from '../../../entry-actions';
|
||||||
@@ -31,6 +33,7 @@ const Item = React.memo(({ id }) => {
|
|||||||
const notificationService = useSelector((state) => selectNotificationServiceById(state, id));
|
const notificationService = useSelector((state) => selectNotificationServiceById(state, id));
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const defaultData = useMemo(
|
const defaultData = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -183,6 +186,7 @@ const Item = React.memo(({ id }) => {
|
|||||||
onChange={handleFieldChange}
|
onChange={handleFieldChange}
|
||||||
onBlur={handleUrlBlur}
|
onBlur={handleUrlBlur}
|
||||||
/>
|
/>
|
||||||
|
<Tooltip content={t('action.sendTestEmail')}>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
loading={notificationService.isTesting}
|
loading={notificationService.isTesting}
|
||||||
@@ -192,15 +196,22 @@ const Item = React.memo(({ id }) => {
|
|||||||
>
|
>
|
||||||
<Icon fitted name="paper plane outline" />
|
<Icon fitted name="paper plane outline" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
<ConfirmationPopup
|
<ConfirmationPopup
|
||||||
title="common.deleteNotificationService"
|
title="common.deleteNotificationService"
|
||||||
content="common.areYouSureYouWantToDeleteThisNotificationService"
|
content="common.areYouSureYouWantToDeleteThisNotificationService"
|
||||||
buttonContent="action.deleteNotificationService"
|
buttonContent="action.deleteNotificationService"
|
||||||
onConfirm={handleDeleteConfirm}
|
onConfirm={handleDeleteConfirm}
|
||||||
>
|
>
|
||||||
<Button type="button" disabled={!notificationService.isPersisted} className={styles.button}>
|
<Tooltip content={t('action.deleteNotificationService')}>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
disabled={!notificationService.isPersisted}
|
||||||
|
className={styles.button}
|
||||||
|
>
|
||||||
<Icon fitted name="trash alternate outline" />
|
<Icon fitted name="trash alternate outline" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</ConfirmationPopup>
|
</ConfirmationPopup>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
+6
-1
@@ -6,9 +6,10 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Trans } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import { Button, Dropdown, Form, Icon, Input, Message } from 'semantic-ui-react';
|
import { Button, Dropdown, Form, Icon, Input, Message } from 'semantic-ui-react';
|
||||||
import { useDidUpdate, useToggle } from '../../../lib/hooks';
|
import { useDidUpdate, useToggle } from '../../../lib/hooks';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import { useEscapeInterceptor, useForm, useNestedRef } from '../../../hooks';
|
import { useEscapeInterceptor, useForm, useNestedRef } from '../../../hooks';
|
||||||
import { NotificationServiceFormats } from '../../../constants/Enums';
|
import { NotificationServiceFormats } from '../../../constants/Enums';
|
||||||
@@ -22,6 +23,8 @@ const DEFAULT_DATA = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const NotificationServices = React.memo(({ ids, onCreate }) => {
|
const NotificationServices = React.memo(({ ids, onCreate }) => {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
|
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
|
||||||
const [focusUrlFieldState, focusUrlField] = useToggle();
|
const [focusUrlFieldState, focusUrlField] = useToggle();
|
||||||
|
|
||||||
@@ -117,9 +120,11 @@ const NotificationServices = React.memo(({ ids, onCreate }) => {
|
|||||||
onChange={handleFieldChange}
|
onChange={handleFieldChange}
|
||||||
onBlur={handleUrlBlur}
|
onBlur={handleUrlBlur}
|
||||||
/>
|
/>
|
||||||
|
<Tooltip content={t('common.addNotificationService', { context: 'title' })}>
|
||||||
<Button positive className={styles.button}>
|
<Button positive className={styles.button}>
|
||||||
<Icon fitted name="plus" />
|
<Icon fitted name="plus" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button } from 'semantic-ui-react';
|
import { Button } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import { usePopupInClosableContext } from '../../../hooks';
|
import { usePopupInClosableContext } from '../../../hooks';
|
||||||
@@ -18,6 +20,7 @@ import styles from './ProjectManagers.module.scss';
|
|||||||
|
|
||||||
const ProjectManagers = React.memo(() => {
|
const ProjectManagers = React.memo(() => {
|
||||||
const projectManagers = useSelector(selectors.selectManagersForCurrentProject);
|
const projectManagers = useSelector(selectors.selectManagersForCurrentProject);
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const canAdd = useSelector((state) => {
|
const canAdd = useSelector((state) => {
|
||||||
const user = selectors.selectCurrentUser(state);
|
const user = selectors.selectCurrentUser(state);
|
||||||
@@ -47,7 +50,9 @@ const ProjectManagers = React.memo(() => {
|
|||||||
))}
|
))}
|
||||||
{canAdd && (
|
{canAdd && (
|
||||||
<AddPopup>
|
<AddPopup>
|
||||||
|
<Tooltip content={t('action.addMember')}>
|
||||||
<Button icon="add user" className={styles.addButton} />
|
<Button icon="add user" className={styles.addButton} />
|
||||||
|
</Tooltip>
|
||||||
</AddPopup>
|
</AddPopup>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Icon, Label } from 'semantic-ui-react';
|
import { Button, Icon, Label } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../../../../lib/custom-ui';
|
||||||
|
|
||||||
import { usePopupInClosableContext } from '../../../../hooks';
|
import { usePopupInClosableContext } from '../../../../hooks';
|
||||||
import ConfirmationStep from '../../../common/ConfirmationStep';
|
import ConfirmationStep from '../../../common/ConfirmationStep';
|
||||||
@@ -13,6 +15,8 @@ import ConfirmationStep from '../../../common/ConfirmationStep';
|
|||||||
import styles from './Image.module.scss';
|
import styles from './Image.module.scss';
|
||||||
|
|
||||||
const Image = React.memo(({ url, isActive, onSelect, onDeselect, onDelete }) => {
|
const Image = React.memo(({ url, isActive, onSelect, onDeselect, onDelete }) => {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
onDeselect();
|
onDeselect();
|
||||||
@@ -52,9 +56,11 @@ const Image = React.memo(({ url, isActive, onSelect, onDeselect, onDelete }) =>
|
|||||||
buttonContent="action.deleteBackgroundImage"
|
buttonContent="action.deleteBackgroundImage"
|
||||||
onConfirm={onDelete}
|
onConfirm={onDelete}
|
||||||
>
|
>
|
||||||
|
<Tooltip content={t('action.deleteBackgroundImage')}>
|
||||||
<Button className={styles.deleteButton}>
|
<Button className={styles.deleteButton}>
|
||||||
<Icon fitted name="trash alternate" size="small" />
|
<Icon fitted name="trash alternate" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</ConfirmationPopup>
|
</ConfirmationPopup>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import classNames from 'classnames';
|
|||||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import { Draggable } from 'react-beautiful-dnd';
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Checkbox, Icon } from 'semantic-ui-react';
|
import { Button, Checkbox, Icon } from 'semantic-ui-react';
|
||||||
import { useDidUpdate } from '../../../../lib/hooks';
|
import { useDidUpdate } from '../../../../lib/hooks';
|
||||||
|
import { Tooltip } from '../../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../../selectors';
|
import selectors from '../../../../selectors';
|
||||||
import entryActions from '../../../../entry-actions';
|
import entryActions from '../../../../entry-actions';
|
||||||
@@ -60,6 +62,7 @@ const Task = React.memo(({ id, index }) => {
|
|||||||
}, shallowEqual);
|
}, shallowEqual);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const [t] = useTranslation();
|
||||||
const [isEditNameOpened, setIsEditNameOpened] = useState(false);
|
const [isEditNameOpened, setIsEditNameOpened] = useState(false);
|
||||||
const [, , setIsClosableActive] = useContext(ClosableContext);
|
const [, , setIsClosableActive] = useContext(ClosableContext);
|
||||||
|
|
||||||
@@ -203,16 +206,20 @@ const Task = React.memo(({ id, index }) => {
|
|||||||
className={styles.assigneeUserAvatar}
|
className={styles.assigneeUserAvatar}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
<Tooltip content={t('common.selectAssignee', { context: 'title' })}>
|
||||||
<Button className={styles.button}>
|
<Button className={styles.button}>
|
||||||
<Icon fitted name="add user" size="small" />
|
<Icon fitted name="add user" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</SelectAssigneePopup>
|
</SelectAssigneePopup>
|
||||||
)}
|
)}
|
||||||
<ActionsPopup taskId={id} onNameEdit={handleNameEdit}>
|
<ActionsPopup taskId={id} onNameEdit={handleNameEdit}>
|
||||||
|
<Tooltip content={t('common.taskActions', { context: 'title' })}>
|
||||||
<Button className={styles.button}>
|
<Button className={styles.button}>
|
||||||
<Icon fitted name="pencil" size="small" />
|
<Icon fitted name="pencil" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</ActionsPopup>
|
</ActionsPopup>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Modal } from 'semantic-ui-react';
|
import { Modal } from 'semantic-ui-react';
|
||||||
|
|
||||||
import useClosable from './use-closable';
|
import useClosable from './use-closable';
|
||||||
@@ -22,6 +23,8 @@ export default (initialClosableValue) => {
|
|||||||
const ClosableModal = useMemo(() => {
|
const ClosableModal = useMemo(() => {
|
||||||
// eslint-disable-next-line no-shadow
|
// eslint-disable-next-line no-shadow
|
||||||
const ClosableModal = React.memo(({ closeIcon, onClose, ...props }) => {
|
const ClosableModal = React.memo(({ closeIcon, onClose, ...props }) => {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const handleClose = useCallback(
|
const handleClose = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
if (isClosableActiveRef.current) {
|
if (isClosableActiveRef.current) {
|
||||||
@@ -41,16 +44,32 @@ export default (initialClosableValue) => {
|
|||||||
[closeIcon, onClose],
|
[closeIcon, onClose],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const semanticCloseIcon =
|
||||||
|
closeIcon === true
|
||||||
|
? {
|
||||||
|
name: 'close',
|
||||||
|
'aria-label': t('action.close'),
|
||||||
|
title: t('action.close'),
|
||||||
|
}
|
||||||
|
: closeIcon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClosableContext.Provider value={closableContextValue}>
|
<ClosableContext.Provider value={closableContextValue}>
|
||||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||||
<Modal open {...props} closeIcon={closeIcon} onClose={handleClose} />
|
<Modal open {...props} closeIcon={semanticCloseIcon} onClose={handleClose} />
|
||||||
</ClosableContext.Provider>
|
</ClosableContext.Provider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ClosableModal.propTypes = {
|
ClosableModal.propTypes = {
|
||||||
closeIcon: PropTypes.bool,
|
closeIcon: PropTypes.oneOfType([
|
||||||
|
PropTypes.bool,
|
||||||
|
PropTypes.shape({
|
||||||
|
name: PropTypes.string,
|
||||||
|
'aria-label': PropTypes.string,
|
||||||
|
title: PropTypes.string,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
onClose: PropTypes.func,
|
onClose: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,16 +5,26 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Popup as SemanticUIPopup } from 'semantic-ui-react';
|
import { Button, Popup as SemanticUIPopup } from 'semantic-ui-react';
|
||||||
|
import Tooltip from '../Tooltip';
|
||||||
|
|
||||||
import styles from './PopupHeader.module.css';
|
import styles from './PopupHeader.module.css';
|
||||||
|
|
||||||
const PopupHeader = React.memo(({ children, onBack }) => (
|
const PopupHeader = React.memo(({ children, onBack }) => {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
<SemanticUIPopup.Header className={styles.wrapper}>
|
<SemanticUIPopup.Header className={styles.wrapper}>
|
||||||
{onBack && <Button icon="angle left" onClick={onBack} className={styles.backButton} />}
|
{onBack && (
|
||||||
|
<Tooltip content={t('action.goBack')}>
|
||||||
|
<Button icon="angle left" onClick={onBack} className={styles.backButton} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
<div className={styles.content}>{children}</div>
|
<div className={styles.content}>{children}</div>
|
||||||
</SemanticUIPopup.Header>
|
</SemanticUIPopup.Header>
|
||||||
));
|
);
|
||||||
|
});
|
||||||
|
|
||||||
PopupHeader.propTypes = {
|
PopupHeader.propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Popup as SemanticUIPopup } from 'semantic-ui-react';
|
||||||
|
|
||||||
|
import styles from './Tooltip.module.css';
|
||||||
|
|
||||||
|
const callAll =
|
||||||
|
(...callbacks) =>
|
||||||
|
(...args) => {
|
||||||
|
callbacks.forEach((callback) => {
|
||||||
|
if (callback) {
|
||||||
|
callback(...args);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const Tooltip = React.forwardRef(
|
||||||
|
({ children, content, disabled, position, 'aria-label': ariaLabel, onClick, ...props }, ref) => {
|
||||||
|
const trigger = useMemo(() => {
|
||||||
|
const contentAriaLabel = typeof content === 'string' ? content : undefined;
|
||||||
|
|
||||||
|
return React.cloneElement(children, {
|
||||||
|
...props,
|
||||||
|
ref,
|
||||||
|
'aria-label': children.props['aria-label'] || ariaLabel || contentAriaLabel,
|
||||||
|
onClick: callAll(children.props.onClick, onClick),
|
||||||
|
});
|
||||||
|
}, [children, content, props, ref, ariaLabel, onClick]);
|
||||||
|
|
||||||
|
if (disabled || !content) {
|
||||||
|
return trigger;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SemanticUIPopup
|
||||||
|
basic
|
||||||
|
inverted
|
||||||
|
trigger={trigger}
|
||||||
|
content={content}
|
||||||
|
on={['hover', 'focus']}
|
||||||
|
position={position}
|
||||||
|
className={styles.wrapper}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Tooltip.propTypes = {
|
||||||
|
children: PropTypes.element.isRequired,
|
||||||
|
content: PropTypes.node.isRequired,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
'aria-label': PropTypes.string,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
position: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
Tooltip.defaultProps = {
|
||||||
|
disabled: false,
|
||||||
|
'aria-label': undefined,
|
||||||
|
onClick: undefined,
|
||||||
|
position: 'top center',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(Tooltip);
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
:global(#app) .wrapper {
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 16px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Tooltip from './Tooltip';
|
||||||
|
|
||||||
|
export default Tooltip;
|
||||||
@@ -7,5 +7,6 @@ import Input from './components/Input';
|
|||||||
import Popup from './components/Popup';
|
import Popup from './components/Popup';
|
||||||
import Masonry from './components/Masonry';
|
import Masonry from './components/Masonry';
|
||||||
import FilePicker from './components/FilePicker';
|
import FilePicker from './components/FilePicker';
|
||||||
|
import Tooltip from './components/Tooltip';
|
||||||
|
|
||||||
export { Input, Popup, Masonry, FilePicker };
|
export { Input, Popup, Masonry, FilePicker, Tooltip };
|
||||||
|
|||||||
@@ -6,13 +6,17 @@
|
|||||||
import { ResizeObserver } from '@juggle/resize-observer';
|
import { ResizeObserver } from '@juggle/resize-observer';
|
||||||
import React, { useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Popup as SemanticUIPopup } from 'semantic-ui-react';
|
import { Button, Popup as SemanticUIPopup } from 'semantic-ui-react';
|
||||||
|
import { Tooltip } from '../custom-ui';
|
||||||
|
|
||||||
import styles from './Popup.module.css';
|
import styles from './Popup.module.css';
|
||||||
|
|
||||||
export default (Step, { position, onOpen, onClose } = {}) => {
|
export default (Step, { position, onOpen, onClose } = {}) => {
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const Popup = React.forwardRef(({ children, ...stepProps }, ref) => {
|
const Popup = React.forwardRef(({ children, ...stepProps }, ref) => {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const [stepParams, setStepParams] = useState(null);
|
const [stepParams, setStepParams] = useState(null);
|
||||||
|
|
||||||
const wrapperRef = useRef(null);
|
const wrapperRef = useRef(null);
|
||||||
@@ -116,7 +120,9 @@ export default (Step, { position, onOpen, onClose } = {}) => {
|
|||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<div ref={handleContentRef}>
|
<div ref={handleContentRef}>
|
||||||
|
<Tooltip content={t('action.close')}>
|
||||||
<Button icon="close" onClick={handleClose} className={styles.closeButton} />
|
<Button icon="close" onClick={handleClose} className={styles.closeButton} />
|
||||||
|
</Tooltip>
|
||||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||||
<Step {...stepProps} {...stepParams} onClose={handleClose} />
|
<Step {...stepProps} {...stepParams} onClose={handleClose} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export default {
|
|||||||
addCustomField_title: 'Datenfeld hinzufügen',
|
addCustomField_title: 'Datenfeld hinzufügen',
|
||||||
addManager_title: 'Projektleiter hinzufügen',
|
addManager_title: 'Projektleiter hinzufügen',
|
||||||
addMember_title: 'Mitglied hinzufügen',
|
addMember_title: 'Mitglied hinzufügen',
|
||||||
|
addNotificationService_title: 'Benachrichtigungsdienst hinzufügen',
|
||||||
addTaskList_title: 'Aufgaben hinzufügen',
|
addTaskList_title: 'Aufgaben hinzufügen',
|
||||||
addUser_title: 'Benutzer hinzufügen',
|
addUser_title: 'Benutzer hinzufügen',
|
||||||
admin: 'Administrator',
|
admin: 'Administrator',
|
||||||
@@ -187,6 +188,12 @@ export default {
|
|||||||
lastUsed: 'Zuletzt verwendet',
|
lastUsed: 'Zuletzt verwendet',
|
||||||
never: 'Nie',
|
never: 'Nie',
|
||||||
noTrustedDevices: 'Keine vertrauenswürdigen Geräte.',
|
noTrustedDevices: 'Keine vertrauenswürdigen Geräte.',
|
||||||
|
openBoardActions: 'Arbeitsbereich-Aktionen öffnen',
|
||||||
|
openCardActions: 'Kartenaktionen öffnen',
|
||||||
|
openListActions: 'Listenaktionen öffnen',
|
||||||
|
openNotifications: 'Benachrichtigungen öffnen',
|
||||||
|
openProjectActions: 'Projektaktionen öffnen',
|
||||||
|
openProjectSettings: 'Projekteinstellungen öffnen',
|
||||||
orEnterSecretManually: 'Oder geben Sie dieses Geheimnis manuell ein:',
|
orEnterSecretManually: 'Oder geben Sie dieses Geheimnis manuell ein:',
|
||||||
recoveryCodesExhausted:
|
recoveryCodesExhausted:
|
||||||
'Alle Wiederherstellungscodes wurden verwendet. Erzeugen Sie neue Codes, um einen Fallback zu haben.',
|
'Alle Wiederherstellungscodes wurden verwendet. Erzeugen Sie neue Codes, um einen Fallback zu haben.',
|
||||||
@@ -206,6 +213,7 @@ export default {
|
|||||||
scanQrCodeWithApp:
|
scanQrCodeWithApp:
|
||||||
'Scannen Sie diesen QR-Code mit einer Authenticator-App wie Google Authenticator oder Authy.',
|
'Scannen Sie diesen QR-Code mit einer Authenticator-App wie Google Authenticator oder Authy.',
|
||||||
security_title: 'Sicherheit',
|
security_title: 'Sicherheit',
|
||||||
|
showHiddenProjects: 'Ausgeblendete Projekte anzeigen',
|
||||||
totpCode: 'TOTP-Code',
|
totpCode: 'TOTP-Code',
|
||||||
totpOrRecoveryCode: 'TOTP- oder Wiederherstellungscode',
|
totpOrRecoveryCode: 'TOTP- oder Wiederherstellungscode',
|
||||||
trustedDevices_title: 'Vertrauenswürdige Browser',
|
trustedDevices_title: 'Vertrauenswürdige Browser',
|
||||||
@@ -469,6 +477,7 @@ export default {
|
|||||||
addComment: 'Kommentar hinzufügen',
|
addComment: 'Kommentar hinzufügen',
|
||||||
addCustomField: 'Datenfeld hinzufügen',
|
addCustomField: 'Datenfeld hinzufügen',
|
||||||
addCustomFieldGroup: 'Feldgruppe hinzufügen',
|
addCustomFieldGroup: 'Feldgruppe hinzufügen',
|
||||||
|
addLabel: 'Label hinzufügen',
|
||||||
addList: 'Liste hinzufügen',
|
addList: 'Liste hinzufügen',
|
||||||
addMember: 'Mitglied hinzufügen',
|
addMember: 'Mitglied hinzufügen',
|
||||||
addMoreDetailedDescription: 'Eine detaillierte Beschreibung hinzufügen',
|
addMoreDetailedDescription: 'Eine detaillierte Beschreibung hinzufügen',
|
||||||
@@ -486,6 +495,7 @@ export default {
|
|||||||
back: 'Zurück',
|
back: 'Zurück',
|
||||||
cancel: 'Abbrechen',
|
cancel: 'Abbrechen',
|
||||||
changePassword: 'Passwort ändern',
|
changePassword: 'Passwort ändern',
|
||||||
|
close: 'Schließen',
|
||||||
copy: 'Kopieren',
|
copy: 'Kopieren',
|
||||||
copyAll: 'Alle kopieren',
|
copyAll: 'Alle kopieren',
|
||||||
copyCard_title: 'Karte Kopieren',
|
copyCard_title: 'Karte Kopieren',
|
||||||
@@ -528,6 +538,7 @@ export default {
|
|||||||
deleteUser_title: 'Benutzer löschen',
|
deleteUser_title: 'Benutzer löschen',
|
||||||
deleteWebhook: 'Webhook löschen',
|
deleteWebhook: 'Webhook löschen',
|
||||||
disable2fa: '2FA deaktivieren',
|
disable2fa: '2FA deaktivieren',
|
||||||
|
disableEditMode: 'Bearbeitungsmodus beenden',
|
||||||
dismissAll: 'Alle verwerfen',
|
dismissAll: 'Alle verwerfen',
|
||||||
done: 'Fertig',
|
done: 'Fertig',
|
||||||
download: 'Herunterladen',
|
download: 'Herunterladen',
|
||||||
@@ -550,6 +561,11 @@ export default {
|
|||||||
emptyTrash: 'Papierkorb leeren',
|
emptyTrash: 'Papierkorb leeren',
|
||||||
emptyTrash_title: 'Papierkorb leeren',
|
emptyTrash_title: 'Papierkorb leeren',
|
||||||
enable2fa: '2FA aktivieren',
|
enable2fa: '2FA aktivieren',
|
||||||
|
enableEditMode: 'Bearbeitungsmodus starten',
|
||||||
|
filterByCurrentUser: 'Nach aktuellem Benutzer filtern',
|
||||||
|
goBack: 'Zurück',
|
||||||
|
hideHiddenProjects: 'Ausgeblendete Projekte verbergen',
|
||||||
|
hideProjectList: 'Projektliste ausblenden',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
join: 'Beitreten',
|
join: 'Beitreten',
|
||||||
leave: 'Verlassen',
|
leave: 'Verlassen',
|
||||||
@@ -565,6 +581,7 @@ export default {
|
|||||||
move: 'Verschieben',
|
move: 'Verschieben',
|
||||||
moveCard_title: 'Karte bewegen',
|
moveCard_title: 'Karte bewegen',
|
||||||
moveList_title: 'Liste verschieben',
|
moveList_title: 'Liste verschieben',
|
||||||
|
paste: 'Einfügen',
|
||||||
regenerate: 'Neu generieren',
|
regenerate: 'Neu generieren',
|
||||||
regenerateApiKey: 'API-Schlüssel neu generieren',
|
regenerateApiKey: 'API-Schlüssel neu generieren',
|
||||||
regenerateRecoveryCodes: 'Wiederherstellungscodes neu generieren',
|
regenerateRecoveryCodes: 'Wiederherstellungscodes neu generieren',
|
||||||
@@ -579,6 +596,7 @@ export default {
|
|||||||
reset2fa: '2FA zurücksetzen',
|
reset2fa: '2FA zurücksetzen',
|
||||||
restoreToList: 'Wiederherstellen in {{list}}',
|
restoreToList: 'Wiederherstellen in {{list}}',
|
||||||
returnToBoard: 'Zurück zum Arbeitsbereich',
|
returnToBoard: 'Zurück zum Arbeitsbereich',
|
||||||
|
returnToProjects: 'Zurück zu den Projekten',
|
||||||
revoke: 'Widerrufen',
|
revoke: 'Widerrufen',
|
||||||
save: 'Speichern',
|
save: 'Speichern',
|
||||||
sendTestEmail: 'Test-E-Mail senden',
|
sendTestEmail: 'Test-E-Mail senden',
|
||||||
@@ -589,11 +607,17 @@ export default {
|
|||||||
showFewerAttachments: 'Weniger Anhänge anzeigen',
|
showFewerAttachments: 'Weniger Anhänge anzeigen',
|
||||||
showLess: 'Weniger anzeigen',
|
showLess: 'Weniger anzeigen',
|
||||||
showMore: 'Mehr anzeigen',
|
showMore: 'Mehr anzeigen',
|
||||||
|
showProjectList: 'Projektliste anzeigen',
|
||||||
sortList_title: 'Liste sortieren',
|
sortList_title: 'Liste sortieren',
|
||||||
start: 'Start',
|
start: 'Start',
|
||||||
stayLoggedIn: 'Angemeldet bleiben',
|
stayLoggedIn: 'Angemeldet bleiben',
|
||||||
stop: 'Stopp',
|
stop: 'Stopp',
|
||||||
subscribe: 'Abonnieren',
|
subscribe: 'Abonnieren',
|
||||||
|
switchToGridProjectsView: 'Projekte als Raster anzeigen',
|
||||||
|
switchToGridView: 'Zur Rasteransicht wechseln',
|
||||||
|
switchToGroupedProjectsView: 'Projekte gruppiert anzeigen',
|
||||||
|
switchToKanbanView: 'Zur Kanban-Ansicht wechseln',
|
||||||
|
switchToListView: 'Zur Listenansicht wechseln',
|
||||||
unsubscribe: 'De-abonnieren',
|
unsubscribe: 'De-abonnieren',
|
||||||
uploadNewAvatar: 'Neuen Avatar hochladen',
|
uploadNewAvatar: 'Neuen Avatar hochladen',
|
||||||
uploadNewImage: 'Neues Bild hochladen',
|
uploadNewImage: 'Neues Bild hochladen',
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export default {
|
|||||||
addCustomField_title: 'Add Custom Field',
|
addCustomField_title: 'Add Custom Field',
|
||||||
addManager_title: 'Add Manager',
|
addManager_title: 'Add Manager',
|
||||||
addMember_title: 'Add Member',
|
addMember_title: 'Add Member',
|
||||||
|
addNotificationService_title: 'Add Notification Service',
|
||||||
addTaskList_title: 'Add Task List',
|
addTaskList_title: 'Add Task List',
|
||||||
addUser_title: 'Add User',
|
addUser_title: 'Add User',
|
||||||
admin: 'Admin',
|
admin: 'Admin',
|
||||||
@@ -166,6 +167,12 @@ export default {
|
|||||||
lastUsed: 'Last used',
|
lastUsed: 'Last used',
|
||||||
never: 'Never',
|
never: 'Never',
|
||||||
noTrustedDevices: 'No trusted devices.',
|
noTrustedDevices: 'No trusted devices.',
|
||||||
|
openBoardActions: 'Open board actions',
|
||||||
|
openCardActions: 'Open card actions',
|
||||||
|
openListActions: 'Open list actions',
|
||||||
|
openNotifications: 'Open notifications',
|
||||||
|
openProjectActions: 'Open project actions',
|
||||||
|
openProjectSettings: 'Open project settings',
|
||||||
orEnterSecretManually: 'Or enter this secret manually:',
|
orEnterSecretManually: 'Or enter this secret manually:',
|
||||||
recoveryCodesExhausted:
|
recoveryCodesExhausted:
|
||||||
'All recovery codes have been used. Regenerate a new set to keep a fallback in case you lose your authenticator app.',
|
'All recovery codes have been used. Regenerate a new set to keep a fallback in case you lose your authenticator app.',
|
||||||
@@ -184,6 +191,7 @@ export default {
|
|||||||
scanQrCodeWithApp:
|
scanQrCodeWithApp:
|
||||||
'Scan this QR code with an authenticator app such as Google Authenticator or Authy.',
|
'Scan this QR code with an authenticator app such as Google Authenticator or Authy.',
|
||||||
security_title: 'Security',
|
security_title: 'Security',
|
||||||
|
showHiddenProjects: 'Show hidden projects',
|
||||||
totpCode: 'TOTP code',
|
totpCode: 'TOTP code',
|
||||||
totpOrRecoveryCode: 'TOTP or recovery code',
|
totpOrRecoveryCode: 'TOTP or recovery code',
|
||||||
trustedDevices_title: 'Trusted Browsers',
|
trustedDevices_title: 'Trusted Browsers',
|
||||||
@@ -443,6 +451,7 @@ export default {
|
|||||||
addComment: 'Add comment',
|
addComment: 'Add comment',
|
||||||
addCustomField: 'Add custom field',
|
addCustomField: 'Add custom field',
|
||||||
addCustomFieldGroup: 'Add custom field group',
|
addCustomFieldGroup: 'Add custom field group',
|
||||||
|
addLabel: 'Add label',
|
||||||
addList: 'Add list',
|
addList: 'Add list',
|
||||||
addMember: 'Add member',
|
addMember: 'Add member',
|
||||||
addMoreDetailedDescription: 'Add more detailed description',
|
addMoreDetailedDescription: 'Add more detailed description',
|
||||||
@@ -460,6 +469,7 @@ export default {
|
|||||||
back: 'Back',
|
back: 'Back',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
changePassword: 'Change password',
|
changePassword: 'Change password',
|
||||||
|
close: 'Close',
|
||||||
copy: 'Copy',
|
copy: 'Copy',
|
||||||
copyAll: 'Copy all',
|
copyAll: 'Copy all',
|
||||||
copyCard_title: 'Copy Card',
|
copyCard_title: 'Copy Card',
|
||||||
@@ -502,6 +512,7 @@ export default {
|
|||||||
deleteUser_title: 'Delete User',
|
deleteUser_title: 'Delete User',
|
||||||
deleteWebhook: 'Delete webhook',
|
deleteWebhook: 'Delete webhook',
|
||||||
disable2fa: 'Disable 2FA',
|
disable2fa: 'Disable 2FA',
|
||||||
|
disableEditMode: 'Disable edit mode',
|
||||||
dismissAll: 'Dismiss all',
|
dismissAll: 'Dismiss all',
|
||||||
done: 'Done',
|
done: 'Done',
|
||||||
download: 'Download',
|
download: 'Download',
|
||||||
@@ -524,6 +535,11 @@ export default {
|
|||||||
emptyTrash: 'Empty trash',
|
emptyTrash: 'Empty trash',
|
||||||
emptyTrash_title: 'Empty Trash',
|
emptyTrash_title: 'Empty Trash',
|
||||||
enable2fa: 'Enable 2FA',
|
enable2fa: 'Enable 2FA',
|
||||||
|
enableEditMode: 'Enable edit mode',
|
||||||
|
filterByCurrentUser: 'Filter by current user',
|
||||||
|
goBack: 'Go back',
|
||||||
|
hideHiddenProjects: 'Hide hidden projects',
|
||||||
|
hideProjectList: 'Hide project list',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
join: 'Join',
|
join: 'Join',
|
||||||
leave: 'Leave',
|
leave: 'Leave',
|
||||||
@@ -539,6 +555,7 @@ export default {
|
|||||||
move: 'Move',
|
move: 'Move',
|
||||||
moveCard_title: 'Move Card',
|
moveCard_title: 'Move Card',
|
||||||
moveList_title: 'Move List',
|
moveList_title: 'Move List',
|
||||||
|
paste: 'Paste',
|
||||||
regenerate: 'Regenerate',
|
regenerate: 'Regenerate',
|
||||||
regenerateApiKey: 'Regenerate API key',
|
regenerateApiKey: 'Regenerate API key',
|
||||||
regenerateRecoveryCodes: 'Regenerate recovery codes',
|
regenerateRecoveryCodes: 'Regenerate recovery codes',
|
||||||
@@ -553,6 +570,7 @@ export default {
|
|||||||
reset2fa: 'Reset 2FA',
|
reset2fa: 'Reset 2FA',
|
||||||
restoreToList: 'Restore to {{list}}',
|
restoreToList: 'Restore to {{list}}',
|
||||||
returnToBoard: 'Return to board',
|
returnToBoard: 'Return to board',
|
||||||
|
returnToProjects: 'Return to projects',
|
||||||
revoke: 'Revoke',
|
revoke: 'Revoke',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
sendTestEmail: 'Send test email',
|
sendTestEmail: 'Send test email',
|
||||||
@@ -563,11 +581,17 @@ export default {
|
|||||||
showFewerAttachments: 'Show fewer attachments',
|
showFewerAttachments: 'Show fewer attachments',
|
||||||
showLess: 'Show less',
|
showLess: 'Show less',
|
||||||
showMore: 'Show more',
|
showMore: 'Show more',
|
||||||
|
showProjectList: 'Show project list',
|
||||||
sortList_title: 'Sort List',
|
sortList_title: 'Sort List',
|
||||||
start: 'Start',
|
start: 'Start',
|
||||||
stayLoggedIn: 'Stay logged in',
|
stayLoggedIn: 'Stay logged in',
|
||||||
stop: 'Stop',
|
stop: 'Stop',
|
||||||
subscribe: 'Subscribe',
|
subscribe: 'Subscribe',
|
||||||
|
switchToGridProjectsView: 'Show projects as a grid',
|
||||||
|
switchToGridView: 'Switch to grid view',
|
||||||
|
switchToGroupedProjectsView: 'Show projects grouped',
|
||||||
|
switchToKanbanView: 'Switch to kanban view',
|
||||||
|
switchToListView: 'Switch to list view',
|
||||||
unsubscribe: 'Unsubscribe',
|
unsubscribe: 'Unsubscribe',
|
||||||
uploadNewAvatar: 'Upload new avatar',
|
uploadNewAvatar: 'Upload new avatar',
|
||||||
uploadNewImage: 'Upload new image',
|
uploadNewImage: 'Upload new image',
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export default {
|
|||||||
addCustomField_title: 'Добавить настраиваемое поле',
|
addCustomField_title: 'Добавить настраиваемое поле',
|
||||||
addManager_title: 'Добавление менеджера',
|
addManager_title: 'Добавление менеджера',
|
||||||
addMember_title: 'Добавление участника',
|
addMember_title: 'Добавление участника',
|
||||||
|
addNotificationService_title: 'Добавить сервис уведомлений',
|
||||||
addTaskList_title: 'Добавить список задач',
|
addTaskList_title: 'Добавить список задач',
|
||||||
addUser_title: 'Добавление пользователя',
|
addUser_title: 'Добавление пользователя',
|
||||||
admin: 'Админ',
|
admin: 'Админ',
|
||||||
@@ -300,6 +301,12 @@ export default {
|
|||||||
onlyOneManagerShouldRemainToMakeThisProjectPrivate:
|
onlyOneManagerShouldRemainToMakeThisProjectPrivate:
|
||||||
'Должен остаться только один менеджер, чтобы сделать этот проект частным',
|
'Должен остаться только один менеджер, чтобы сделать этот проект частным',
|
||||||
openBoard_title: 'Откройте доску',
|
openBoard_title: 'Откройте доску',
|
||||||
|
openBoardActions: 'Открыть действия доски',
|
||||||
|
openCardActions: 'Открыть действия карточки',
|
||||||
|
openListActions: 'Открыть действия списка',
|
||||||
|
openNotifications: 'Открыть уведомления',
|
||||||
|
openProjectActions: 'Открыть действия проекта',
|
||||||
|
openProjectSettings: 'Открыть настройки проекта',
|
||||||
optional_inline: 'необязательно',
|
optional_inline: 'необязательно',
|
||||||
orEnterSecretManually: 'Или введите этот секретный ключ вручную:',
|
orEnterSecretManually: 'Или введите этот секретный ключ вручную:',
|
||||||
organization: 'Организация',
|
organization: 'Организация',
|
||||||
@@ -367,6 +374,7 @@ export default {
|
|||||||
settings: 'Настройки',
|
settings: 'Настройки',
|
||||||
shared: 'Общий',
|
shared: 'Общий',
|
||||||
sharedWithMe_title: 'Общие со мной',
|
sharedWithMe_title: 'Общие со мной',
|
||||||
|
showHiddenProjects: 'Показать скрытые проекты',
|
||||||
showOnFrontOfCard: 'Показать на лицевой стороне карточки',
|
showOnFrontOfCard: 'Показать на лицевой стороне карточки',
|
||||||
smtp: 'SMTP',
|
smtp: 'SMTP',
|
||||||
sortList_title: 'Сортировка списка',
|
sortList_title: 'Сортировка списка',
|
||||||
@@ -459,6 +467,7 @@ export default {
|
|||||||
addCustomField: 'Добавить настраиваемое поле',
|
addCustomField: 'Добавить настраиваемое поле',
|
||||||
addCustomFieldGroup: 'Добавить группу настраиваемых полей',
|
addCustomFieldGroup: 'Добавить группу настраиваемых полей',
|
||||||
addList: 'Добавить список',
|
addList: 'Добавить список',
|
||||||
|
addLabel: 'Добавить метку',
|
||||||
addMember: 'Добавить участника',
|
addMember: 'Добавить участника',
|
||||||
addMoreDetailedDescription: 'Добавить более подробное описание',
|
addMoreDetailedDescription: 'Добавить более подробное описание',
|
||||||
addTask: 'Добавить задачу',
|
addTask: 'Добавить задачу',
|
||||||
@@ -475,6 +484,7 @@ export default {
|
|||||||
back: 'Назад',
|
back: 'Назад',
|
||||||
cancel: 'Отменить',
|
cancel: 'Отменить',
|
||||||
changePassword: 'Изменить пароль',
|
changePassword: 'Изменить пароль',
|
||||||
|
close: 'Закрыть',
|
||||||
copy: 'Копировать',
|
copy: 'Копировать',
|
||||||
copyAll: 'Копировать все',
|
copyAll: 'Копировать все',
|
||||||
copyCard_title: 'Копировать карточку',
|
copyCard_title: 'Копировать карточку',
|
||||||
@@ -536,9 +546,15 @@ export default {
|
|||||||
editTitle_title: 'Изменить название',
|
editTitle_title: 'Изменить название',
|
||||||
editType_title: 'Изменить тип',
|
editType_title: 'Изменить тип',
|
||||||
editUsername_title: 'Изменить имя пользователя',
|
editUsername_title: 'Изменить имя пользователя',
|
||||||
|
enableEditMode: 'Включить режим редактирования',
|
||||||
emptyTrash: 'Очистить корзину',
|
emptyTrash: 'Очистить корзину',
|
||||||
emptyTrash_title: 'Очистить корзину',
|
emptyTrash_title: 'Очистить корзину',
|
||||||
|
disableEditMode: 'Выключить режим редактирования',
|
||||||
enable2fa: 'Включить 2FA',
|
enable2fa: 'Включить 2FA',
|
||||||
|
filterByCurrentUser: 'Фильтр по текущему пользователю',
|
||||||
|
goBack: 'Назад',
|
||||||
|
hideHiddenProjects: 'Скрыть скрытые проекты',
|
||||||
|
hideProjectList: 'Скрыть список проектов',
|
||||||
import: 'Импорт',
|
import: 'Импорт',
|
||||||
join: 'Присоединиться',
|
join: 'Присоединиться',
|
||||||
leave: 'Покинуть',
|
leave: 'Покинуть',
|
||||||
@@ -554,6 +570,7 @@ export default {
|
|||||||
move: 'Переместить',
|
move: 'Переместить',
|
||||||
moveCard_title: 'Переместить карточку',
|
moveCard_title: 'Переместить карточку',
|
||||||
moveList_title: 'Переместить список',
|
moveList_title: 'Переместить список',
|
||||||
|
paste: 'Вставить',
|
||||||
regenerate: 'Перегенерировать',
|
regenerate: 'Перегенерировать',
|
||||||
regenerateApiKey: 'Перегенерировать ключ API',
|
regenerateApiKey: 'Перегенерировать ключ API',
|
||||||
regenerateRecoveryCodes: 'Перегенерировать коды восстановления',
|
regenerateRecoveryCodes: 'Перегенерировать коды восстановления',
|
||||||
@@ -568,9 +585,11 @@ export default {
|
|||||||
reset2fa: 'Сбросить 2FA',
|
reset2fa: 'Сбросить 2FA',
|
||||||
restoreToList: 'Восстановить в {{list}}',
|
restoreToList: 'Восстановить в {{list}}',
|
||||||
returnToBoard: 'Вернуться на доску',
|
returnToBoard: 'Вернуться на доску',
|
||||||
|
returnToProjects: 'Вернуться к проектам',
|
||||||
revoke: 'Отозвать',
|
revoke: 'Отозвать',
|
||||||
save: 'Сохранить',
|
save: 'Сохранить',
|
||||||
sendTestEmail: 'Отправить тестовое письмо',
|
sendTestEmail: 'Отправить тестовое письмо',
|
||||||
|
showProjectList: 'Показать список проектов',
|
||||||
showActive: 'Показать активные',
|
showActive: 'Показать активные',
|
||||||
showAllAttachments: 'Показать все вложения ({{hidden}} скрыто)',
|
showAllAttachments: 'Показать все вложения ({{hidden}} скрыто)',
|
||||||
showCardsWithThisUser: 'Показать карточки с этим пользователем',
|
showCardsWithThisUser: 'Показать карточки с этим пользователем',
|
||||||
@@ -583,6 +602,13 @@ export default {
|
|||||||
stayLoggedIn: 'Остаться в системе',
|
stayLoggedIn: 'Остаться в системе',
|
||||||
stop: 'Остановить',
|
stop: 'Остановить',
|
||||||
subscribe: 'Подписаться',
|
subscribe: 'Подписаться',
|
||||||
|
switchToGridProjectsView: 'Показать проекты сеткой',
|
||||||
|
switchToGridView: 'Переключиться на вид сетки',
|
||||||
|
switchToGroupedProjectsView: 'Показать проекты по группам',
|
||||||
|
switchToKanbanView: 'Переключиться на вид канбана',
|
||||||
|
switchToListView: 'Переключиться на вид списка',
|
||||||
|
unlinkSso: 'Отвязать SSO',
|
||||||
|
unlinkSso_title: 'Отвязать SSO',
|
||||||
unsubscribe: 'Отписаться',
|
unsubscribe: 'Отписаться',
|
||||||
uploadNewAvatar: 'Загрузить новый аватар',
|
uploadNewAvatar: 'Загрузить новый аватар',
|
||||||
uploadNewImage: 'Загрузить новое изображение',
|
uploadNewImage: 'Загрузить новое изображение',
|
||||||
|
|||||||
Reference in New Issue
Block a user