diff --git a/client/src/components/cards/Card/TaskList/Task.jsx b/client/src/components/cards/Card/TaskList/Task.jsx
index 5603bf00..731aa7ea 100644
--- a/client/src/components/cards/Card/TaskList/Task.jsx
+++ b/client/src/components/cards/Card/TaskList/Task.jsx
@@ -9,17 +9,43 @@ import classNames from 'classnames';
import { useSelector } from 'react-redux';
import selectors from '../../../../selectors';
+import { ListTypes } from '../../../../constants/Enums';
import Linkify from '../../../common/Linkify';
import styles from './Task.module.scss';
const Task = React.memo(({ id }) => {
const selectTaskById = useMemo(() => selectors.makeSelectTaskById(), []);
+ const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
+ const selectListById = useMemo(() => selectors.makeSelectListById(), []);
const task = useSelector((state) => selectTaskById(state, id));
+ const isCompleted = useSelector((state) => {
+ if (task.isCompleted) {
+ return true;
+ }
+
+ 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;
+ });
+
return (
-
+
{task.name}
);
diff --git a/client/src/components/cards/Card/TaskList/TaskList.jsx b/client/src/components/cards/Card/TaskList/TaskList.jsx
index 49a6c1e9..368b73c8 100644
--- a/client/src/components/cards/Card/TaskList/TaskList.jsx
+++ b/client/src/components/cards/Card/TaskList/TaskList.jsx
@@ -17,16 +17,13 @@ import Task from './Task';
import styles from './TaskList.module.scss';
const TaskList = React.memo(({ id }) => {
+ const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
+ const selectListById = useMemo(() => selectors.makeSelectListById(), []);
const selectTasksByTaskListId = useMemo(() => selectors.makeSelectTasksByTaskListId(), []);
const tasks = useSelector((state) => selectTasksByTaskListId(state, id));
- const [isOpened, toggleOpened] = useToggle();
-
// TODO: move to selector?
- const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
- const selectListById = useMemo(() => selectors.makeSelectListById(), []);
-
const completedTasksTotal = useSelector((state) =>
tasks.reduce((result, task) => {
if (task.isCompleted) {
@@ -53,6 +50,8 @@ const TaskList = React.memo(({ id }) => {
}, 0),
);
+ const [isOpened, toggleOpened] = useToggle();
+
const handleToggleClick = useCallback(
(event) => {
event.stopPropagation();
diff --git a/client/src/components/common/Linkify.jsx b/client/src/components/common/Linkify.jsx
index f1d4f5cb..e0304891 100644
--- a/client/src/components/common/Linkify.jsx
+++ b/client/src/components/common/Linkify.jsx
@@ -3,16 +3,44 @@
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
-import React, { useCallback } from 'react';
+import React, { useCallback, useMemo } 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';
+import matchPaths from '../../utils/match-paths';
+import Paths from '../../constants/Paths';
const Linkify = React.memo(({ children, linkStopPropagation, ...props }) => {
- const cardNamesById = useSelector(selectors.selectCardNamesById);
+ const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
+
+ const url = useMemo(() => {
+ try {
+ return new URL(children, window.location);
+ } catch {
+ return null;
+ }
+ }, [children]);
+
+ const isSameSite = !!url && url.origin === window.location.origin;
+
+ const cardsPathMatch = useMemo(() => {
+ if (!isSameSite) {
+ return null;
+ }
+
+ return matchPaths(url.pathname, [Paths.CARDS]);
+ }, [url.pathname, isSameSite]);
+
+ const card = useSelector((state) => {
+ if (!cardsPathMatch) {
+ return null;
+ }
+
+ return selectCardById(state, cardsPathMatch.params.id);
+ });
const handleLinkClick = useCallback(
(event) => {
@@ -20,44 +48,27 @@ const Linkify = React.memo(({ children, linkStopPropagation, ...props }) => {
event.stopPropagation();
}
- if (!event.target.getAttribute('target')) {
+ if (isSameSite) {
event.preventDefault();
history.push(event.target.href);
}
},
- [linkStopPropagation],
+ [linkStopPropagation, isSameSite],
);
const linkRenderer = useCallback(
- ({ attributes: { href, ...linkProps }, content }) => {
- let url;
- try {
- url = new URL(href, window.location);
- } catch {
- /* empty */
- }
-
- 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 (
-
- {isSameSite ? linkContent : content}
-
- );
- },
- [handleLinkClick, cardNamesById],
+ ({ attributes: { href, ...linkProps }, content }) => (
+
+ {card ? card.name : content}
+
+ ),
+ [isSameSite, card, handleLinkClick],
);
return (
diff --git a/client/src/components/task-lists/TaskList/Task/Task.jsx b/client/src/components/task-lists/TaskList/Task/Task.jsx
index be5b88b4..9458afea 100755
--- a/client/src/components/task-lists/TaskList/Task/Task.jsx
+++ b/client/src/components/task-lists/TaskList/Task/Task.jsx
@@ -28,19 +28,22 @@ 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 selectListById = useMemo(() => selectors.makeSelectListById(), []);
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;
}
@@ -73,21 +76,13 @@ 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, isToggleDisabled]);
+ }, [id, task.isCompleted, dispatch]);
const handleUserSelect = useCallback(
(userId) => {
@@ -108,6 +103,10 @@ const Task = React.memo(({ id, index }) => {
);
}, [id, dispatch]);
+ const isEditable = task.isPersisted && canEdit;
+ const isCompleted = task.isCompleted || isLinkedCardCompleted;
+ const isToggleDisabled = !task.isPersisted || !canToggle || isLinkedCardCompleted;
+
const handleClick = useCallback(() => {
if (isEditable) {
setIsEditNameOpened(true);
diff --git a/client/src/components/task-lists/TaskList/TaskList.jsx b/client/src/components/task-lists/TaskList/TaskList.jsx
index 08081c8a..8ff3d8c4 100755
--- a/client/src/components/task-lists/TaskList/TaskList.jsx
+++ b/client/src/components/task-lists/TaskList/TaskList.jsx
@@ -23,31 +23,14 @@ import styles from './TaskList.module.scss';
const TaskList = React.memo(({ id }) => {
const selectTaskListById = useMemo(() => selectors.makeSelectTaskListById(), []);
+ const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
const selectListById = useMemo(() => selectors.makeSelectListById(), []);
const selectTasksByTaskListId = useMemo(() => selectors.makeSelectTasksByTaskListId(), []);
const taskList = useSelector((state) => selectTaskListById(state, id));
const tasks = useSelector((state) => selectTasksByTaskListId(state, id));
- const canEdit = useSelector((state) => {
- const { listId } = selectors.selectCurrentCard(state);
- const list = selectListById(state, listId);
-
- if (isListArchiveOrTrash(list)) {
- return false;
- }
-
- const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
- return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;
- });
-
- const [t] = useTranslation();
- const [isAddOpened, setIsAddOpened] = useState(false);
- const [, , setIsClosableActive] = useContext(ClosableContext);
-
// TODO: move to selector?
- const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
-
const completedTasksTotal = useSelector((state) =>
tasks.reduce((result, task) => {
if (task.isCompleted) {
@@ -74,6 +57,22 @@ const TaskList = React.memo(({ id }) => {
}, 0),
);
+ const canEdit = useSelector((state) => {
+ const { listId } = selectors.selectCurrentCard(state);
+ const list = selectListById(state, listId);
+
+ if (isListArchiveOrTrash(list)) {
+ return false;
+ }
+
+ const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
+ return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;
+ });
+
+ const [t] = useTranslation();
+ const [isAddOpened, setIsAddOpened] = useState(false);
+ const [, , setIsClosableActive] = useContext(ClosableContext);
+
const handleAddClick = useCallback(() => {
setIsAddOpened(true);
}, []);
diff --git a/client/src/selectors/cards.js b/client/src/selectors/cards.js
index b9d2875b..eb203b1a 100644
--- a/client/src/selectors/cards.js
+++ b/client/src/selectors/cards.js
@@ -32,18 +32,6 @@ 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,
@@ -479,7 +467,6 @@ export default {
selectCardById,
makeSelectCardIndexById,
selectCardIndexById,
- selectCardNamesById,
makeSelectUserIdsByCardId,
selectUserIdsByCardId,
makeSelectLabelIdsByCardId,