diff --git a/client/src/components/cards/Card/TaskList/TaskList.jsx b/client/src/components/cards/Card/TaskList/TaskList.jsx
index e9497848..49a6c1e9 100644
--- a/client/src/components/cards/Card/TaskList/TaskList.jsx
+++ b/client/src/components/cards/Card/TaskList/TaskList.jsx
@@ -9,6 +9,7 @@ import classNames from 'classnames';
import { useSelector } from 'react-redux';
import { Progress } from 'semantic-ui-react';
import { useToggle } from '../../../../lib/hooks';
+import { ListTypes } from '../../../../constants/Enums';
import selectors from '../../../../selectors';
import Task from './Task';
@@ -23,9 +24,33 @@ const TaskList = React.memo(({ id }) => {
const [isOpened, toggleOpened] = useToggle();
// TODO: move to selector?
- const completedTasksTotal = useMemo(
- () => tasks.reduce((result, task) => (task.isCompleted ? result + 1 : result), 0),
- [tasks],
+ const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
+ const selectListById = useMemo(() => selectors.makeSelectListById(), []);
+
+ const completedTasksTotal = useSelector((state) =>
+ tasks.reduce((result, task) => {
+ if (task.isCompleted) {
+ return result + 1;
+ }
+
+ const regex = /\/cards\/([^/]+)/g;
+ const matches = task.name.matchAll(regex);
+
+ // eslint-disable-next-line no-restricted-syntax
+ for (const [, cardId] of matches) {
+ const card = selectCardById(state, cardId);
+
+ if (card) {
+ const list = selectListById(state, card.listId);
+
+ if (list && list.type === ListTypes.CLOSED) {
+ return result + 1;
+ }
+ }
+ }
+
+ return result;
+ }, 0),
);
const handleToggleClick = useCallback(
diff --git a/client/src/components/common/Linkify.jsx b/client/src/components/common/Linkify.jsx
index 86df52ec..f1d4f5cb 100644
--- a/client/src/components/common/Linkify.jsx
+++ b/client/src/components/common/Linkify.jsx
@@ -5,11 +5,15 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
+import { useSelector } from 'react-redux';
import LinkifyReact from 'linkify-react';
import history from '../../history';
+import selectors from '../../selectors';
const Linkify = React.memo(({ children, linkStopPropagation, ...props }) => {
+ const cardNamesById = useSelector(selectors.selectCardNamesById);
+
const handleLinkClick = useCallback(
(event) => {
if (linkStopPropagation) {
@@ -34,6 +38,12 @@ const Linkify = React.memo(({ children, linkStopPropagation, ...props }) => {
}
const isSameSite = !!url && url.origin === window.location.origin;
+ let linkContent = content;
+ if (isSameSite) {
+ const { pathname } = url;
+ const match = pathname.match(/^\/cards\/([^/]+)$/);
+ linkContent = cardNamesById[match?.[1]] || pathname;
+ }
return (
{
rel={isSameSite ? undefined : 'noreferrer'}
onClick={handleLinkClick}
>
- {isSameSite ? url.pathname : content}
+ {isSameSite ? linkContent : content}
);
},
- [handleLinkClick],
+ [handleLinkClick, cardNamesById],
);
return (
diff --git a/client/src/components/task-lists/TaskList/Task/Task.jsx b/client/src/components/task-lists/TaskList/Task/Task.jsx
index b1671f79..be5b88b4 100755
--- a/client/src/components/task-lists/TaskList/Task/Task.jsx
+++ b/client/src/components/task-lists/TaskList/Task/Task.jsx
@@ -16,7 +16,7 @@ import selectors from '../../../../selectors';
import entryActions from '../../../../entry-actions';
import { usePopupInClosableContext } from '../../../../hooks';
import { isListArchiveOrTrash } from '../../../../utils/record-helpers';
-import { BoardMembershipRoles } from '../../../../constants/Enums';
+import { BoardMembershipRoles, ListTypes } from '../../../../constants/Enums';
import { ClosableContext } from '../../../../contexts';
import EditName from './EditName';
import SelectAssigneeStep from './SelectAssigneeStep';
@@ -29,9 +29,26 @@ import styles from './Task.module.scss';
const Task = React.memo(({ id, index }) => {
const selectTaskById = useMemo(() => selectors.makeSelectTaskById(), []);
const selectListById = useMemo(() => selectors.makeSelectListById(), []);
+ const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
const task = useSelector((state) => selectTaskById(state, id));
+ const isLinkedCardCompleted = useSelector((state) => {
+ const regex = /\/cards\/([^/]+)/g;
+ const matches = task.name.matchAll(regex);
+ // eslint-disable-next-line no-restricted-syntax
+ for (const [, cardId] of matches) {
+ const card = selectCardById(state, cardId);
+ if (card) {
+ const list = selectListById(state, card.listId);
+ if (list && list.type === ListTypes.CLOSED) {
+ return true;
+ }
+ }
+ }
+ return false;
+ });
+
const { canEdit, canToggle } = useSelector((state) => {
const { listId } = selectors.selectCurrentCard(state);
const list = selectListById(state, listId);
@@ -56,13 +73,21 @@ const Task = React.memo(({ id, index }) => {
const [isEditNameOpened, setIsEditNameOpened] = useState(false);
const [, , setIsClosableActive] = useContext(ClosableContext);
+ const isEditable = task.isPersisted && canEdit;
+ const isCompleted = task.isCompleted || isLinkedCardCompleted;
+ const isToggleDisabled = !task.isPersisted || !canToggle || isLinkedCardCompleted;
+
const handleToggleChange = useCallback(() => {
+ if (isToggleDisabled) {
+ return;
+ }
+
dispatch(
entryActions.updateTask(id, {
isCompleted: !task.isCompleted,
}),
);
- }, [id, task.isCompleted, dispatch]);
+ }, [id, task.isCompleted, dispatch, isToggleDisabled]);
const handleUserSelect = useCallback(
(userId) => {
@@ -83,8 +108,6 @@ const Task = React.memo(({ id, index }) => {
);
}, [id, dispatch]);
- const isEditable = task.isPersisted && canEdit;
-
const handleClick = useCallback(() => {
if (isEditable) {
setIsEditNameOpened(true);
@@ -122,8 +145,8 @@ const Task = React.memo(({ id, index }) => {
>
@@ -138,9 +161,7 @@ const Task = React.memo(({ id, index }) => {
className={classNames(styles.text, canEdit && styles.textEditable)}
onClick={handleClick}
>
-
+
{task.name}
diff --git a/client/src/components/task-lists/TaskList/TaskList.jsx b/client/src/components/task-lists/TaskList/TaskList.jsx
index 25e7be7d..08081c8a 100755
--- a/client/src/components/task-lists/TaskList/TaskList.jsx
+++ b/client/src/components/task-lists/TaskList/TaskList.jsx
@@ -14,7 +14,7 @@ import { useDidUpdate } from '../../../lib/hooks';
import selectors from '../../../selectors';
import { isListArchiveOrTrash } from '../../../utils/record-helpers';
import DroppableTypes from '../../../constants/DroppableTypes';
-import { BoardMembershipRoles } from '../../../constants/Enums';
+import { BoardMembershipRoles, ListTypes } from '../../../constants/Enums';
import { ClosableContext } from '../../../contexts';
import Task from './Task';
import AddTask from './AddTask';
@@ -46,9 +46,32 @@ const TaskList = React.memo(({ id }) => {
const [, , setIsClosableActive] = useContext(ClosableContext);
// TODO: move to selector?
- const completedTasksTotal = useMemo(
- () => tasks.reduce((result, task) => (task.isCompleted ? result + 1 : result), 0),
- [tasks],
+ const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
+
+ const completedTasksTotal = useSelector((state) =>
+ tasks.reduce((result, task) => {
+ if (task.isCompleted) {
+ return result + 1;
+ }
+
+ const regex = /\/cards\/([^/]+)/g;
+ const matches = task.name.matchAll(regex);
+
+ // eslint-disable-next-line no-restricted-syntax
+ for (const [, cardId] of matches) {
+ const card = selectCardById(state, cardId);
+
+ if (card) {
+ const list = selectListById(state, card.listId);
+
+ if (list && list.type === ListTypes.CLOSED) {
+ return result + 1;
+ }
+ }
+ }
+
+ return result;
+ }, 0),
);
const handleAddClick = useCallback(() => {
diff --git a/client/src/selectors/cards.js b/client/src/selectors/cards.js
index eb203b1a..b9d2875b 100644
--- a/client/src/selectors/cards.js
+++ b/client/src/selectors/cards.js
@@ -32,6 +32,18 @@ export const makeSelectCardById = () =>
export const selectCardById = makeSelectCardById();
+export const selectCardNamesById = createSelector(orm, ({ Card }) =>
+ Card.all()
+ .toModelArray()
+ .reduce(
+ (result, cardModel) => ({
+ ...result,
+ [cardModel.id]: cardModel.name,
+ }),
+ {},
+ ),
+);
+
export const makeSelectCardIndexById = () =>
createSelector(
orm,
@@ -467,6 +479,7 @@ export default {
selectCardById,
makeSelectCardIndexById,
selectCardIndexById,
+ selectCardNamesById,
makeSelectUserIdsByCardId,
selectUserIdsByCardId,
makeSelectLabelIdsByCardId,