ref: Refactoring

This commit is contained in:
Maksim Eltyshev
2025-11-27 18:24:55 +01:00
parent 7be2343076
commit 54e230d4c1
80 changed files with 252 additions and 159 deletions
@@ -11,8 +11,8 @@ import { Link } from 'react-router-dom';
import { Comment } from 'semantic-ui-react';
import selectors from '../../../selectors';
import { isUserStatic } from '../../../utils/record-helpers';
import Paths from '../../../constants/Paths';
import { StaticUserIds } from '../../../constants/StaticUsers';
import { ActivityTypes } from '../../../constants/Enums';
import TimeAgo from '../../common/TimeAgo';
import UserAvatar from '../../users/UserAvatar';
@@ -30,12 +30,11 @@ const Item = React.memo(({ id }) => {
const [t] = useTranslation();
const userName =
user.id === StaticUserIds.DELETED
? t(`common.${user.name}`, {
context: 'title',
})
: user.name;
const userName = isUserStatic(user)
? t(`common.${user.name}`, {
context: 'title',
})
: user.name;
const cardName = card ? card.name : activity.data.card.name;
@@ -10,7 +10,7 @@ import { useTranslation, Trans } from 'react-i18next';
import { Comment } from 'semantic-ui-react';
import selectors from '../../../selectors';
import { StaticUserIds } from '../../../constants/StaticUsers';
import { isUserStatic } from '../../../utils/record-helpers';
import { ActivityTypes } from '../../../constants/Enums';
import TimeAgo from '../../common/TimeAgo';
import UserAvatar from '../../users/UserAvatar';
@@ -26,12 +26,11 @@ const Item = React.memo(({ id }) => {
const [t] = useTranslation();
const userName =
user.id === StaticUserIds.DELETED
? t(`common.${user.name}`, {
context: 'title',
})
: user.name;
const userName = isUserStatic(user)
? t(`common.${user.name}`, {
context: 'title',
})
: user.name;
let contentNode;
switch (activity.type) {
@@ -15,13 +15,13 @@ import ListView from './ListView';
const FiniteContent = React.memo(() => {
const board = useSelector(selectors.selectCurrentBoard);
const cardIds = useSelector(selectors.selectFilteredCardIdsForCurrentBoard);
const hasAnyFiniteList = useSelector((state) => !!selectors.selectFirstFiniteListId(state));
const canAddCard = useSelector((state) => !!selectors.selectFirstKanbanListId(state));
const dispatch = useDispatch();
const handleCardCreate = useCallback(
(data, autoOpen) => {
dispatch(entryActions.createCardInFirstFiniteList(data, undefined, autoOpen));
dispatch(entryActions.createCardInCurrentContext(data, undefined, autoOpen));
},
[dispatch],
);
@@ -39,7 +39,7 @@ const FiniteContent = React.memo(() => {
default:
}
return <View cardIds={cardIds} onCardCreate={hasAnyFiniteList ? handleCardCreate : undefined} />;
return <View cardIds={cardIds} onCardCreate={canAddCard ? handleCardCreate : undefined} />;
});
export default FiniteContent;
@@ -23,7 +23,7 @@ import styles from './KanbanContent.module.scss';
import globalStyles from '../../../../styles.module.scss';
const KanbanContent = React.memo(() => {
const listIds = useSelector(selectors.selectFiniteListIdsForCurrentBoard);
const listIds = useSelector(selectors.selectKanbanListIdsForCurrentBoard);
const canAddList = useSelector((state) => {
const isEditModeEnabled = selectors.selectIsEditModeEnabled(state); // TODO: move out?
@@ -21,6 +21,7 @@
top: 2px;
transition: background 85ms ease;
width: 20px;
z-index: 1000;
&:hover {
background: #ebeef0;
@@ -52,6 +53,7 @@
background: #fff;
border-radius: 3px;
box-shadow: 0 1px 0 #ccc;
overflow: hidden;
position: relative;
&:hover {
@@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next';
import { Icon } from 'semantic-ui-react';
import selectors from '../../../selectors';
import { StaticUserIds } from '../../../constants/StaticUsers';
import { isUserStatic } from '../../../utils/record-helpers';
import TimeAgo from '../../common/TimeAgo';
import UserAvatar from '../../users/UserAvatar';
@@ -32,7 +32,7 @@ const CreationDetailsStep = React.memo(({ userId }) => {
</span>
<span className={styles.content}>
<div className={styles.name}>
{user.id === StaticUserIds.DELETED
{isUserStatic(user)
? t(`common.${user.name}`, {
context: 'title',
})
@@ -9,32 +9,32 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { Gallery, Item as GalleryItem } from 'react-photoswipe-gallery';
import { Button, Grid, Icon } from 'semantic-ui-react';
import { useDidUpdate } from '../../../../lib/hooks';
import { useDidUpdate } from '../../../lib/hooks';
import selectors from '../../../../selectors';
import entryActions from '../../../../entry-actions';
import { usePopupInClosableContext } from '../../../../hooks';
import { isUsableMarkdownElement } from '../../../../utils/element-helpers';
import { BoardMembershipRoles, CardTypes, ListTypes } from '../../../../constants/Enums';
import { CardTypeIcons } from '../../../../constants/Icons';
import { ClosableContext } from '../../../../contexts';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
import { usePopupInClosableContext } from '../../../hooks';
import { isUsableMarkdownElement } from '../../../utils/element-helpers';
import { BoardMembershipRoles, CardTypes, ListTypes } from '../../../constants/Enums';
import { CardTypeIcons } from '../../../constants/Icons';
import { ClosableContext } from '../../../contexts';
import NameField from './NameField';
import Thumbnail from './Thumbnail';
import NameField from '../NameField';
import CustomFieldGroups from '../CustomFieldGroups';
import Communication from '../Communication';
import CreationDetailsStep from '../CreationDetailsStep';
import MoreActionsStep from '../MoreActionsStep';
import Markdown from '../../../common/Markdown';
import EditMarkdown from '../../../common/EditMarkdown';
import ConfirmationStep from '../../../common/ConfirmationStep';
import UserAvatar from '../../../users/UserAvatar';
import BoardMembershipsStep from '../../../board-memberships/BoardMembershipsStep';
import LabelChip from '../../../labels/LabelChip';
import LabelsStep from '../../../labels/LabelsStep';
import ListsStep from '../../../lists/ListsStep';
import Attachments from '../../../attachments/Attachments';
import AddAttachmentStep from '../../../attachments/AddAttachmentStep';
import AddCustomFieldGroupStep from '../../../custom-field-groups/AddCustomFieldGroupStep';
import CustomFieldGroups from './CustomFieldGroups';
import Communication from './Communication';
import CreationDetailsStep from './CreationDetailsStep';
import MoreActionsStep from './MoreActionsStep';
import Markdown from '../../common/Markdown';
import EditMarkdown from '../../common/EditMarkdown';
import ConfirmationStep from '../../common/ConfirmationStep';
import UserAvatar from '../../users/UserAvatar';
import BoardMembershipsStep from '../../board-memberships/BoardMembershipsStep';
import LabelChip from '../../labels/LabelChip';
import LabelsStep from '../../labels/LabelsStep';
import ListsStep from '../../lists/ListsStep';
import Attachments from '../../attachments/Attachments';
import AddAttachmentStep from '../../attachments/AddAttachmentStep';
import AddCustomFieldGroupStep from '../../custom-field-groups/AddCustomFieldGroupStep';
import styles from './StoryContent.module.scss';
@@ -99,7 +99,7 @@
}
.coverWrapper {
padding-bottom: 20px;
padding-bottom: 10px;
}
.cursorPointer {
@@ -283,7 +283,7 @@
}
.moduleWrapperAttachments {
margin-bottom: 20px;
margin-bottom: 10px;
}
.moreActionsButton {
@@ -1,8 +0,0 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import StoryContent from './StoryContent';
export default StoryContent;
@@ -8,7 +8,7 @@ import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { Item as GalleryItem } from 'react-photoswipe-gallery';
import selectors from '../../../../selectors';
import selectors from '../../../selectors';
import styles from './Thumbnail.module.scss';
@@ -14,8 +14,7 @@ import { useDidUpdate } from '../../../lib/hooks';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
import { usePopupInClosableContext } from '../../../hooks';
import { isListArchiveOrTrash } from '../../../utils/record-helpers';
import { StaticUserIds } from '../../../constants/StaticUsers';
import { isListArchiveOrTrash, isUserStatic } from '../../../utils/record-helpers';
import { BoardMembershipRoles } from '../../../constants/Enums';
import { ClosableContext } from '../../../contexts';
import Edit from './Edit';
@@ -107,7 +106,7 @@ const Item = React.memo(({ id }) => {
) : (
<div className={classNames(styles.bubble, isCurrentUser && styles.bubbleRight)}>
<div className={styles.header}>
{user.id === StaticUserIds.DELETED
{isUserStatic(user)
? t(`common.${user.name}`, {
context: 'title',
})
@@ -17,9 +17,9 @@ import AddStep from './AddStep';
import styles from './UsersPane.module.scss';
const UsersPane = React.memo(() => {
const activeUsersTotal = useSelector(selectors.selectActiveUsersTotal);
const activeUsersLimit = useSelector(selectors.selectActiveUsersLimit);
const users = useSelector(selectors.selectUsers);
const activeUsersTotal = useSelector(selectors.selectActiveUsersTotal);
const canAdd = useSelector((state) => {
const oidcBootstrap = selectors.selectOidcBootstrap(state);
@@ -12,7 +12,7 @@ import selectors from '../../../selectors';
import matchPaths from '../../../utils/match-paths';
import Paths from '../../../constants/Paths';
const Linkify = React.memo(({ href, content, stopPropagation, ...props }) => {
const Link = React.memo(({ href, content, stopPropagation, ...props }) => {
const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
const url = useMemo(() => {
@@ -68,14 +68,14 @@ const Linkify = React.memo(({ href, content, stopPropagation, ...props }) => {
);
});
Linkify.propTypes = {
Link.propTypes = {
href: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
stopPropagation: PropTypes.bool,
};
Linkify.defaultProps = {
Link.defaultProps = {
stopPropagation: false,
};
export default Linkify;
export default Link;
@@ -14,8 +14,8 @@ import { Button } from 'semantic-ui-react';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
import { mentionMarkupToText } from '../../../utils/mentions';
import { isUserStatic } from '../../../utils/record-helpers';
import Paths from '../../../constants/Paths';
import { StaticUserIds } from '../../../constants/StaticUsers';
import { NotificationTypes } from '../../../constants/Enums';
import TimeAgo from '../../common/TimeAgo';
import UserAvatar from '../../users/UserAvatar';
@@ -42,12 +42,11 @@ const Item = React.memo(({ id, onClose }) => {
dispatch(entryActions.deleteNotification(id));
}, [id, dispatch]);
const creatorUserName =
creatorUser.id === StaticUserIds.DELETED
? t(`common.${creatorUser.name}`, {
context: 'title',
})
: creatorUser.name;
const creatorUserName = isUserStatic(creatorUser)
? t(`common.${creatorUser.name}`, {
context: 'title',
})
: creatorUser.name;
const cardName = card ? card.name : notification.data.card.name;
@@ -13,7 +13,7 @@ import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import selectors from '../../../selectors';
import { StaticUserIds } from '../../../constants/StaticUsers';
import { isUserStatic } from '../../../utils/record-helpers';
import styles from './UserAvatar.module.scss';
@@ -62,7 +62,7 @@ const UserAvatar = React.memo(
const contentNode = (
<span
title={
user.id === StaticUserIds.DELETED
isUserStatic(user)
? t(`common.${user.name}`, {
context: 'title',
})
+1 -1
View File
@@ -186,7 +186,7 @@ export default {
CARDS_IN_CURRENT_LIST_FETCH: `${PREFIX}/CARDS_IN_CURRENT_LIST_FETCH`,
CARDS_UPDATE_HANDLE: `${PREFIX}/CARDS_UPDATE_HANDLE`,
CARD_CREATE: `${PREFIX}/CARD_CREATE`,
CARD_IN_FIRST_FINITE_LIST_CREATE: `${PREFIX}/CARD_IN_FIRST_FINITE_LIST_CREATE`,
CARD_IN_CURRENT_CONTEXT_CREATE: `${PREFIX}/CARD_IN_CURRENT_CONTEXT_CREATE`,
CARD_IN_CURRENT_LIST_CREATE: `${PREFIX}/CARD_IN_CURRENT_LIST_CREATE`,
CARD_CREATE_HANDLE: `${PREFIX}/CARD_CREATE_HANDLE`,
CARD_UPDATE: `${PREFIX}/CARD_UPDATE`,
+3 -3
View File
@@ -28,8 +28,8 @@ const createCard = (listId, data, index, autoOpen = false) => ({
},
});
const createCardInFirstFiniteList = (data, index = 0, autoOpen = false) => ({
type: EntryActionTypes.CARD_IN_FIRST_FINITE_LIST_CREATE,
const createCardInCurrentContext = (data, index = 0, autoOpen = false) => ({
type: EntryActionTypes.CARD_IN_CURRENT_CONTEXT_CREATE,
payload: {
data,
index,
@@ -180,7 +180,7 @@ export default {
fetchCardsInCurrentList,
handleCardsUpdate,
createCard,
createCardInFirstFiniteList,
createCardInCurrentContext,
createCardInCurrentList,
handleCardCreate,
updateCard,
+4 -4
View File
@@ -7,7 +7,7 @@ import { attr, fk, many } from 'redux-orm';
import BaseModel from './BaseModel';
import buildSearchParts from '../utils/build-search-parts';
import { isListFinite } from '../utils/record-helpers';
import { isListKanban } from '../utils/record-helpers';
import ActionTypes from '../constants/ActionTypes';
import Config from '../constants/Config';
import { BoardContexts, BoardViews } from '../constants/Enums';
@@ -285,8 +285,8 @@ export default class extends BaseModel {
return this.lists.orderBy(['position', 'id.length', 'id']);
}
getFiniteListsQuerySet() {
return this.getListsQuerySet().filter((list) => isListFinite(list));
getKanbanListsQuerySet() {
return this.getListsQuerySet().filter((list) => isListKanban(list));
}
getCustomFieldGroupsQuerySet() {
@@ -316,7 +316,7 @@ export default class extends BaseModel {
}
getCardsModelArray() {
return this.getFiniteListsQuerySet()
return this.getKanbanListsQuerySet()
.toModelArray()
.flatMap((listModel) => listModel.getCardsModelArray());
}
+5 -7
View File
@@ -336,13 +336,11 @@ export default class extends BaseModel {
return [];
}
if (searchRegex) {
cardModels = cardModels.filter(
(cardModel) =>
searchRegex.test(cardModel.name) ||
(cardModel.description && searchRegex.test(cardModel.description)),
);
}
cardModels = cardModels.filter(
(cardModel) =>
searchRegex.test(cardModel.name) ||
(cardModel.description && searchRegex.test(cardModel.description)),
);
} else {
const searchParts = buildSearchParts(this.board.search);
+4 -4
View File
@@ -167,10 +167,10 @@ export function* createCard(listId, data, index, autoOpen) {
}
}
export function* createCardInFirstFiniteList(data, index, autoOpen) {
const firstFiniteListId = yield select(selectors.selectFirstFiniteListId);
export function* createCardInCurrentContext(data, index, autoOpen) {
const firstKanbanListId = yield select(selectors.selectFirstKanbanListId);
yield call(createCard, firstFiniteListId, data, index, autoOpen);
yield call(createCard, firstKanbanListId, data, index, autoOpen);
}
export function* createCardInCurrentList(data, autoOpen) {
@@ -601,7 +601,7 @@ export default {
fetchCardsInCurrentList,
handleCardsUpdate,
createCard,
createCardInFirstFiniteList,
createCardInCurrentContext,
createCardInCurrentList,
handleCardCreate,
updateCard,
+2 -2
View File
@@ -20,9 +20,9 @@ export default function* cardsWatchers() {
services.createCard(listId, data, index, autoOpen),
),
takeEvery(
EntryActionTypes.CARD_IN_FIRST_FINITE_LIST_CREATE,
EntryActionTypes.CARD_IN_CURRENT_CONTEXT_CREATE,
({ payload: { data, index, autoOpen } }) =>
services.createCardInFirstFiniteList(data, index, autoOpen),
services.createCardInCurrentContext(data, index, autoOpen),
),
takeEvery(EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE, ({ payload: { data, autoOpen } }) =>
services.createCardInCurrentList(data, autoOpen),
+3 -3
View File
@@ -271,7 +271,7 @@ export const selectTrashListIdForCurrentBoard = createSelector(
},
);
export const selectFiniteListIdsForCurrentBoard = createSelector(
export const selectKanbanListIdsForCurrentBoard = createSelector(
orm,
(state) => selectPath(state).boardId,
({ Board }, id) => {
@@ -286,7 +286,7 @@ export const selectFiniteListIdsForCurrentBoard = createSelector(
}
return boardModel
.getFiniteListsQuerySet()
.getKanbanListsQuerySet()
.toRefArray()
.map((list) => list.id);
},
@@ -482,7 +482,7 @@ export default {
selectLabelsForCurrentBoard,
selectArchiveListIdForCurrentBoard,
selectTrashListIdForCurrentBoard,
selectFiniteListIdsForCurrentBoard,
selectKanbanListIdsForCurrentBoard,
selectAvailableListsForCurrentBoard,
selectCardsExceptCurrentForCurrentBoard,
selectFilteredCardIdsForCurrentBoard,
+3 -3
View File
@@ -127,7 +127,7 @@ export const selectCurrentList = createSelector(
},
);
export const selectFirstFiniteListId = createSelector(
export const selectFirstKanbanListId = createSelector(
orm,
(state) => selectPath(state).boardId,
({ Board }, id) => {
@@ -141,7 +141,7 @@ export const selectFirstFiniteListId = createSelector(
return boardModel;
}
const listModel = boardModel.getFiniteListsQuerySet().first();
const listModel = boardModel.getKanbanListsQuerySet().first();
return listModel && listModel.id;
},
);
@@ -174,6 +174,6 @@ export default {
selectIsListWithIdAvailableForCurrentUser,
selectCurrentListId,
selectCurrentList,
selectFirstFiniteListId,
selectFirstKanbanListId,
selectFilteredCardIdsForCurrentList,
};
+1 -1
View File
@@ -75,7 +75,7 @@ export const selectNextListPosition = createSelector(
return boardModel;
}
return nextPosition(boardModel.getFiniteListsQuerySet().toRefArray(), index, excludedId);
return nextPosition(boardModel.getKanbanListsQuerySet().toRefArray(), index, excludedId);
},
);
+5
View File
@@ -3,8 +3,11 @@
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import { StaticUserIds } from '../constants/StaticUsers';
import { ListTypes, UserRoles } from '../constants/Enums';
export const isUserStatic = (user) => [StaticUserIds.DELETED].includes(user.id);
export const isUserAdminOrProjectOwner = (user) =>
[UserRoles.ADMIN, UserRoles.PROJECT_OWNER].includes(user.role);
@@ -12,3 +15,5 @@ export const isListArchiveOrTrash = (list) =>
[ListTypes.ARCHIVE, ListTypes.TRASH].includes(list.type);
export const isListFinite = (list) => [ListTypes.ACTIVE, ListTypes.CLOSED].includes(list.type);
export const isListKanban = (list) => [ListTypes.ACTIVE, ListTypes.CLOSED].includes(list.type);
+1
View File
@@ -11,6 +11,7 @@ const USERNAME_REGEX = /^[a-zA-Z0-9]+((_|\.)?[a-zA-Z0-9])*$/;
export const isUrl = (string) =>
isURL(string, {
protocols: ['http', 'https'],
require_tld: false,
require_protocol: true,
max_allowed_length: 2048,
});