feat: Version 2

Closes #627, closes #1047
This commit is contained in:
Maksim Eltyshev
2025-05-10 02:09:06 +02:00
parent ad7fb51cfa
commit 2ee1166747
1557 changed files with 76832 additions and 47042 deletions

View File

@@ -0,0 +1,129 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import truncate from 'lodash/truncate';
import React, { useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { useTranslation, Trans } from 'react-i18next';
import { Link } from 'react-router-dom';
import { Button } from 'semantic-ui-react';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
import Paths from '../../../constants/Paths';
import { StaticUserIds } from '../../../constants/StaticUsers';
import { NotificationTypes } from '../../../constants/Enums';
import UserAvatar from '../../users/UserAvatar';
import styles from './Item.module.scss';
const Item = React.memo(({ id, onClose }) => {
const selectNotificationById = useMemo(() => selectors.makeSelectNotificationById(), []);
const selectCreatorUserById = useMemo(() => selectors.makeSelectUserById(), []);
const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
const notification = useSelector((state) => selectNotificationById(state, id));
const creatorUser = useSelector((state) =>
selectCreatorUserById(state, notification.creatorUserId),
);
const card = useSelector((state) => selectCardById(state, notification.cardId));
const dispatch = useDispatch();
const [t] = useTranslation();
const handleDeleteClick = useCallback(() => {
dispatch(entryActions.deleteNotification(id));
}, [id, dispatch]);
const creatorUserName =
creatorUser.id === StaticUserIds.DELETED
? t(`common.${creatorUser.name}`, {
context: 'title',
})
: creatorUser.name;
const cardName = card ? card.name : notification.data.card.name;
let contentNode;
switch (notification.type) {
case NotificationTypes.MOVE_CARD: {
const { fromList, toList } = notification.data;
const fromListName = fromList.name || t(`common.${fromList.type}`);
const toListName = toList.name || t(`common.${toList.type}`);
contentNode = (
<Trans
i18nKey="common.userMovedCardFromListToList"
values={{
user: creatorUserName,
card: cardName,
fromList: fromListName,
toList: toListName,
}}
>
{creatorUserName}
{' moved '}
<Link to={Paths.CARDS.replace(':id', notification.cardId)} onClick={onClose}>
{cardName}
</Link>
{' from '}
{fromListName}
{' to '}
{toListName}
</Trans>
);
break;
}
case NotificationTypes.COMMENT_CARD: {
const commentText = truncate(notification.data.text);
contentNode = (
<Trans
i18nKey="common.userLeftNewCommentToCard"
values={{
user: creatorUserName,
comment: commentText,
card: cardName,
}}
>
{creatorUserName}
{` left a new comment «${commentText}» to `}
<Link to={Paths.CARDS.replace(':id', notification.cardId)} onClick={onClose}>
{cardName}
</Link>
</Trans>
);
break;
}
default:
contentNode = null;
}
return (
<div className={styles.wrapper}>
<UserAvatar id={notification.creatorUserId} size="large" />
<span className={styles.content}>{contentNode}</span>
<Button
type="button"
icon="trash alternate outline"
className={styles.button}
onClick={handleDeleteClick}
/>
</div>
);
});
Item.propTypes = {
id: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
};
export default Item;

View File

@@ -0,0 +1,42 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
:global(#app) {
.button {
background: transparent;
box-shadow: none;
float: right;
height: 20px;
line-height: 20px;
margin: 0;
min-height: auto;
padding: 0;
transition: background 0.3s ease;
width: 20px;
&:hover {
background: #e9e9e9;
}
}
.content {
display: inline-block;
font-size: 12px;
min-height: 36px;
overflow: hidden;
padding: 0 4px 0 8px;
vertical-align: top;
width: calc(100% - 56px);
word-break: break-word;
}
.wrapper {
padding: 12px;
&:hover {
background: #f0f0f0;
}
}
}

View File

@@ -0,0 +1,65 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { Button } from 'semantic-ui-react';
import { Popup } from '../../../lib/custom-ui';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
import Item from './Item';
import styles from './NotificationsStep.module.scss';
const NotificationsStep = React.memo(({ onClose }) => {
const notificationIds = useSelector(selectors.selectNotificationIdsForCurrentUser);
const dispatch = useDispatch();
const [t] = useTranslation();
const handleDeleteAllClick = useCallback(() => {
dispatch(entryActions.deleteAllNotifications());
}, [dispatch]);
return (
<>
<Popup.Header>
{t('common.notifications', {
context: 'title',
})}
</Popup.Header>
<Popup.Content>
{notificationIds.length > 0 ? (
<>
<div className={styles.items}>
{notificationIds.map((notificationId) => (
<Item key={notificationId} id={notificationId} onClose={onClose} />
))}
</div>
{notificationIds.length > 1 && (
<Button
fluid
content={t('action.dismissAll')}
className={styles.deleteAllButton}
onClick={handleDeleteAllClick}
/>
)}
</>
) : (
t('common.noUnreadNotifications')
)}
</Popup.Content>
</>
);
});
NotificationsStep.propTypes = {
onClose: PropTypes.func.isRequired,
};
export default NotificationsStep;

View File

@@ -0,0 +1,46 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
:global(#app) {
.deleteAllButton {
background: transparent;
box-shadow: none;
color: #6b808c;
font-weight: normal;
margin-top: 8px;
padding: 6px 11px;
text-decoration: underline;
transition: none;
&:hover {
background: rgba(9, 30, 66, 0.08);
color: #092d42;
}
}
.items {
margin: 0 -12px;
max-height: 60vh;
overflow-x: hidden;
overflow-y: auto;
@supports (-moz-appearance: none) {
scrollbar-color: rgba(0, 0, 0, 0.32) transparent;
scrollbar-width: thin;
}
&::-webkit-scrollbar {
width: 5px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
border-radius: 3px;
}
}
}

View File

@@ -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 NotificationsStep from './NotificationsStep';
export default NotificationsStep;