diff --git a/client/src/actions/activities.js b/client/src/actions/activities.js index d69f0134..1369da82 100644 --- a/client/src/actions/activities.js +++ b/client/src/actions/activities.js @@ -5,15 +5,39 @@ import ActionTypes from '../constants/ActionTypes'; -const fetchActivities = (cardId) => ({ - type: ActionTypes.ACTIVITIES_FETCH, +const fetchActivitiesInBoard = (boardId) => ({ + type: ActionTypes.ACTIVITIES_IN_BOARD_FETCH, + payload: { + boardId, + }, +}); + +fetchActivitiesInBoard.success = (boardId, activities, users) => ({ + type: ActionTypes.ACTIVITIES_IN_BOARD_FETCH__SUCCESS, + payload: { + boardId, + activities, + users, + }, +}); + +fetchActivitiesInBoard.failure = (boardId, error) => ({ + type: ActionTypes.ACTIVITIES_IN_BOARD_FETCH__FAILURE, + payload: { + boardId, + error, + }, +}); + +const fetchActivitiesInCard = (cardId) => ({ + type: ActionTypes.ACTIVITIES_IN_CARD_FETCH, payload: { cardId, }, }); -fetchActivities.success = (cardId, activities, users) => ({ - type: ActionTypes.ACTIVITIES_FETCH__SUCCESS, +fetchActivitiesInCard.success = (cardId, activities, users) => ({ + type: ActionTypes.ACTIVITIES_IN_CARD_FETCH__SUCCESS, payload: { cardId, activities, @@ -21,8 +45,8 @@ fetchActivities.success = (cardId, activities, users) => ({ }, }); -fetchActivities.failure = (cardId, error) => ({ - type: ActionTypes.ACTIVITIES_FETCH__FAILURE, +fetchActivitiesInCard.failure = (cardId, error) => ({ + type: ActionTypes.ACTIVITIES_IN_CARD_FETCH__FAILURE, payload: { cardId, error, @@ -37,6 +61,7 @@ const handleActivityCreate = (activity) => ({ }); export default { - fetchActivities, + fetchActivitiesInBoard, + fetchActivitiesInCard, handleActivityCreate, }; diff --git a/client/src/api/activities.js b/client/src/api/activities.js index cb0cbbb8..7db3ef1d 100755 --- a/client/src/api/activities.js +++ b/client/src/api/activities.js @@ -16,7 +16,13 @@ export const transformActivity = (activity) => ({ /* Actions */ -const getActivities = (cardId, data, headers) => +const getActivitiesInBoard = (boardId, data, headers) => + socket.get(`/boards/${boardId}/actions`, data, headers).then((body) => ({ + ...body, + items: body.items.map(transformActivity), + })); + +const getActivitiesInCard = (cardId, data, headers) => socket.get(`/cards/${cardId}/actions`, data, headers).then((body) => ({ ...body, items: body.items.map(transformActivity), @@ -32,6 +38,7 @@ const makeHandleActivityCreate = (next) => (body) => { }; export default { - getActivities, + getActivitiesInBoard, + getActivitiesInCard, makeHandleActivityCreate, }; diff --git a/client/src/components/activities/BoardActivitiesModal/BoardActivitiesModal.jsx b/client/src/components/activities/BoardActivitiesModal/BoardActivitiesModal.jsx new file mode 100755 index 00000000..81c0240f --- /dev/null +++ b/client/src/components/activities/BoardActivitiesModal/BoardActivitiesModal.jsx @@ -0,0 +1,73 @@ +/*! + * 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 { useDispatch, useSelector } from 'react-redux'; +import { useTranslation } from 'react-i18next'; +import { useInView } from 'react-intersection-observer'; +import { Comment, Loader } from 'semantic-ui-react'; + +import selectors from '../../../selectors'; +import entryActions from '../../../entry-actions'; +import { useClosableModal } from '../../../hooks'; +import Item from './Item'; + +import styles from './BoardActivitiesModal.module.scss'; + +const BoardActivitiesModal = React.memo(() => { + const activityIds = useSelector(selectors.selectActivityIdsForCurrentBoard); + + const { isActivitiesFetching, isAllActivitiesFetched } = useSelector( + selectors.selectCurrentBoard, + ); + + const dispatch = useDispatch(); + const [t] = useTranslation(); + + const handleClose = useCallback(() => { + dispatch(entryActions.closeModal()); + }, [dispatch]); + + const [inViewRef] = useInView({ + threshold: 1, + onChange: (inView) => { + if (inView) { + dispatch(entryActions.fetchActivitiesInCurrentBoard()); + } + }, + }); + + const [ClosableModal] = useClosableModal(); + + return ( + + + {t('common.boardActions', { + context: 'title', + })} + + +
+ + {activityIds.map((activityId) => ( + + ))} + +
+ {isActivitiesFetching !== undefined && isAllActivitiesFetched !== undefined && ( +
+ {isActivitiesFetching ? ( + + ) : ( + !isAllActivitiesFetched &&
+ )} +
+ )} + + + ); +}); + +export default BoardActivitiesModal; diff --git a/client/src/components/activities/BoardActivitiesModal/BoardActivitiesModal.module.scss b/client/src/components/activities/BoardActivitiesModal/BoardActivitiesModal.module.scss new file mode 100644 index 00000000..373d35ae --- /dev/null +++ b/client/src/components/activities/BoardActivitiesModal/BoardActivitiesModal.module.scss @@ -0,0 +1,18 @@ +/*! + * Copyright (c) 2024 PLANKA Software GmbH + * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md + */ + +:global(#app) { + .items { + max-width: none; + } + + .itemsWrapper { + margin-top: 12px; + } + + .loaderWrapper { + margin-top: 10px; + } +} diff --git a/client/src/components/activities/BoardActivitiesModal/Item.jsx b/client/src/components/activities/BoardActivitiesModal/Item.jsx new file mode 100755 index 00000000..35cd0720 --- /dev/null +++ b/client/src/components/activities/BoardActivitiesModal/Item.jsx @@ -0,0 +1,220 @@ +/*! + * 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 { useSelector } from 'react-redux'; +import { useTranslation, Trans } from 'react-i18next'; +import { Link } from 'react-router-dom'; +import { Comment } from 'semantic-ui-react'; + +import selectors from '../../../selectors'; +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'; + +import styles from './Item.module.scss'; + +const Item = React.memo(({ id }) => { + const selectActivityById = useMemo(() => selectors.makeSelectActivityById(), []); + const selectUserById = useMemo(() => selectors.makeSelectUserById(), []); + const selectCardById = useMemo(() => selectors.makeSelectCardById(), []); + + const activity = useSelector((state) => selectActivityById(state, id)); + const user = useSelector((state) => selectUserById(state, activity.userId)); + const card = useSelector((state) => selectCardById(state, activity.cardId)); + + const [t] = useTranslation(); + + const userName = + user.id === StaticUserIds.DELETED + ? t(`common.${user.name}`, { + context: 'title', + }) + : user.name; + + const cardName = card ? card.name : activity.data.card.name; + + let contentNode; + switch (activity.type) { + case ActivityTypes.CREATE_CARD: { + const { list } = activity.data; + const listName = list.name || t(`common.${list.type}`); + + contentNode = ( + + {userName} + {' added '} + {cardName} + {' to '} + {listName} + + ); + + break; + } + case ActivityTypes.MOVE_CARD: { + const { fromList, toList } = activity.data; + + const fromListName = fromList.name || t(`common.${fromList.type}`); + const toListName = toList.name || t(`common.${toList.type}`); + + contentNode = ( + + {userName} + {' moved '} + {cardName} + {' from '} + {fromListName} + {' to '} + {toListName} + + ); + + break; + } + case ActivityTypes.ADD_MEMBER_TO_CARD: + contentNode = + user.id === activity.data.user.id ? ( + + {userName} + {' joined '} + {cardName} + + ) : ( + + {userName} + {' added '} + {activity.data.user.name} + {' to '} + {cardName} + + ); + + break; + case ActivityTypes.REMOVE_MEMBER_FROM_CARD: + contentNode = + user.id === activity.data.user.id ? ( + + {userName} + {' left '} + {cardName} + + ) : ( + + {userName} + {' removed '} + {activity.data.user.name} + {' from '} + {cardName} + + ); + + break; + case ActivityTypes.COMPLETE_TASK: + contentNode = ( + + {userName} + {' completed '} + {activity.data.task.name} + {' on '} + {cardName} + + ); + + break; + case ActivityTypes.UNCOMPLETE_TASK: + contentNode = ( + + {userName} + {' marked '} + {activity.data.task.name} + {' incomplete on '} + {cardName} + + ); + + break; + default: + contentNode = null; + } + + return ( + + + + +
+
{contentNode}
+ + + +
+
+ ); +}); + +Item.propTypes = { + id: PropTypes.string.isRequired, +}; + +export default Item; diff --git a/client/src/components/activities/Activities/Item.module.scss b/client/src/components/activities/BoardActivitiesModal/Item.module.scss similarity index 90% rename from client/src/components/activities/Activities/Item.module.scss rename to client/src/components/activities/BoardActivitiesModal/Item.module.scss index 7245597b..2b420687 100644 --- a/client/src/components/activities/Activities/Item.module.scss +++ b/client/src/components/activities/BoardActivitiesModal/Item.module.scss @@ -7,12 +7,12 @@ .author { color: #17394d; font-weight: bold; - line-height: 20px; } .content { border-bottom: 1px solid #092d4221; display: inline-block; + line-height: 20px; padding-bottom: 14px; vertical-align: top; width: calc(100% - 40px); @@ -21,11 +21,6 @@ .date { color: #6b808c; font-size: 12px; - line-height: 20px; - } - - .text { - line-height: 20px; } .user { diff --git a/client/src/components/activities/BoardActivitiesModal/index.js b/client/src/components/activities/BoardActivitiesModal/index.js new file mode 100755 index 00000000..ba2ef125 --- /dev/null +++ b/client/src/components/activities/BoardActivitiesModal/index.js @@ -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 BoardActivitiesModal from './BoardActivitiesModal'; + +export default BoardActivitiesModal; diff --git a/client/src/components/activities/Activities/Activities.jsx b/client/src/components/activities/CardActivities/CardActivities.jsx similarity index 92% rename from client/src/components/activities/Activities/Activities.jsx rename to client/src/components/activities/CardActivities/CardActivities.jsx index 686d3c4d..39868ede 100755 --- a/client/src/components/activities/Activities/Activities.jsx +++ b/client/src/components/activities/CardActivities/CardActivities.jsx @@ -12,9 +12,9 @@ import selectors from '../../../selectors'; import entryActions from '../../../entry-actions'; import Item from './Item'; -import styles from './Activities.module.scss'; +import styles from './CardActivities.module.scss'; -const Activities = React.memo(() => { +const CardActivities = React.memo(() => { const activityIds = useSelector(selectors.selectActivityIdsForCurrentCard); const { isActivitiesFetching, isAllActivitiesFetched } = useSelector(selectors.selectCurrentCard); @@ -51,4 +51,4 @@ const Activities = React.memo(() => { ); }); -export default Activities; +export default CardActivities; diff --git a/client/src/components/activities/Activities/Activities.module.scss b/client/src/components/activities/CardActivities/CardActivities.module.scss similarity index 100% rename from client/src/components/activities/Activities/Activities.module.scss rename to client/src/components/activities/CardActivities/CardActivities.module.scss diff --git a/client/src/components/activities/Activities/Item.jsx b/client/src/components/activities/CardActivities/Item.jsx similarity index 82% rename from client/src/components/activities/Activities/Item.jsx rename to client/src/components/activities/CardActivities/Item.jsx index e4d34acb..4a644f2d 100755 --- a/client/src/components/activities/Activities/Item.jsx +++ b/client/src/components/activities/CardActivities/Item.jsx @@ -48,10 +48,8 @@ const Item = React.memo(({ id }) => { }} > {userName} - - {' added this card to '} - {listName} - + {' added this card to '} + {listName} ); @@ -73,12 +71,10 @@ const Item = React.memo(({ id }) => { }} > {userName} - - {' moved this card from '} - {fromListName} - {' to '} - {toListName} - + {' moved this card from '} + {fromListName} + {' to '} + {toListName} ); @@ -94,7 +90,7 @@ const Item = React.memo(({ id }) => { }} > {userName} - {' joined this card'} + {' joined this card'} ) : ( { }} > {userName} - - {' added '} - {activity.data.user.name} - {' to this card'} - + {' added '} + {activity.data.user.name} + {' to this card'} ); @@ -124,7 +118,7 @@ const Item = React.memo(({ id }) => { }} > {userName} - {' left this card'} + {' left this card'} ) : ( { }} > {userName} - - {' removed '} - {activity.data.user.name} - {' from this card'} - + {' removed '} + {activity.data.user.name} + {' from this card'} ); @@ -154,11 +146,9 @@ const Item = React.memo(({ id }) => { }} > {userName} - - {' completed '} - {activity.data.task.name} - {' on this card'} - + {' completed '} + {activity.data.task.name} + {' on this card'} ); @@ -173,11 +163,9 @@ const Item = React.memo(({ id }) => { }} > {userName} - - {' marked '} - {activity.data.task.name} - {' incomplete on this card'} - + {' marked '} + {activity.data.task.name} + {' incomplete on this card'} ); diff --git a/client/src/components/activities/CardActivities/Item.module.scss b/client/src/components/activities/CardActivities/Item.module.scss new file mode 100644 index 00000000..2b420687 --- /dev/null +++ b/client/src/components/activities/CardActivities/Item.module.scss @@ -0,0 +1,31 @@ +/*! + * Copyright (c) 2024 PLANKA Software GmbH + * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md + */ + +:global(#app) { + .author { + color: #17394d; + font-weight: bold; + } + + .content { + border-bottom: 1px solid #092d4221; + display: inline-block; + line-height: 20px; + padding-bottom: 14px; + vertical-align: top; + width: calc(100% - 40px); + } + + .date { + color: #6b808c; + font-size: 12px; + } + + .user { + display: inline-block; + padding: 4px 8px 0 0; + vertical-align: top; + } +} diff --git a/client/src/components/activities/Activities/index.js b/client/src/components/activities/CardActivities/index.js similarity index 66% rename from client/src/components/activities/Activities/index.js rename to client/src/components/activities/CardActivities/index.js index 26c9f8db..775e3cd0 100755 --- a/client/src/components/activities/Activities/index.js +++ b/client/src/components/activities/CardActivities/index.js @@ -3,6 +3,6 @@ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md */ -import Activities from './Activities'; +import CardActivities from './CardActivities'; -export default Activities; +export default CardActivities; diff --git a/client/src/components/boards/Board/Board.jsx b/client/src/components/boards/Board/Board.jsx index 1929a9fb..401fe508 100644 --- a/client/src/components/boards/Board/Board.jsx +++ b/client/src/components/boards/Board/Board.jsx @@ -7,14 +7,17 @@ import React from 'react'; import { useSelector } from 'react-redux'; import selectors from '../../../selectors'; +import ModalTypes from '../../../constants/ModalTypes'; import { BoardContexts, BoardViews } from '../../../constants/Enums'; import KanbanContent from './KanbanContent'; import FiniteContent from './FiniteContent'; import EndlessContent from './EndlessContent'; import CardModal from '../../cards/CardModal'; +import BoardActivitiesModal from '../../activities/BoardActivitiesModal'; const Board = React.memo(() => { const board = useSelector(selectors.selectCurrentBoard); + const modal = useSelector(selectors.selectCurrentModal); const isCardModalOpened = useSelector((state) => !!selectors.selectPath(state).cardId); let Content; @@ -35,10 +38,23 @@ const Board = React.memo(() => { } } + let modalNode = null; + if (isCardModalOpened) { + modalNode = ; + } else if (modal) { + switch (modal.type) { + case ModalTypes.BOARD_ACTIVITIES: + modalNode = ; + + break; + default: + } + } + return ( <> - {isCardModalOpened && } + {modalNode} ); }); diff --git a/client/src/components/boards/BoardActions/RightSide/ActionsStep.jsx b/client/src/components/boards/BoardActions/RightSide/ActionsStep.jsx index 7a50caba..f4bb8fe3 100755 --- a/client/src/components/boards/BoardActions/RightSide/ActionsStep.jsx +++ b/client/src/components/boards/BoardActions/RightSide/ActionsStep.jsx @@ -21,14 +21,14 @@ import CustomFieldGroupsStep from '../../../custom-field-groups/CustomFieldGroup import styles from './ActionsStep.module.scss'; const StepTypes = { - EMPTY_TRASH: 'EMPTY_TRASH', CUSTOM_FIELD_GROUPS: 'CUSTOM_FIELD_GROUPS', + EMPTY_TRASH: 'EMPTY_TRASH', }; const ActionsStep = React.memo(({ onClose }) => { const board = useSelector(selectors.selectCurrentBoard); - const { withSubscribe, withTrashEmptier, withCustomFieldGroups } = useSelector((state) => { + const { withSubscribe, withCustomFieldGroups, withTrashEmptier } = useSelector((state) => { const isManager = selectors.selectIsCurrentUserManagerForCurrentProject(state); const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state); @@ -42,8 +42,8 @@ const ActionsStep = React.memo(({ onClose }) => { return { withSubscribe: isMember, // TODO: rename? - withTrashEmptier: board.context === BoardContexts.TRASH && (isManager || isEditor), withCustomFieldGroups: isEditor, + withTrashEmptier: board.context === BoardContexts.TRASH && (isManager || isEditor), }; }, shallowEqual); @@ -69,21 +69,28 @@ const ActionsStep = React.memo(({ onClose }) => { [onClose, dispatch], ); + const handleActivitiesClick = useCallback(() => { + dispatch(entryActions.openBoardActivitiesModal()); + onClose(); + }, [onClose, dispatch]); + const handleEmptyTrashConfirm = useCallback(() => { dispatch(entryActions.clearTrashListInCurrentBoard()); onClose(); }, [onClose, dispatch]); - const handleEmptyTrashClick = useCallback(() => { - openStep(StepTypes.EMPTY_TRASH); - }, [openStep]); - const handleCustomFieldsClick = useCallback(() => { openStep(StepTypes.CUSTOM_FIELD_GROUPS); }, [openStep]); + const handleEmptyTrashClick = useCallback(() => { + openStep(StepTypes.EMPTY_TRASH); + }, [openStep]); + if (step) { switch (step.type) { + case StepTypes.CUSTOM_FIELD_GROUPS: + return ; case StepTypes.EMPTY_TRASH: return ( { onBack={handleBack} /> ); - case StepTypes.CUSTOM_FIELD_GROUPS: - return ; default: } } @@ -128,6 +133,12 @@ const ActionsStep = React.memo(({ onClose }) => { })} )} + + + {t('common.actions', { + context: 'title', + })} + {withTrashEmptier && ( <> {(withSubscribe || withCustomFieldGroups) &&
} diff --git a/client/src/components/cards/CardModal/Communication.jsx b/client/src/components/cards/CardModal/Communication.jsx index 9d72f547..3b9bbc72 100644 --- a/client/src/components/cards/CardModal/Communication.jsx +++ b/client/src/components/cards/CardModal/Communication.jsx @@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next'; import { Menu, Tab } from 'semantic-ui-react'; import Comments from '../../comments/Comments'; -import Activities from '../../activities/Activities'; +import CardActivities from '../../activities/CardActivities'; import styles from './Communication.module.scss'; @@ -34,7 +34,7 @@ const Communication = React.memo(() => { })} ), - render: () => , + render: () => , }, ]; diff --git a/client/src/components/notifications/NotificationsStep/Item.module.scss b/client/src/components/notifications/NotificationsStep/Item.module.scss index b9358e9f..22cc4b1e 100644 --- a/client/src/components/notifications/NotificationsStep/Item.module.scss +++ b/client/src/components/notifications/NotificationsStep/Item.module.scss @@ -29,6 +29,7 @@ .content { display: inline-block; font-size: 13px; + line-height: 20px; min-height: 36px; overflow: hidden; padding: 0 4px 0 8px; @@ -40,7 +41,6 @@ .date { color: #6b808c; font-size: 12px; - line-height: 20px; } .wrapper { diff --git a/client/src/constants/ActionTypes.js b/client/src/constants/ActionTypes.js index f8754a73..567afcee 100644 --- a/client/src/constants/ActionTypes.js +++ b/client/src/constants/ActionTypes.js @@ -360,9 +360,12 @@ export default { /* Activities */ - ACTIVITIES_FETCH: 'ACTIVITIES_FETCH', - ACTIVITIES_FETCH__SUCCESS: 'ACTIVITIES_FETCH__SUCCESS', - ACTIVITIES_FETCH__FAILURE: 'ACTIVITIES_FETCH__FAILURE', + ACTIVITIES_IN_BOARD_FETCH: 'ACTIVITIES_IN_BOARD_FETCH', + ACTIVITIES_IN_BOARD_FETCH__SUCCESS: 'ACTIVITIES_IN_BOARD_FETCH__SUCCESS', + ACTIVITIES_IN_BOARD_FETCH__FAILURE: 'ACTIVITIES_IN_BOARD_FETCH__FAILURE', + ACTIVITIES_IN_CARD_FETCH: 'ACTIVITIES_IN_CARD_FETCH', + ACTIVITIES_IN_CARD_FETCH__SUCCESS: 'ACTIVITIES_IN_CARD_FETCH__SUCCESS', + ACTIVITIES_IN_CARD_FETCH__FAILURE: 'ACTIVITIES_IN_CARD_FETCH__FAILURE', ACTIVITY_CREATE_HANDLE: 'ACTIVITY_CREATE_HANDLE', /* Notifications */ diff --git a/client/src/constants/EntryActionTypes.js b/client/src/constants/EntryActionTypes.js index 2222c37d..9195eac1 100755 --- a/client/src/constants/EntryActionTypes.js +++ b/client/src/constants/EntryActionTypes.js @@ -255,6 +255,7 @@ export default { /* Activities */ + ACTIVITIES_IN_CURRENT_BOARD_FETCH: `${PREFIX}/ACTIVITIES_IN_CURRENT_BOARD_FETCH`, ACTIVITIES_IN_CURRENT_CARD_FETCH: `${PREFIX}/ACTIVITIES_IN_CURRENT_CARD_FETCH`, ACTIVITY_CREATE_HANDLE: `${PREFIX}/ACTIVITY_CREATE_HANDLE`, diff --git a/client/src/constants/ModalTypes.js b/client/src/constants/ModalTypes.js index fe59e827..66750985 100755 --- a/client/src/constants/ModalTypes.js +++ b/client/src/constants/ModalTypes.js @@ -8,6 +8,7 @@ const USER_SETTINGS = 'USER_SETTINGS'; const ADD_PROJECT = 'ADD_PROJECT'; const PROJECT_SETTINGS = 'PROJECT_SETTINGS'; const BOARD_SETTINGS = 'BOARD_SETTINGS'; +const BOARD_ACTIVITIES = 'BOARD_ACTIVITIES'; export default { ADMINISTRATION, @@ -15,4 +16,5 @@ export default { ADD_PROJECT, PROJECT_SETTINGS, BOARD_SETTINGS, + BOARD_ACTIVITIES, }; diff --git a/client/src/entry-actions/activities.js b/client/src/entry-actions/activities.js index 65fcfbda..6c786d30 100755 --- a/client/src/entry-actions/activities.js +++ b/client/src/entry-actions/activities.js @@ -5,6 +5,11 @@ import EntryActionTypes from '../constants/EntryActionTypes'; +const fetchActivitiesInCurrentBoard = () => ({ + type: EntryActionTypes.ACTIVITIES_IN_CURRENT_BOARD_FETCH, + payload: {}, +}); + const fetchActivitiesInCurrentCard = () => ({ type: EntryActionTypes.ACTIVITIES_IN_CURRENT_CARD_FETCH, payload: {}, @@ -18,6 +23,7 @@ const handleActivityCreate = (activity) => ({ }); export default { + fetchActivitiesInCurrentBoard, fetchActivitiesInCurrentCard, handleActivityCreate, }; diff --git a/client/src/entry-actions/modals.js b/client/src/entry-actions/modals.js index a36372b6..1c1224c3 100755 --- a/client/src/entry-actions/modals.js +++ b/client/src/entry-actions/modals.js @@ -47,6 +47,13 @@ const openBoardSettingsModal = (boardId) => ({ }, }); +const openBoardActivitiesModal = () => ({ + type: EntryActionTypes.MODAL_OPEN, + payload: { + type: ModalTypes.BOARD_ACTIVITIES, + }, +}); + const closeModal = () => ({ type: EntryActionTypes.MODAL_CLOSE, payload: {}, @@ -58,5 +65,6 @@ export default { openAddProjectModal, openProjectSettingsModal, openBoardSettingsModal, + openBoardActivitiesModal, closeModal, }; diff --git a/client/src/hooks/use-closable-modal.jsx b/client/src/hooks/use-closable-modal.jsx index 2e30802b..f165fecb 100644 --- a/client/src/hooks/use-closable-modal.jsx +++ b/client/src/hooks/use-closable-modal.jsx @@ -59,6 +59,7 @@ export default (initialClosableValue) => { onClose: undefined, }; + ClosableModal.Header = Modal.Header; ClosableModal.Content = Modal.Content; ClosableModal.Actions = Modal.Actions; diff --git a/client/src/locales/ar-YE/core.js b/client/src/locales/ar-YE/core.js index a2b52b17..3713654a 100644 --- a/client/src/locales/ar-YE/core.js +++ b/client/src/locales/ar-YE/core.js @@ -152,12 +152,12 @@ export default { time: 'الوقت', title: 'العنوان', userActions_title: 'إجراءات المستخدم', - userAddedThisCardToList: '<0>{{user}}<1> تمت إضافة هذه البطاقة إلى {{list}}', + userAddedThisCardToList: '<0>{{user}} تمت إضافة هذه البطاقة إلى {{list}}', userLeftNewCommentToCard: '<0>{{user}} ترك تعليق جديد «{{comment}}» إلى <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} انتقل <2>{{card}} من {{fromList}} إلى {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> نُقلت هذه البطاقة من {{fromList}} إلى {{toList}}', + '<0>{{user}} نُقلت هذه البطاقة من {{fromList}} إلى {{toList}}', username: 'اسم المستخدم', users: 'المستخدمين', viewer: 'مشاهد', diff --git a/client/src/locales/bg-BG/core.js b/client/src/locales/bg-BG/core.js index 82bc7b41..23a5e241 100644 --- a/client/src/locales/bg-BG/core.js +++ b/client/src/locales/bg-BG/core.js @@ -153,13 +153,13 @@ export default { time: 'Време', title: 'Заглавие', userActions_title: 'Потребителски действия', - userAddedThisCardToList: '<0>{{user}}<1> добави тази карта в {{list}}', + userAddedThisCardToList: '<0>{{user}} добави тази карта в {{list}}', userLeftNewCommentToCard: '<0>{{user}} остави нов коментар «{{comment}}» в <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} премести <2>{{card}} от {{fromList}} към {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> премести тази карта от {{fromList}} към {{toList}}', + '<0>{{user}} премести тази карта от {{fromList}} към {{toList}}', username: 'Потребителско име', users: 'Потребители', viewer: 'Зрител', diff --git a/client/src/locales/cs-CZ/core.js b/client/src/locales/cs-CZ/core.js index 5001995c..c0d74dc6 100644 --- a/client/src/locales/cs-CZ/core.js +++ b/client/src/locales/cs-CZ/core.js @@ -276,13 +276,13 @@ export default { unsavedChanges: 'Neuložené změny', uploadedImages: 'Nahrané obrázky', userActions_title: 'Akce uživatele', - userAddedThisCardToList: '<0>{{user}}<1> přidal kartu do {{list}}', + userAddedThisCardToList: '<0>{{user}} přidal kartu do {{list}}', userLeftNewCommentToCard: '<0>{{user}} zanechal nový komentář «{{comment}}» k <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} přesunul <2>{{card}} z {{fromList}} do {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> přesunul tuto kartu z {{fromList}} do {{toList}}', + '<0>{{user}} přesunul tuto kartu z {{fromList}} do {{toList}}', username: 'Uživatelské jméno', users: 'Uživatelé', viewer: 'Prohlížeč', diff --git a/client/src/locales/da-DK/core.js b/client/src/locales/da-DK/core.js index 508b726a..6de72c0a 100644 --- a/client/src/locales/da-DK/core.js +++ b/client/src/locales/da-DK/core.js @@ -154,13 +154,13 @@ export default { time: 'Tid', title: 'Overskrift', userActions_title: 'Brugerhandlinger', - userAddedThisCardToList: '<0>{{user}}<1> tilføjede kortet til {{list}}', + userAddedThisCardToList: '<0>{{user}} tilføjede kortet til {{list}}', userLeftNewCommentToCard: '<0>{{user}} skrevet en ny kommentar «{{comment}}» på <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} flyttede <2>{{card}} fra {{fromList}} til {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> flyttede kortet fra {{fromList}} til {{toList}}', + '<0>{{user}} flyttede kortet fra {{fromList}} til {{toList}}', username: 'Brugernavn', users: 'Brugere', viewer: 'Læser', diff --git a/client/src/locales/de-DE/core.js b/client/src/locales/de-DE/core.js index 57ff436e..a4798104 100644 --- a/client/src/locales/de-DE/core.js +++ b/client/src/locales/de-DE/core.js @@ -296,13 +296,13 @@ export default { unsavedChanges: 'Ungespeicherte Änderungen', uploadedImages: 'Hochgeladene Bilder', userActions_title: 'Benutzeraktionen', - userAddedThisCardToList: '<0>{{user}}<1> hat diese Karte hinzugefügt zu {{list}}', + userAddedThisCardToList: '<0>{{user}} hat diese Karte hinzugefügt zu {{list}}', userLeftNewCommentToCard: '<0>{{user}} hat einen neuen Kommentar verfasst: «{{comment}}» in <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} bewegte <2>{{card}} von {{fromList}} nach {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> bewegte diese Karte von {{fromList}} nach {{toList}}', + '<0>{{user}} bewegte diese Karte von {{fromList}} nach {{toList}}', username: 'Benutzername', users: 'Benutzer', viewer: 'Betrachter', diff --git a/client/src/locales/en-GB/core.js b/client/src/locales/en-GB/core.js index 0be9ccb0..ae9d0d4e 100644 --- a/client/src/locales/en-GB/core.js +++ b/client/src/locales/en-GB/core.js @@ -286,22 +286,28 @@ export default { unsavedChanges: 'Unsaved changes', uploadedImages: 'Uploaded images', userActions_title: 'User Actions', - userAddedThisCardToList: '<0>{{user}}<1> added this card to {{list}}', - userAddedUserToThisCard: '<0>{{actorUser}}<1> added {{addedUser}} to this card', + userAddedCardToList: '<0>{{user}} added <2>{{card}} to {{list}}', + userAddedThisCardToList: '<0>{{user}} added this card to {{list}}', + userAddedUserToCard: '<0>{{actorUser}} added {{addedUser}} to <4>{{card}}', + userAddedUserToThisCard: '<0>{{actorUser}} added {{addedUser}} to this card', userAddedYouToCard: '<0>{{user}} added you to <2>{{card}}', - userCompletedTaskOnThisCard: '<0>{{user}}<1> completed {{task}} on this card', - userJoinedThisCard: `<0>{{user}}<1> joined this card`, + userCompletedTaskOnCard: '<0>{{user}} completed {{task}} on <4>{{card}}', + userCompletedTaskOnThisCard: '<0>{{user}} completed {{task}} on this card', + userJoinedCard: `<0>{{user}} joined <2>{{card}}`, + userJoinedThisCard: `<0>{{user}} joined this card`, userLeftNewCommentToCard: '<0>{{user}} left a new comment «{{comment}}» to <2>{{card}}', - userLeftThisCard: '<0>{{user}}<1> left this card', - userMarkedTaskIncompleteOnThisCard: - '<0>{{user}}<1> marked {{task}} incomplete on this card', + userLeftCard: '<0>{{user}} left <2>{{card}}', + userLeftThisCard: '<0>{{user}} left this card', + userMarkedTaskIncompleteOnCard: + '<0>{{user}} marked {{task}} incomplete on <4>{{card}}', + userMarkedTaskIncompleteOnThisCard: '<0>{{user}} marked {{task}} incomplete on this card', userMovedCardFromListToList: '<0>{{user}} moved <2>{{card}} from {{fromList}} to {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> moved this card from {{fromList}} to {{toList}}', - userRemovedUserFromThisCard: - '<0>{{actorUser}}<1> removed {{removedUser}} from this card', + '<0>{{user}} moved this card from {{fromList}} to {{toList}}', + userRemovedUserFromCard: '<0>{{actorUser}} removed {{removedUser}} from <4>{{card}}', + userRemovedUserFromThisCard: '<0>{{actorUser}} removed {{removedUser}} from this card', username: 'Username', users: 'Users', viewer: 'Viewer', diff --git a/client/src/locales/en-US/core.js b/client/src/locales/en-US/core.js index cc858c3c..a42ac99d 100644 --- a/client/src/locales/en-US/core.js +++ b/client/src/locales/en-US/core.js @@ -281,22 +281,28 @@ export default { unsavedChanges: 'Unsaved changes', uploadedImages: 'Uploaded images', userActions_title: 'User Actions', - userAddedThisCardToList: '<0>{{user}}<1> added this card to {{list}}', - userAddedUserToThisCard: '<0>{{actorUser}}<1> added {{addedUser}} to this card', + userAddedCardToList: '<0>{{user}} added <2>{{card}} to {{list}}', + userAddedThisCardToList: '<0>{{user}} added this card to {{list}}', + userAddedUserToCard: '<0>{{actorUser}} added {{addedUser}} to <4>{{card}}', + userAddedUserToThisCard: '<0>{{actorUser}} added {{addedUser}} to this card', userAddedYouToCard: '<0>{{user}} added you to <2>{{card}}', - userCompletedTaskOnThisCard: '<0>{{user}}<1> completed {{task}} on this card', - userJoinedThisCard: `<0>{{user}}<1> joined this card`, + userCompletedTaskOnCard: '<0>{{user}} completed {{task}} on <4>{{card}}', + userCompletedTaskOnThisCard: '<0>{{user}} completed {{task}} on this card', + userJoinedCard: `<0>{{user}} joined <2>{{card}}`, + userJoinedThisCard: `<0>{{user}} joined this card`, userLeftNewCommentToCard: '<0>{{user}} left a new comment «{{comment}}» to <2>{{card}}', - userLeftThisCard: '<0>{{user}}<1> left this card', - userMarkedTaskIncompleteOnThisCard: - '<0>{{user}}<1> marked {{task}} incomplete on this card', + userLeftCard: '<0>{{user}} left <2>{{card}}', + userLeftThisCard: '<0>{{user}} left this card', + userMarkedTaskIncompleteOnCard: + '<0>{{user}} marked {{task}} incomplete on <4>{{card}}', + userMarkedTaskIncompleteOnThisCard: '<0>{{user}} marked {{task}} incomplete on this card', userMovedCardFromListToList: '<0>{{user}} moved <2>{{card}} from {{fromList}} to {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> moved this card from {{fromList}} to {{toList}}', - userRemovedUserFromThisCard: - '<0>{{actorUser}}<1> removed {{removedUser}} from this card', + '<0>{{user}} moved this card from {{fromList}} to {{toList}}', + userRemovedUserFromCard: '<0>{{actorUser}} removed {{removedUser}} from <4>{{card}}', + userRemovedUserFromThisCard: '<0>{{actorUser}} removed {{removedUser}} from this card', username: 'Username', users: 'Users', viewer: 'Viewer', diff --git a/client/src/locales/es-ES/core.js b/client/src/locales/es-ES/core.js index 41bb79e2..20bc484c 100644 --- a/client/src/locales/es-ES/core.js +++ b/client/src/locales/es-ES/core.js @@ -117,13 +117,13 @@ export default { time: 'Tiempo', title: 'Título', userActions_title: 'Acciones de Usuario', - userAddedThisCardToList: '<0>{{user}}<1> añadido a esta tarjeta en {{list}}', + userAddedThisCardToList: '<0>{{user}} añadido a esta tarjeta en {{list}}', userLeftNewCommentToCard: '<0>{{user}} dejó un nuevo comentario «{{comment}}» en <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} movió <2>{{card}} de {{fromList}} a {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> movió esta tarjeta de {{fromList}} a {{toList}}', + '<0>{{user}} movió esta tarjeta de {{fromList}} a {{toList}}', username: 'Nombre de usuario', users: 'Usuarios', writeComment: 'Escribir un comentario...', diff --git a/client/src/locales/fa-IR/core.js b/client/src/locales/fa-IR/core.js index 795ca8e0..100a71ba 100644 --- a/client/src/locales/fa-IR/core.js +++ b/client/src/locales/fa-IR/core.js @@ -153,13 +153,13 @@ export default { time: 'زمان', title: 'عنوان', userActions_title: 'اقدامات کاربر', - userAddedThisCardToList: '<0>{{user}}<1> این کارت را به {{list}} اضافه کرد', + userAddedThisCardToList: '<0>{{user}} این کارت را به {{list}} اضافه کرد', userLeftNewCommentToCard: '<0>{{user}} نظر جدید «{{comment}}» را به <2>{{card}} اضافه کرد', userMovedCardFromListToList: '<0>{{user}} <2>{{card}} را از {{fromList}} به {{toList}} منتقل کرد', userMovedThisCardFromListToList: - '<0>{{user}}<1> این کارت را از {{fromList}} به {{toList}} منتقل کرد', + '<0>{{user}} این کارت را از {{fromList}} به {{toList}} منتقل کرد', username: 'نام کاربری', users: 'کاربران', viewer: 'بیننده', diff --git a/client/src/locales/fr-FR/core.js b/client/src/locales/fr-FR/core.js index 74356971..f690c019 100644 --- a/client/src/locales/fr-FR/core.js +++ b/client/src/locales/fr-FR/core.js @@ -153,13 +153,13 @@ export default { time: 'Temps', title: 'Titre', userActions_title: "Actions de l'utilisateur", - userAddedThisCardToList: '<0>{{user}}<1> a ajouté cette carte à {{list}}', + userAddedThisCardToList: '<0>{{user}} a ajouté cette carte à {{list}}', userLeftNewCommentToCard: '<0>{{user}} a laissé un nouveau commentaire {{comment}} à <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} a déplacé <2>{{card}} de {{fromList}} vers {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> a déplacé cette carte de {{fromList}} vers {{toList}}', + '<0>{{user}} a déplacé cette carte de {{fromList}} vers {{toList}}', username: "Nom d'utilisateur", users: 'Utilisateurs', viewer: 'Spectateur', diff --git a/client/src/locales/hu-HU/core.js b/client/src/locales/hu-HU/core.js index 6f9fe79b..e7a00406 100644 --- a/client/src/locales/hu-HU/core.js +++ b/client/src/locales/hu-HU/core.js @@ -151,14 +151,13 @@ export default { time: 'Idő', title: 'Cím', userActions_title: 'Felhasználói műveletek', - userAddedThisCardToList: - '<0>{{user}}<1> hozzáadta ezt a kártyát a következőhöz: {{list}}', + userAddedThisCardToList: '<0>{{user}} hozzáadta ezt a kártyát a következőhöz: {{list}}', userLeftNewCommentToCard: '<0>{{user}} új kommentet hagyott itt: «{{comment}}» a következő kártyán: <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} áthelyezte ezt a kártyát innen: {{fromList}} ide: {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> áthelyezte ezt a kártyát innen: {{fromList}} ide: {{toList}}', + '<0>{{user}} áthelyezte ezt a kártyát innen: {{fromList}} ide: {{toList}}', username: 'Felhasználónév', users: 'Felhasználók', viewer: 'Néző', diff --git a/client/src/locales/id-ID/core.js b/client/src/locales/id-ID/core.js index 0c064f40..e5a154c0 100644 --- a/client/src/locales/id-ID/core.js +++ b/client/src/locales/id-ID/core.js @@ -147,12 +147,12 @@ export default { time: 'Waktu', title: 'Judul', userActions_title: 'Aksi Pengguna', - userAddedThisCardToList: '<0>{{user}}<1> menambahkan kartu ini ke {{list}}', + userAddedThisCardToList: '<0>{{user}} menambahkan kartu ini ke {{list}}', userLeftNewCommentToCard: '<0>{{user}} mengomentari «{{comment}}» di <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} memindahkan <2>{{card}} dari {{fromList}} ke {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> memindahkan kartu ini dari {{fromList}} ke {{toList}}', + '<0>{{user}} memindahkan kartu ini dari {{fromList}} ke {{toList}}', username: 'Username', users: 'Pengguna', viewer: 'Penglihat', diff --git a/client/src/locales/it-IT/core.js b/client/src/locales/it-IT/core.js index 21e3bb44..032cf709 100644 --- a/client/src/locales/it-IT/core.js +++ b/client/src/locales/it-IT/core.js @@ -284,13 +284,13 @@ export default { unsavedChanges: 'Modifiche non salvate', uploadedImages: 'Immagini caricate', userActions_title: 'Azioni utente', - userAddedThisCardToList: '<0>{{user}}<1> ha aggiunto questa scheda a {{list}}', + userAddedThisCardToList: '<0>{{user}} ha aggiunto questa scheda a {{list}}', userLeftNewCommentToCard: '<0>{{user}} ha lasciato un commento «{{comment}}» a <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} ha spostato <2>{{card}} da {{fromList}} a {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> ha spostato questa scheda da {{fromList}} a {{toList}}', + '<0>{{user}} ha spostato questa scheda da {{fromList}} a {{toList}}', username: 'Username', users: 'Utenti', viewer: 'Visualizzatore', diff --git a/client/src/locales/ja-JP/core.js b/client/src/locales/ja-JP/core.js index d6946dcb..8841b6dc 100644 --- a/client/src/locales/ja-JP/core.js +++ b/client/src/locales/ja-JP/core.js @@ -146,13 +146,13 @@ export default { time: '時間', title: 'タイトル', userActions_title: 'ユーザーのアクション', - userAddedThisCardToList: '<0>{{user}} 様が <1>{{list}} をこのカードに追加しました', + userAddedThisCardToList: '<0>{{user}} 様が {{list}} をこのカードに追加しました', userLeftNewCommentToCard: '<0>{{user}} 様が <2>{{card}} に新しいコメント «{{comment}}» を残しました', userMovedCardFromListToList: '<0>{{user}} 様が <2>{{card}} を {{fromList}} から {{toList}} に移動しました', userMovedThisCardFromListToList: - '<0>{{user}}<1> 様がこのカードを {{fromList}} から {{toList}} に移動しました', + '<0>{{user}} 様がこのカードを {{fromList}} から {{toList}} に移動しました', username: 'ユーザー名', users: 'ユーザー', viewer: 'ビューア', diff --git a/client/src/locales/ko-KR/core.js b/client/src/locales/ko-KR/core.js index 590a4ffe..e2022a4e 100644 --- a/client/src/locales/ko-KR/core.js +++ b/client/src/locales/ko-KR/core.js @@ -151,13 +151,13 @@ export default { time: '시간', title: '제목', userActions_title: '사용자 작업', - userAddedThisCardToList: '<0>{{user}}<1>님이 이 카드를 {{list}}에 추가했습니다', + userAddedThisCardToList: '<0>{{user}}님이 이 카드를 {{list}}에 추가했습니다', userLeftNewCommentToCard: '<0>{{user}}님이 <2>{{card}}에 새 댓글 «{{comment}}»을 남겼습니다', userMovedCardFromListToList: '<0>{{user}}님이 <2>{{card}}를 {{fromList}}에서 {{toList}}로 이동했습니다', userMovedThisCardFromListToList: - '<0>{{user}}<1> 님이 {{fromList}}에서 {{toList}}로 이 카드를 옮겼습니다', + '<0>{{user}} 님이 {{fromList}}에서 {{toList}}로 이 카드를 옮겼습니다', username: '사용자 이름', users: '사용자들', viewer: '뷰어', diff --git a/client/src/locales/nl-NL/core.js b/client/src/locales/nl-NL/core.js index 6f0f2697..f50d43b7 100644 --- a/client/src/locales/nl-NL/core.js +++ b/client/src/locales/nl-NL/core.js @@ -147,13 +147,13 @@ export default { time: 'Tijd', title: 'Titel', userActions_title: 'Gebruikersacties', - userAddedThisCardToList: '<0>{{user}}<1> heeft deze kaart toegevoegd aan {{list}}', + userAddedThisCardToList: '<0>{{user}} heeft deze kaart toegevoegd aan {{list}}', userLeftNewCommentToCard: '<0>{{user}} heeft een nieuwe opmerking achtergelaten «{{comment}}» bij <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} heeft <2>{{card}} verplaatst van {{fromList}} naar {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> heeft deze kaart verplaatst van {{fromList}} naar {{toList}}', + '<0>{{user}} heeft deze kaart verplaatst van {{fromList}} naar {{toList}}', username: 'Gebruikersnaam', users: 'Gebruikers', viewer: 'Kijker', diff --git a/client/src/locales/pl-PL/core.js b/client/src/locales/pl-PL/core.js index 70ac7cdf..b0af5cff 100644 --- a/client/src/locales/pl-PL/core.js +++ b/client/src/locales/pl-PL/core.js @@ -148,13 +148,13 @@ export default { time: 'Czas', title: 'Tytuł', userActions_title: 'Akcje użytkownika', - userAddedThisCardToList: '<0>{{user}}<1> dodał tę kartę w {{list}}', + userAddedThisCardToList: '<0>{{user}} dodał tę kartę w {{list}}', userLeftNewCommentToCard: '<0>{{user}} zamieścił nowy komentarz «{{comment}}» w <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} przeniósł <2>{{card}} z {{fromList}} do {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> przeniósł tę kartę z {{fromList}} do {{toList}}', + '<0>{{user}} przeniósł tę kartę z {{fromList}} do {{toList}}', username: 'Nazwa Użytkownika', users: 'Użytkownicy', viewer: 'Odwiedzający', diff --git a/client/src/locales/pt-BR/core.js b/client/src/locales/pt-BR/core.js index 6e088dad..84a2f16d 100644 --- a/client/src/locales/pt-BR/core.js +++ b/client/src/locales/pt-BR/core.js @@ -147,13 +147,13 @@ export default { time: 'Tempo', title: 'Título', userActions_title: 'Ações do Usuário', - userAddedThisCardToList: '<0>{{user}}<1> adicionou este cartão a {{list}}', + userAddedThisCardToList: '<0>{{user}} adicionou este cartão a {{list}}', userLeftNewCommentToCard: '<0>{{user}} deixou um novo comentário «{{comment}}» em <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} moveu <2>{{card}} de {{fromList}} para {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> moveu este cartão de {{fromList}} para {{toList}}', + '<0>{{user}} moveu este cartão de {{fromList}} para {{toList}}', username: 'Nome de usuário', users: 'Usuários', viewer: 'Visualizador', diff --git a/client/src/locales/ro-RO/core.js b/client/src/locales/ro-RO/core.js index 412cbea5..02d01ea5 100644 --- a/client/src/locales/ro-RO/core.js +++ b/client/src/locales/ro-RO/core.js @@ -147,13 +147,13 @@ export default { time: 'Timp', title: 'Titlu', userActions_title: 'Acțiunile utilizatorului', - userAddedThisCardToList: '<0>{{user}}<1> a adăugat acest card în {{list}}', + userAddedThisCardToList: '<0>{{user}} a adăugat acest card în {{list}}', userLeftNewCommentToCard: '<0>{{user}} a lăsat un nou comentariu «{{comment}}» în <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} a mutat <2>{{card}} din {{fromList}} în {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> a mutat aceast card din {{fromList}} în {{toList}}', + '<0>{{user}} a mutat aceast card din {{fromList}} în {{toList}}', username: 'Nume utilizator', users: 'Utilizatori', viewer: 'Vizualizator', diff --git a/client/src/locales/ru-RU/core.js b/client/src/locales/ru-RU/core.js index 2ee40787..8a99fa58 100644 --- a/client/src/locales/ru-RU/core.js +++ b/client/src/locales/ru-RU/core.js @@ -284,13 +284,13 @@ export default { unsavedChanges: 'Несохранённые изменения', uploadedImages: 'Загруженные изображения', userActions_title: 'Действия с пользователем', - userAddedThisCardToList: '<0>{{user}}<1> добавил(а) эту карточку в {{list}}', + userAddedThisCardToList: '<0>{{user}} добавил(а) эту карточку в {{list}}', userLeftNewCommentToCard: '<0>{{user}} оставил(а) комментарий «{{comment}}» к <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} переместил(а) <2>{{card}} из {{fromList}} в {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> переместил(а) эту карточку из {{fromList}} в {{toList}}', + '<0>{{user}} переместил(а) эту карточку из {{fromList}} в {{toList}}', username: 'Имя пользователя', users: 'Пользователи', viewer: 'Читатель', diff --git a/client/src/locales/sk-SK/core.js b/client/src/locales/sk-SK/core.js index b344cabd..1999e0fd 100644 --- a/client/src/locales/sk-SK/core.js +++ b/client/src/locales/sk-SK/core.js @@ -132,13 +132,13 @@ export default { time: 'Čas', title: 'Názov', userActions_title: 'Akcie na používateľovi', - userAddedThisCardToList: '<0>{{user}}<1> pridal kartu do {{list}}', + userAddedThisCardToList: '<0>{{user}} pridal kartu do {{list}}', userLeftNewCommentToCard: '<0>{{user}} zanechal nový komentár «{{comment}}» k <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} presunul <2>{{card}} z {{fromList}} do {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> presunul túto kartu z {{fromList}} do {{toList}}', + '<0>{{user}} presunul túto kartu z {{fromList}} do {{toList}}', username: 'Používateľské meno', users: 'Používatelia', writeComment: 'Napísať komentár...', diff --git a/client/src/locales/sr-Cyrl-CS/core.js b/client/src/locales/sr-Cyrl-CS/core.js index 87a47917..46b143be 100644 --- a/client/src/locales/sr-Cyrl-CS/core.js +++ b/client/src/locales/sr-Cyrl-CS/core.js @@ -152,13 +152,13 @@ export default { time: 'Време', title: 'Наслов', userActions_title: 'Корисничке радње', - userAddedThisCardToList: '<0>{{user}}<1> је додао ову картицу на {{list}}', + userAddedThisCardToList: '<0>{{user}} је додао ову картицу на {{list}}', userLeftNewCommentToCard: '<0>{{user}} је оставио нови коментар «{{comment}}» у <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} је преместио <2>{{card}} са {{fromList}} у {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> је преместио ову картицу са {{fromList}} на {{toList}}', + '<0>{{user}} је преместио ову картицу са {{fromList}} на {{toList}}', username: 'Корисничко име', users: 'Корисници', viewer: 'Прегледач', diff --git a/client/src/locales/sr-Latn-CS/core.js b/client/src/locales/sr-Latn-CS/core.js index 3bdf748b..4ca706f7 100644 --- a/client/src/locales/sr-Latn-CS/core.js +++ b/client/src/locales/sr-Latn-CS/core.js @@ -152,13 +152,13 @@ export default { time: 'Vreme', title: 'Naslov', userActions_title: 'Korisničke radnje', - userAddedThisCardToList: '<0>{{user}}<1> je dodao ovu karticu na {{list}}', + userAddedThisCardToList: '<0>{{user}} je dodao ovu karticu na {{list}}', userLeftNewCommentToCard: '<0>{{user}} je ostavio novi komentar «{{comment}}» u <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} je premestio <2>{{card}} sa {{fromList}} u {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> je premestio ovu karticu sa {{fromList}} na {{toList}}', + '<0>{{user}} je premestio ovu karticu sa {{fromList}} na {{toList}}', username: 'Korisničko ime', users: 'Korisnici', viewer: 'Pregledač', diff --git a/client/src/locales/sv-SE/core.js b/client/src/locales/sv-SE/core.js index 0a8c729e..0310ce06 100644 --- a/client/src/locales/sv-SE/core.js +++ b/client/src/locales/sv-SE/core.js @@ -133,13 +133,13 @@ export default { time: 'Tid', title: 'Titel', userActions_title: 'Användaråtgärder', - userAddedThisCardToList: '<0>{{user}}<1> lade till detta kort i {{list}}', + userAddedThisCardToList: '<0>{{user}} lade till detta kort i {{list}}', userLeftNewCommentToCard: '<0>{{user}} lämnade en ny kommentar «{{comment}}» på <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} flyttade <2>{{card}} från {{fromList}} till {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> flyttade detta kort från {{fromList}} till {{toList}}', + '<0>{{user}} flyttade detta kort från {{fromList}} till {{toList}}', username: 'Användarnamn', users: 'Användare', writeComment: 'Skriv en kommentar...', diff --git a/client/src/locales/tr-TR/core.js b/client/src/locales/tr-TR/core.js index b667f2b5..2f2de1fc 100644 --- a/client/src/locales/tr-TR/core.js +++ b/client/src/locales/tr-TR/core.js @@ -134,13 +134,13 @@ export default { time: 'zaman', title: 'başlık', userActions_title: 'Kullanıcı İşlemleri', - userAddedThisCardToList: '<0>{{user}}<1> bu kartı {{list} listesine ekledi', + userAddedThisCardToList: '<0>{{user}} bu kartı {{list}} listesine ekledi', userLeftNewCommentToCard: '<0>{{user}} yeni bir yorum yazdı: <2>{{card} kartına «{{comment}}»', userMovedCardFromListToList: '<0>{{user}}, <2>{{card}> kartını {{fromList}} listesinden {{toList}} listesine taşıdı', userMovedThisCardFromListToList: - '<0>{{user}}<1> bu kartı {{fromList}} konumundan {{toList}} konumuna taşıdı', + '<0>{{user}} bu kartı {{fromList}} konumundan {{toList}} konumuna taşıdı', username: 'kullanıcı adı', users: 'kullanıcı', writeComment: 'Yorum yazın...', diff --git a/client/src/locales/uk-UA/core.js b/client/src/locales/uk-UA/core.js index b3b384ea..bdf293be 100644 --- a/client/src/locales/uk-UA/core.js +++ b/client/src/locales/uk-UA/core.js @@ -281,13 +281,13 @@ export default { unsavedChanges: 'Незбережені зміни', uploadedImages: 'Завантажені зображення', userActions_title: 'Дії користувача', - userAddedThisCardToList: '<0>{{user}}<1> додав(ла) цю картку до {{list}}', + userAddedThisCardToList: '<0>{{user}} додав(ла) цю картку до {{list}}', userLeftNewCommentToCard: '<0>{{user}} залишив(ла) новий коментар «{{comment}}» до <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} перемістив(ла) <2>{{card}} з {{fromList}} в {{toList}}', userMovedThisCardFromListToList: - '<0>{{user}}<1> перемістив(ла) цю картку з {{fromList}} в {{toList}}', + '<0>{{user}} перемістив(ла) цю картку з {{fromList}} в {{toList}}', username: "Ім'я користувача", users: 'Користувачі', viewer: 'Переглядач', diff --git a/client/src/locales/uz-UZ/core.js b/client/src/locales/uz-UZ/core.js index af7245b4..9048a968 100644 --- a/client/src/locales/uz-UZ/core.js +++ b/client/src/locales/uz-UZ/core.js @@ -130,13 +130,13 @@ export default { time: 'Vaqt', title: 'Sarlavha', userActions_title: 'Foydalanuvchi Amallari', - userAddedThisCardToList: "<1>Ushbu kartani {{list}} ga<0>{{user}} qo'shdi", + userAddedThisCardToList: "Ushbu kartani {{list}} ga<0>{{user}} qo'shdi", userLeftNewCommentToCard: '<0>{{user}} <2>{{card}} ga yangi izoh qoldirdi «{{comment}}»', userMovedCardFromListToList: "<0>{{user}} <2>{{card}} ni {{fromList}} dan {{toList}} ga ko'chirdi", userMovedThisCardFromListToList: - "<0>{{user}}<1> ushbu kartani {{fromList}} dan {{toList}} ga ko'chirdi", + "<0>{{user}} ushbu kartani {{fromList}} dan {{toList}} ga ko'chirdi", username: 'Foydalanuvchi nomi', users: 'Foydalanuvchilar', writeComment: 'Izoh yozish...', diff --git a/client/src/locales/zh-CN/core.js b/client/src/locales/zh-CN/core.js index d8f3485d..665b3182 100644 --- a/client/src/locales/zh-CN/core.js +++ b/client/src/locales/zh-CN/core.js @@ -143,12 +143,11 @@ export default { time: '时间', title: '标题', userActions_title: '用户操作', - userAddedThisCardToList: '<0>{{user}}<1> 向列表 {{list}} 添加了该卡片', - userLeftNewCommentToCard: '<0>{{user}} 给 {{card}} 添加了一个新评论 «{{comment}}»', + userAddedThisCardToList: '<0>{{user}} 向列表 {{list}} 添加了该卡片', + userLeftNewCommentToCard: '<0>{{user}} 给 <2>{{card}} 添加了一个新评论 «{{comment}}»', userMovedCardFromListToList: '<0>{{user}} 将卡片 <2>{{card}} 从 {{fromList}} 移动到 {{toList}}', - userMovedThisCardFromListToList: - '<0>{{user}}<1> 将该卡片从 {{fromList}} 移动到 {{toList}}', + userMovedThisCardFromListToList: '<0>{{user}} 将该卡片从 {{fromList}} 移动到 {{toList}}', username: '用户名', users: '用户', viewer: '视图', diff --git a/client/src/locales/zh-TW/core.js b/client/src/locales/zh-TW/core.js index 7b2e9b69..91fe857c 100644 --- a/client/src/locales/zh-TW/core.js +++ b/client/src/locales/zh-TW/core.js @@ -143,12 +143,12 @@ export default { time: '時間', title: '標題', userActions_title: '使用者操作', - userAddedThisCardToList: '<0>{{user}}<1> 向列表 {{list}} 添加了該卡片', - userLeftNewCommentToCard: '<0>{{user}} 給 {{card}} 添加了一條新評論 「{{comment}}」', + userAddedThisCardToList: '<0>{{user}} 向列表 {{list}} 添加了該卡片', + userLeftNewCommentToCard: + '<0>{{user}} 給 <2>{{card}} 添加了一條新評論 「{{comment}}」', userMovedCardFromListToList: '<0>{{user}} 將卡片 <2>{{card}} 從 {{fromList}} 移動到 {{toList}}', - userMovedThisCardFromListToList: - '<0>{{user}}<1> 將該卡片從 {{fromList}} 移動到 {{toList}}', + userMovedThisCardFromListToList: '<0>{{user}} 將該卡片從 {{fromList}} 移動到 {{toList}}', username: '使用者名稱', users: '使用者', viewer: '檢視', diff --git a/client/src/models/Activity.js b/client/src/models/Activity.js index 71a630b2..a4d4a01c 100755 --- a/client/src/models/Activity.js +++ b/client/src/models/Activity.js @@ -18,6 +18,11 @@ export default class extends BaseModel { createdAt: attr({ getDefault: () => new Date(), }), + boardId: fk({ + to: 'Board', + as: 'board', + relatedName: 'activities', + }), cardId: fk({ to: 'Card', as: 'card', @@ -37,7 +42,8 @@ export default class extends BaseModel { break; case ActionTypes.LIST_CARDS_MOVE__SUCCESS: - case ActionTypes.ACTIVITIES_FETCH__SUCCESS: + case ActionTypes.ACTIVITIES_IN_BOARD_FETCH__SUCCESS: + case ActionTypes.ACTIVITIES_IN_CARD_FETCH__SUCCESS: payload.activities.forEach((activity) => { Activity.upsert(activity); }); diff --git a/client/src/models/Board.js b/client/src/models/Board.js index b29e098c..429178b7 100755 --- a/client/src/models/Board.js +++ b/client/src/models/Board.js @@ -9,6 +9,7 @@ import BaseModel from './BaseModel'; import buildSearchParts from '../utils/build-search-parts'; import { isListFinite } from '../utils/record-helpers'; import ActionTypes from '../constants/ActionTypes'; +import Config from '../constants/Config'; import { BoardContexts, BoardViews } from '../constants/Enums'; const prepareFetchedBoard = (board) => ({ @@ -39,6 +40,15 @@ export default class extends BaseModel { isFetching: attr({ getDefault: () => null, }), + lastActivityId: attr({ + getDefault: () => null, + }), + isActivitiesFetching: attr({ + getDefault: () => false, + }), + isAllActivitiesFetched: attr({ + getDefault: () => null, + }), projectId: fk({ to: 'Project', as: 'project', @@ -241,6 +251,22 @@ export default class extends BaseModel { case ActionTypes.LABEL_FROM_BOARD_FILTER_REMOVE: Board.withId(payload.boardId).filterLabels.remove(payload.id); + break; + case ActionTypes.ACTIVITIES_IN_BOARD_FETCH: + Board.withId(payload.boardId).update({ + isActivitiesFetching: true, + }); + + break; + case ActionTypes.ACTIVITIES_IN_BOARD_FETCH__SUCCESS: + Board.withId(payload.boardId).update({ + isActivitiesFetching: false, + isAllActivitiesFetched: payload.activities.length < Config.ACTIVITIES_LIMIT, + ...(payload.activities.length > 0 && { + lastActivityId: payload.activities[payload.activities.length - 1].id, + }), + }); + break; default: } @@ -266,6 +292,10 @@ export default class extends BaseModel { return this.customFieldGroups.orderBy(['position', 'id.length', 'id']); } + getActivitiesQuerySet() { + return this.activities.orderBy(['id.length', 'id'], ['desc', 'desc']); + } + getUnreadNotificationsQuerySet() { return this.notifications.filter({ isRead: false, @@ -347,6 +377,30 @@ export default class extends BaseModel { return cardModels; } + getActivitiesModelArray() { + if (this.isAllActivitiesFetched === null) { + return []; + } + + const activityModels = this.getActivitiesQuerySet().toModelArray(); + + if (this.lastActivityId && this.isAllActivitiesFetched === false) { + return activityModels.filter((activityModel) => { + if (activityModel.id.length > this.lastActivityId.length) { + return true; + } + + if (activityModel.id.length < this.lastActivityId.length) { + return false; + } + + return activityModel.id >= this.lastActivityId; + }); + } + + return activityModels; + } + hasMembershipWithUserId(userId) { return this.memberships .filter({ diff --git a/client/src/models/Card.js b/client/src/models/Card.js index 5981aac8..8fe5b4bb 100755 --- a/client/src/models/Card.js +++ b/client/src/models/Card.js @@ -402,13 +402,13 @@ export default class extends BaseModel { }); break; - case ActionTypes.ACTIVITIES_FETCH: + case ActionTypes.ACTIVITIES_IN_CARD_FETCH: Card.withId(payload.cardId).update({ isActivitiesFetching: true, }); break; - case ActionTypes.ACTIVITIES_FETCH__SUCCESS: + case ActionTypes.ACTIVITIES_IN_CARD_FETCH__SUCCESS: Card.withId(payload.cardId).update({ isActivitiesFetching: false, isAllActivitiesFetched: payload.activities.length < Config.ACTIVITIES_LIMIT, @@ -607,7 +607,6 @@ export default class extends BaseModel { this.customFieldValues.delete(); this.comments.delete(); - this.activities.delete(); } deleteWithClearable() { diff --git a/client/src/models/User.js b/client/src/models/User.js index 3eb93569..5fd06e66 100755 --- a/client/src/models/User.js +++ b/client/src/models/User.js @@ -295,7 +295,8 @@ export default class extends BaseModel { case ActionTypes.CARD_CREATE_HANDLE: case ActionTypes.COMMENTS_FETCH__SUCCESS: case ActionTypes.COMMENT_CREATE_HANDLE: - case ActionTypes.ACTIVITIES_FETCH__SUCCESS: + case ActionTypes.ACTIVITIES_IN_BOARD_FETCH__SUCCESS: + case ActionTypes.ACTIVITIES_IN_CARD_FETCH__SUCCESS: case ActionTypes.NOTIFICATION_CREATE_HANDLE: payload.users.forEach((user) => { User.upsert(user); diff --git a/client/src/sagas/core/services/activities.js b/client/src/sagas/core/services/activities.js index a72ebead..c319c1a4 100644 --- a/client/src/sagas/core/services/activities.js +++ b/client/src/sagas/core/services/activities.js @@ -10,10 +10,10 @@ import selectors from '../../../selectors'; import actions from '../../../actions'; import api from '../../../api'; -export function* fetchActivities(cardId) { - const { lastActivityId } = yield select(selectors.selectCardById, cardId); +export function* fetchActivitiesInBoard(boardId) { + const { lastActivityId } = yield select(selectors.selectBoardById, boardId); - yield put(actions.fetchActivities(cardId)); + yield put(actions.fetchActivitiesInBoard(boardId)); let activities; let users; @@ -22,21 +22,50 @@ export function* fetchActivities(cardId) { ({ items: activities, included: { users }, - } = yield call(request, api.getActivities, cardId, { + } = yield call(request, api.getActivitiesInBoard, boardId, { beforeId: lastActivityId || undefined, })); } catch (error) { - yield put(actions.fetchActivities.failure(cardId, error)); + yield put(actions.fetchActivitiesInBoard.failure(boardId, error)); return; } - yield put(actions.fetchActivities.success(cardId, activities, users)); + yield put(actions.fetchActivitiesInBoard.success(boardId, activities, users)); +} + +export function* fetchActivitiesInCurrentBoard() { + const { boardId } = yield select(selectors.selectPath); + + yield call(fetchActivitiesInBoard, boardId); +} + +export function* fetchActivitiesInCard(cardId) { + const { lastActivityId } = yield select(selectors.selectCardById, cardId); + + yield put(actions.fetchActivitiesInCard(cardId)); + + let activities; + let users; + + try { + ({ + items: activities, + included: { users }, + } = yield call(request, api.getActivitiesInCard, cardId, { + beforeId: lastActivityId || undefined, + })); + } catch (error) { + yield put(actions.fetchActivitiesInCard.failure(cardId, error)); + return; + } + + yield put(actions.fetchActivitiesInCard.success(cardId, activities, users)); } export function* fetchActivitiesInCurrentCard() { const { cardId } = yield select(selectors.selectPath); - yield call(fetchActivities, cardId); + yield call(fetchActivitiesInCard, cardId); } export function* handleActivityCreate(activity) { @@ -44,7 +73,9 @@ export function* handleActivityCreate(activity) { } export default { - fetchActivities, + fetchActivitiesInBoard, + fetchActivitiesInCurrentBoard, + fetchActivitiesInCard, fetchActivitiesInCurrentCard, handleActivityCreate, }; diff --git a/client/src/sagas/core/watchers/activities.js b/client/src/sagas/core/watchers/activities.js index a7a05ae1..5366d88d 100644 --- a/client/src/sagas/core/watchers/activities.js +++ b/client/src/sagas/core/watchers/activities.js @@ -10,6 +10,9 @@ import EntryActionTypes from '../../../constants/EntryActionTypes'; export default function* activitiesWatchers() { yield all([ + takeEvery(EntryActionTypes.ACTIVITIES_IN_CURRENT_BOARD_FETCH, () => + services.fetchActivitiesInCurrentBoard(), + ), takeEvery(EntryActionTypes.ACTIVITIES_IN_CURRENT_CARD_FETCH, () => services.fetchActivitiesInCurrentCard(), ), diff --git a/client/src/selectors/boards.js b/client/src/selectors/boards.js index 022d808d..c636d348 100644 --- a/client/src/selectors/boards.js +++ b/client/src/selectors/boards.js @@ -383,6 +383,24 @@ export const selectCustomFieldGroupsForCurrentBoard = createSelector( }, ); +export const selectActivityIdsForCurrentBoard = createSelector( + orm, + (state) => selectPath(state).boardId, + ({ Board }, id) => { + if (!id) { + return id; + } + + const boardModel = Board.withId(id); + + if (!boardModel) { + return boardModel; + } + + return boardModel.getActivitiesModelArray().map((activity) => activity.id); + }, +); + export const selectFilterUserIdsForCurrentBoard = createSelector( orm, (state) => selectPath(state).boardId, @@ -447,6 +465,7 @@ export default { selectFilteredCardIdsForCurrentBoard, selectCustomFieldGroupIdsForCurrentBoard, selectCustomFieldGroupsForCurrentBoard, + selectActivityIdsForCurrentBoard, selectFilterUserIdsForCurrentBoard, selectFilterLabelIdsForCurrentBoard, selectIsBoardWithIdExists, diff --git a/server/api/controllers/actions/index-in-board.js b/server/api/controllers/actions/index-in-board.js new file mode 100755 index 00000000..91312bd8 --- /dev/null +++ b/server/api/controllers/actions/index-in-board.js @@ -0,0 +1,68 @@ +/*! + * Copyright (c) 2024 PLANKA Software GmbH + * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md + */ + +const { idInput } = require('../../../utils/inputs'); + +const Errors = { + BOARD_NOT_FOUND: { + boardNotFound: 'Board not found', + }, +}; + +module.exports = { + inputs: { + boardId: { + ...idInput, + required: true, + }, + beforeId: idInput, + }, + + exits: { + boardNotFound: { + responseType: 'notFound', + }, + }, + + async fn(inputs) { + const { currentUser } = this.req; + + const { board, project } = await sails.helpers.boards + .getPathToProjectById(inputs.boardId) + .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND); + + const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId( + board.id, + currentUser.id, + ); + + if (!boardMembership) { + if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) { + const isProjectManager = await sails.helpers.users.isProjectManager( + currentUser.id, + project.id, + ); + + if (!isProjectManager) { + throw Errors.BOARD_NOT_FOUND; // Forbidden + } + } + } + + const actions = await Action.qm.getByBoardId(board.id, { + beforeId: inputs.beforeId, + }); + + const userIds = sails.helpers.utils.mapRecords(actions, 'userId', true, true); + const users = await User.qm.getByIds(userIds); + + return { + items: actions, + included: { + users: sails.helpers.users.presentMany(users, currentUser), + }, + }; + }, +}; diff --git a/server/api/controllers/actions/index.js b/server/api/controllers/actions/index-in-card.js similarity index 100% rename from server/api/controllers/actions/index.js rename to server/api/controllers/actions/index-in-card.js diff --git a/server/api/helpers/actions/create-one.js b/server/api/helpers/actions/create-one.js index d90b4020..d3d448cb 100644 --- a/server/api/helpers/actions/create-one.js +++ b/server/api/helpers/actions/create-one.js @@ -115,6 +115,7 @@ module.exports = { const action = await Action.qm.createOne({ ...values, + boardId: values.card.boardId, cardId: values.card.id, userId: values.user.id, }); @@ -149,10 +150,7 @@ module.exports = { values: { action, type: action.type, - data: { - ...action.data, - card: _.pick(values.card, ['name']), - }, + data: action.data, userId: action.data.user.id, creatorUser: values.user, card: values.card, @@ -182,10 +180,7 @@ module.exports = { userId, action, type: action.type, - data: { - ...action.data, - card: _.pick(values.card, ['name']), - }, + data: action.data, creatorUser: values.user, card: values.card, }, diff --git a/server/api/helpers/boards/delete-related.js b/server/api/helpers/boards/delete-related.js index 24f0e875..5828172e 100644 --- a/server/api/helpers/boards/delete-related.js +++ b/server/api/helpers/boards/delete-related.js @@ -35,6 +35,15 @@ module.exports = { await sails.helpers.lists.deleteRelated(lists); + await Action.qm.update( + { + boardId: boardIdOrIds, + }, + { + boardId: null, + }, + ); + await NotificationService.qm.delete({ boardId: boardIdOrIds, }); diff --git a/server/api/helpers/card-memberships/create-one.js b/server/api/helpers/card-memberships/create-one.js index b66fd2fb..e71c2e0d 100644 --- a/server/api/helpers/card-memberships/create-one.js +++ b/server/api/helpers/card-memberships/create-one.js @@ -110,6 +110,7 @@ module.exports = { type: Action.Types.ADD_MEMBER_TO_CARD, data: { user: _.pick(values.user, ['id', 'name']), + card: _.pick(values.card, ['name']), }, user: inputs.actorUser, card: values.card, diff --git a/server/api/helpers/card-memberships/delete-one.js b/server/api/helpers/card-memberships/delete-one.js index 659bb90a..4d07111e 100644 --- a/server/api/helpers/card-memberships/delete-one.js +++ b/server/api/helpers/card-memberships/delete-one.js @@ -86,6 +86,7 @@ module.exports = { type: Action.Types.REMOVE_MEMBER_FROM_CARD, data: { user: _.pick(inputs.user, ['id', 'name']), + card: _.pick(inputs.card, ['name']), }, user: inputs.actorUser, card: inputs.card, diff --git a/server/api/helpers/cards/create-one.js b/server/api/helpers/cards/create-one.js index 0d721e1c..ae05a43e 100644 --- a/server/api/helpers/cards/create-one.js +++ b/server/api/helpers/cards/create-one.js @@ -124,6 +124,7 @@ module.exports = { card, type: Action.Types.CREATE_CARD, data: { + card: _.pick(card, ['name']), list: _.pick(values.list, ['id', 'type', 'name']), }, user: values.creatorUser, diff --git a/server/api/helpers/cards/duplicate-one.js b/server/api/helpers/cards/duplicate-one.js index 317e68e6..3c0f3970 100644 --- a/server/api/helpers/cards/duplicate-one.js +++ b/server/api/helpers/cards/duplicate-one.js @@ -276,6 +276,7 @@ module.exports = { card, type: Action.Types.CREATE_CARD, // TODO: introduce separate type? data: { + card: _.pick(card, ['name']), list: _.pick(inputs.list, ['id', 'type', 'name']), }, user: values.creatorUser, diff --git a/server/api/helpers/cards/update-one.js b/server/api/helpers/cards/update-one.js index 84cf8814..26071782 100644 --- a/server/api/helpers/cards/update-one.js +++ b/server/api/helpers/cards/update-one.js @@ -463,6 +463,7 @@ module.exports = { card, type: Action.Types.MOVE_CARD, data: { + card: _.pick(card, ['name']), fromList: _.pick(inputs.list, ['id', 'type', 'name']), toList: _.pick(values.list, ['id', 'type', 'name']), }, diff --git a/server/api/helpers/tasks/update-one.js b/server/api/helpers/tasks/update-one.js index 6261398f..c503e915 100644 --- a/server/api/helpers/tasks/update-one.js +++ b/server/api/helpers/tasks/update-one.js @@ -138,6 +138,7 @@ module.exports = { values: { type: task.isCompleted ? Action.Types.COMPLETE_TASK : Action.Types.UNCOMPLETE_TASK, data: { + card: _.pick(inputs.card, ['name']), task: _.pick(task, ['id', 'name']), }, user: inputs.actorUser, diff --git a/server/api/hooks/query-methods/models/Action.js b/server/api/hooks/query-methods/models/Action.js index b445a6d0..25a58b95 100644 --- a/server/api/hooks/query-methods/models/Action.js +++ b/server/api/hooks/query-methods/models/Action.js @@ -11,6 +11,20 @@ const create = (arrayOfValues) => Action.createEach(arrayOfValues).fetch(); const createOne = (values) => Action.create({ ...values }).fetch(); +const getByBoardId = (boardId, { beforeId } = {}) => { + const criteria = { + boardId, + }; + + if (beforeId) { + criteria.id = { + '<': beforeId, + }; + } + + return Action.find(criteria).sort('id DESC').limit(LIMIT); +}; + const getByCardId = (cardId, { beforeId } = {}) => { const criteria = { cardId, @@ -33,6 +47,7 @@ const delete_ = (criteria) => Action.destroy(criteria).fetch(); module.exports = { create, createOne, + getByBoardId, getByCardId, update, delete: delete_, diff --git a/server/api/models/Action.js b/server/api/models/Action.js index 955bf812..bc742385 100755 --- a/server/api/models/Action.js +++ b/server/api/models/Action.js @@ -52,6 +52,10 @@ module.exports = { // ╠═╣╚═╗╚═╗║ ║║ ║╠═╣ ║ ║║ ║║║║╚═╗ // ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝ + boardId: { + model: 'Board', + columnName: 'board_id', + }, cardId: { model: 'Card', required: true, diff --git a/server/config/routes.js b/server/config/routes.js index 3307beb3..ba2292bb 100644 --- a/server/config/routes.js +++ b/server/config/routes.js @@ -163,7 +163,8 @@ module.exports.routes = { 'PATCH /api/comments/:id': 'comments/update', 'DELETE /api/comments/:id': 'comments/delete', - 'GET /api/cards/:cardId/actions': 'actions/index', + 'GET /api/boards/:boardId/actions': 'actions/index-in-board', + 'GET /api/cards/:cardId/actions': 'actions/index-in-card', 'GET /api/notifications': 'notifications/index', 'GET /api/notifications/:id': 'notifications/show', diff --git a/server/db/migrations/20250522151122_add_board_activity_log.js b/server/db/migrations/20250522151122_add_board_activity_log.js new file mode 100644 index 00000000..feaf095c --- /dev/null +++ b/server/db/migrations/20250522151122_add_board_activity_log.js @@ -0,0 +1,30 @@ +/*! + * Copyright (c) 2024 PLANKA Software GmbH + * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md + */ + +exports.up = async (knex) => { + await knex.schema.alterTable('action', (table) => { + /* Columns */ + + table.bigInteger('board_id'); + + /* Indexes */ + + table.index('board_id'); + }); + + return knex.raw(` + UPDATE action + SET + board_id = card.board_id, + data = data || jsonb_build_object('card', jsonb_build_object('name', card.name)) + FROM card + WHERE action.card_id = card.id; + `); +}; + +exports.down = (knex) => + knex.schema.table('action', (table) => { + table.dropColumn('board_id'); + });