mirror of
https://github.com/mattermost/focalboard.git
synced 2025-03-26 20:53:55 +02:00
Refactor "..." menu into component & add to Calendar view (#3321)
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
parent
46fdbf9048
commit
3bbecc8117
@ -63,6 +63,11 @@
|
||||
"Calculations.Options.range.label": "Range",
|
||||
"Calculations.Options.sum.displayName": "Sum",
|
||||
"Calculations.Options.sum.label": "Sum",
|
||||
"CalendarCard.untitled": "Untitled",
|
||||
"CardActionsMenu.copiedLink": "Copied!",
|
||||
"CardActionsMenu.copyLink": "Copy link",
|
||||
"CardActionsMenu.delete": "Delete",
|
||||
"CardActionsMenu.duplicate": "Duplicate",
|
||||
"CardBadges.title-checkboxes": "Checkboxes",
|
||||
"CardBadges.title-comments": "Comments",
|
||||
"CardBadges.title-description": "This card has a description",
|
||||
@ -87,8 +92,6 @@
|
||||
"CardDetailProperty.property-deleted": "Deleted {propertyName} successfully!",
|
||||
"CardDetailProperty.property-name-change-subtext": "type from \"{oldPropType}\" to \"{newPropType}\"",
|
||||
"CardDetial.limited-link": "Learn more about our plans.",
|
||||
"CardDialog.copiedLink": "Copied!",
|
||||
"CardDialog.copyLink": "Copy link",
|
||||
"CardDialog.delete-confirmation-dialog-button-text": "Delete",
|
||||
"CardDialog.delete-confirmation-dialog-heading": "Confirm card delete!",
|
||||
"CardDialog.editing-template": "You're editing a template.",
|
||||
@ -144,17 +147,9 @@
|
||||
"FindBoardsDialog.NoResultsSubtext": "Check the spelling or try another search.",
|
||||
"FindBoardsDialog.SubTitle": "Type to find a board. Use <b>UP/DOWN</b> to browse. <b>ENTER</b> to select, <b>ESC</b> to dismiss",
|
||||
"FindBoardsDialog.Title": "Find Boards",
|
||||
"GalleryCard.copiedLink": "Copied!",
|
||||
"GalleryCard.copyLink": "Copy link",
|
||||
"GalleryCard.delete": "Delete",
|
||||
"GalleryCard.duplicate": "Duplicate",
|
||||
"GroupBy.hideEmptyGroups": "Hide {count} empty groups",
|
||||
"GroupBy.showHiddenGroups": "Show {count} hidden groups",
|
||||
"GroupBy.ungroup": "Ungroup",
|
||||
"KanbanCard.copiedLink": "Copied!",
|
||||
"KanbanCard.copyLink": "Copy link",
|
||||
"KanbanCard.delete": "Delete",
|
||||
"KanbanCard.duplicate": "Duplicate",
|
||||
"KanbanCard.untitled": "Untitled",
|
||||
"Mutator.new-board-from-template": "new board from template",
|
||||
"Mutator.new-card-from-template": "new card from template",
|
||||
|
@ -727,6 +727,20 @@ exports[`components/calendar/toolbar return calendar, no date property 1`] = `
|
||||
<div
|
||||
class="EventContent"
|
||||
>
|
||||
<div
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper optionsMenu"
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="IconButton"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-dots-horizontal OptionsIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="octo-icontitle"
|
||||
>
|
||||
@ -2987,6 +3001,20 @@ exports[`components/calendar/toolbar return calendar, with date property not set
|
||||
<div
|
||||
class="EventContent"
|
||||
>
|
||||
<div
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper optionsMenu"
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="IconButton"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-dots-horizontal OptionsIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="octo-icontitle"
|
||||
>
|
||||
@ -5945,6 +5973,20 @@ exports[`components/calendar/toolbar return calendar, with date property set 1`]
|
||||
<div
|
||||
class="EventContent"
|
||||
>
|
||||
<div
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper optionsMenu"
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="IconButton"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-dots-horizontal OptionsIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="octo-icontitle"
|
||||
>
|
||||
@ -7513,6 +7555,20 @@ exports[`components/calendar/toolbar return calendar, without permissions 1`] =
|
||||
<div
|
||||
class="EventContent"
|
||||
>
|
||||
<div
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper optionsMenu"
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="IconButton"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-dots-horizontal OptionsIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="octo-icontitle"
|
||||
>
|
||||
|
@ -22,6 +22,11 @@ import {useHasCurrentBoardPermissions} from '../../hooks/permissions'
|
||||
import CardBadges from '../cardBadges'
|
||||
|
||||
import './fullcalendar.scss'
|
||||
import MenuWrapper from '../../widgets/menuWrapper'
|
||||
import IconButton from '../../widgets/buttons/iconButton'
|
||||
import CardActionsMenu from '../cardActionsMenu/cardActionsMenu'
|
||||
import OptionsIcon from '../../widgets/icons/options'
|
||||
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'
|
||||
|
||||
const oneDay = 60 * 60 * 24 * 1000
|
||||
|
||||
@ -121,17 +126,33 @@ const CalendarFullView = (props: Props): JSX.Element|null => {
|
||||
|
||||
const renderEventContent = (eventProps: EventContentArg): JSX.Element|null => {
|
||||
const {event} = eventProps
|
||||
const card = cards.find((o) => o.id === event.id) || cards[0]
|
||||
return (
|
||||
<div
|
||||
className='EventContent'
|
||||
onClick={() => props.showCard(event.id)}
|
||||
>
|
||||
{!props.readonly &&
|
||||
<MenuWrapper
|
||||
className='optionsMenu'
|
||||
stopPropagationOnToggle={true}
|
||||
>
|
||||
<IconButton icon={<OptionsIcon/>}/>
|
||||
<CardActionsMenu
|
||||
cardId={card.id}
|
||||
onClickDelete={() => mutator.deleteBlock(card, 'delete card')}
|
||||
onClickDuplicate={() => {
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})
|
||||
mutator.duplicateCard(card.id, board.id)
|
||||
}}
|
||||
/>
|
||||
</MenuWrapper>}
|
||||
<div className='octo-icontitle'>
|
||||
{ event.extendedProps.icon ? <div className='octo-icon'>{event.extendedProps.icon}</div> : undefined }
|
||||
<div
|
||||
className='fc-event-title'
|
||||
key='__title'
|
||||
>{event.title || intl.formatMessage({id: 'KanbanCard.untitled', defaultMessage: 'Untitled'})}</div>
|
||||
>{event.title || intl.formatMessage({id: 'CalendarCard.untitled', defaultMessage: 'Untitled'})}</div>
|
||||
</div>
|
||||
{visiblePropertyTemplates.map((template) => (
|
||||
<Tooltip
|
||||
@ -141,14 +162,14 @@ const CalendarFullView = (props: Props): JSX.Element|null => {
|
||||
<PropertyValueElement
|
||||
board={board}
|
||||
readOnly={true}
|
||||
card={cards.find((o) => o.id === event.id) || cards[0]}
|
||||
card={card}
|
||||
propertyTemplate={template}
|
||||
showEmptyPlaceholder={false}
|
||||
/>
|
||||
</Tooltip>
|
||||
))}
|
||||
{visibleBadges &&
|
||||
<CardBadges card={cards.find((o) => o.id === event.id) || cards[0]}/> }
|
||||
<CardBadges card={card}/> }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -10,6 +10,10 @@
|
||||
|
||||
&:hover {
|
||||
background-color: unset;
|
||||
|
||||
.optionsMenu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +23,20 @@
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.optionsMenu {
|
||||
background-color: rgb(var(--center-channel-bg-rgb));
|
||||
border-radius: var(--default-rad);
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 30;
|
||||
|
||||
.IconButton {
|
||||
background-color: rgba(var(--center-channel-color-rgb), 0.13);
|
||||
}
|
||||
}
|
||||
|
||||
.octo-tooltip {
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
@ -180,7 +198,6 @@
|
||||
background-color: rgb(var(--center-channel-bg-rgb));
|
||||
box-shadow: var(--elevation-1);
|
||||
margin: 0 8px 10px;
|
||||
overflow: hidden;
|
||||
padding: 4px 6px;
|
||||
|
||||
&:hover::before {
|
||||
|
@ -0,0 +1,300 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/cardActionsMenu should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Menu noselect left "
|
||||
>
|
||||
<div
|
||||
class="menu-contents"
|
||||
>
|
||||
<div
|
||||
class="menu-options"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-label="Delete"
|
||||
class="MenuOption TextOption menu-option"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="d-flex"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Delete
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
aria-label="Copy link"
|
||||
class="MenuOption TextOption menu-option"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="d-flex"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-link-variant LinkIcon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Copy link
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
/>
|
||||
<div
|
||||
class="menu-options hideOnWidescreen"
|
||||
>
|
||||
<div
|
||||
aria-label="Cancel"
|
||||
class="MenuOption TextOption menu-option menu-cancel"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="d-flex"
|
||||
>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/cardActionsMenu should match snapshot w/ children prop 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Menu noselect left "
|
||||
>
|
||||
<div
|
||||
class="menu-contents"
|
||||
>
|
||||
<div
|
||||
class="menu-options"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-label="Delete"
|
||||
class="MenuOption TextOption menu-option"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="d-flex"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Delete
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
aria-label="Copy link"
|
||||
class="MenuOption TextOption menu-option"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="d-flex"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-link-variant LinkIcon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Copy link
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Test.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
/>
|
||||
<div
|
||||
class="menu-options hideOnWidescreen"
|
||||
>
|
||||
<div
|
||||
aria-label="Cancel"
|
||||
class="MenuOption TextOption menu-option menu-cancel"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="d-flex"
|
||||
>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/cardActionsMenu should match snapshot w/ onClickDuplicate prop 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Menu noselect left "
|
||||
>
|
||||
<div
|
||||
class="menu-contents"
|
||||
>
|
||||
<div
|
||||
class="menu-options"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-label="Delete"
|
||||
class="MenuOption TextOption menu-option"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="d-flex"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Delete
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Duplicate"
|
||||
class="MenuOption TextOption menu-option"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="d-flex"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-content-copy content-copy"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Duplicate
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
aria-label="Copy link"
|
||||
class="MenuOption TextOption menu-option"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="d-flex"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-link-variant LinkIcon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Copy link
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
/>
|
||||
<div
|
||||
class="menu-options hideOnWidescreen"
|
||||
>
|
||||
<div
|
||||
aria-label="Cancel"
|
||||
class="MenuOption TextOption menu-option menu-cancel"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="d-flex"
|
||||
>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import '@testing-library/jest-dom'
|
||||
import {act, render} from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import {Provider as ReduxProvider} from 'react-redux'
|
||||
|
||||
import { TestBlockFactory } from '../../test/testBlockFactory'
|
||||
import {mockDOM, mockStateStore, wrapIntl} from '../../testUtils'
|
||||
|
||||
import CardActionsMenu from './cardActionsMenu'
|
||||
|
||||
beforeAll(() => {
|
||||
mockDOM()
|
||||
})
|
||||
|
||||
describe('components/cardActionsMenu', () => {
|
||||
const board = TestBlockFactory.createBoard()
|
||||
board.id = 'boardId'
|
||||
|
||||
const state = {
|
||||
boards: {
|
||||
current: board.id,
|
||||
boards: {
|
||||
[board.id]: board,
|
||||
},
|
||||
templates: [],
|
||||
myBoardMemberships: {
|
||||
[board.id]: {userId: 'user_id_1', schemeAdmin: true},
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
current: {id: 'team-id'},
|
||||
},
|
||||
users: {
|
||||
me: {
|
||||
id: 'user_id_1',
|
||||
},
|
||||
},
|
||||
}
|
||||
const store = mockStateStore([], state)
|
||||
|
||||
test('should match snapshot', async () => {
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(wrapIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardActionsMenu
|
||||
cardId='123'
|
||||
onClickDelete={jest.fn()}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
container = result.container
|
||||
})
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should match snapshot w/ onClickDuplicate prop', async () => {
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(wrapIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardActionsMenu
|
||||
cardId='123'
|
||||
onClickDelete={jest.fn()}
|
||||
onClickDuplicate={jest.fn()}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
container = result.container
|
||||
})
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should match snapshot w/ children prop', async () => {
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(wrapIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardActionsMenu
|
||||
cardId='123'
|
||||
onClickDelete={jest.fn()}
|
||||
>
|
||||
<React.Fragment>
|
||||
Test.
|
||||
</React.Fragment>
|
||||
</CardActionsMenu>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
container = result.container
|
||||
})
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
})
|
71
webapp/src/components/cardActionsMenu/cardActionsMenu.tsx
Normal file
71
webapp/src/components/cardActionsMenu/cardActionsMenu.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, { ReactNode } from 'react'
|
||||
import {useIntl} from 'react-intl'
|
||||
|
||||
import DeleteIcon from '../../widgets/icons/delete'
|
||||
import Menu from '../../widgets/menu'
|
||||
import BoardPermissionGate from '../permissions/boardPermissionGate'
|
||||
import DuplicateIcon from '../../widgets/icons/duplicate'
|
||||
import LinkIcon from '../../widgets/icons/Link'
|
||||
import {Utils} from '../../utils'
|
||||
import {Permission} from '../../constants'
|
||||
import {sendFlashMessage} from '../flashMessages'
|
||||
import {IUser} from '../../user'
|
||||
import {getMe} from '../../store/users'
|
||||
import {useAppSelector} from '../../store/hooks'
|
||||
|
||||
type Props = {
|
||||
cardId: string
|
||||
onClickDelete: () => void,
|
||||
onClickDuplicate?: () => void
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
export const CardActionsMenu = (props: Props): JSX.Element => {
|
||||
const {cardId} = props
|
||||
|
||||
const me = useAppSelector<IUser|null>(getMe)
|
||||
const intl = useIntl()
|
||||
|
||||
return (
|
||||
<Menu position='left'>
|
||||
<BoardPermissionGate permissions={[Permission.ManageBoardCards]}>
|
||||
<Menu.Text
|
||||
icon={<DeleteIcon/>}
|
||||
id='delete'
|
||||
name={intl.formatMessage({id: 'CardActionsMenu.delete', defaultMessage: 'Delete'})}
|
||||
onClick={props.onClickDelete}
|
||||
/>
|
||||
{props.onClickDuplicate &&
|
||||
<Menu.Text
|
||||
icon={<DuplicateIcon/>}
|
||||
id='duplicate'
|
||||
name={intl.formatMessage({id: 'CardActionsMenu.duplicate', defaultMessage: 'Duplicate'})}
|
||||
onClick={props.onClickDuplicate}
|
||||
/>}
|
||||
</BoardPermissionGate>
|
||||
{me?.id !== 'single-user' &&
|
||||
<Menu.Text
|
||||
icon={<LinkIcon/>}
|
||||
id='copy'
|
||||
name={intl.formatMessage({id: 'CardActionsMenu.copyLink', defaultMessage: 'Copy link'})}
|
||||
onClick={() => {
|
||||
let cardLink = window.location.href
|
||||
|
||||
if (!cardLink.includes(cardId)) {
|
||||
cardLink += `/${cardId}`
|
||||
}
|
||||
|
||||
Utils.copyTextToClipboard(cardLink)
|
||||
sendFlashMessage({content: intl.formatMessage({id: 'CardActionsMenu.copiedLink', defaultMessage: 'Copied!'}), severity: 'high'})
|
||||
}}
|
||||
/>
|
||||
}
|
||||
{props.children}
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
|
||||
export default CardActionsMenu
|
@ -14,8 +14,6 @@ import {useAppSelector} from '../store/hooks'
|
||||
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../telemetry/telemetryClient'
|
||||
import {Utils} from '../utils'
|
||||
import CompassIcon from '../widgets/icons/compassIcon'
|
||||
import DeleteIcon from '../widgets/icons/delete'
|
||||
import LinkIcon from '../widgets/icons/Link'
|
||||
import Menu from '../widgets/menu'
|
||||
|
||||
import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../components/confirmationDialogBox'
|
||||
@ -32,9 +30,9 @@ import BoardPermissionGate from './permissions/boardPermissionGate'
|
||||
|
||||
import CardDetail from './cardDetail/cardDetail'
|
||||
import Dialog from './dialog'
|
||||
import {sendFlashMessage} from './flashMessages'
|
||||
|
||||
import './cardDialog.scss'
|
||||
import CardActionsMenu from './cardActionsMenu/cardActionsMenu'
|
||||
|
||||
type Props = {
|
||||
board: Board
|
||||
@ -110,32 +108,10 @@ const CardDialog = (props: Props): JSX.Element => {
|
||||
}
|
||||
|
||||
const menu = (
|
||||
<Menu position='left'>
|
||||
<BoardPermissionGate permissions={[Permission.ManageBoardCards]}>
|
||||
<Menu.Text
|
||||
id='delete'
|
||||
icon={<DeleteIcon/>}
|
||||
name='Delete'
|
||||
onClick={handleDeleteButtonOnClick}
|
||||
/>
|
||||
</BoardPermissionGate>
|
||||
{me?.id !== 'single-user' &&
|
||||
<Menu.Text
|
||||
icon={<LinkIcon/>}
|
||||
id='copy'
|
||||
name={intl.formatMessage({id: 'CardDialog.copyLink', defaultMessage: 'Copy link'})}
|
||||
onClick={() => {
|
||||
let cardLink = window.location.href
|
||||
|
||||
if (!cardLink.includes(props.cardId)) {
|
||||
cardLink += `/${props.cardId}`
|
||||
}
|
||||
|
||||
Utils.copyTextToClipboard(cardLink)
|
||||
sendFlashMessage({content: intl.formatMessage({id: 'CardDialog.copiedLink', defaultMessage: 'Copied!'}), severity: 'high'})
|
||||
}}
|
||||
/>
|
||||
}
|
||||
<CardActionsMenu
|
||||
cardId={props.cardId}
|
||||
onClickDelete={handleDeleteButtonOnClick}
|
||||
>
|
||||
{!isTemplate &&
|
||||
<BoardPermissionGate permissions={[Permission.ManageBoardProperties]}>
|
||||
<Menu.Text
|
||||
@ -149,7 +125,7 @@ const CardDialog = (props: Props): JSX.Element => {
|
||||
/>
|
||||
</BoardPermissionGate>
|
||||
}
|
||||
</Menu>
|
||||
</CardActionsMenu>
|
||||
)
|
||||
|
||||
const followActionButton = (following: boolean): React.ReactNode => {
|
||||
|
@ -282,6 +282,7 @@ exports[`src/components/gallery/Gallery should match snapshot 1`] = `
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
@ -413,6 +414,7 @@ exports[`src/components/gallery/Gallery should match snapshot without permission
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
|
@ -123,6 +123,7 @@ exports[`src/components/gallery/GalleryCard with a comment content should match
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
@ -271,6 +272,7 @@ exports[`src/components/gallery/GalleryCard with an image content should match s
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
@ -457,6 +459,7 @@ exports[`src/components/gallery/GalleryCard with many contents should match snap
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
@ -609,6 +612,7 @@ exports[`src/components/gallery/GalleryCard with many images content should matc
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
@ -927,6 +931,7 @@ exports[`src/components/gallery/GalleryCard without block content should match s
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
|
@ -1,37 +1,27 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {useMemo} from 'react'
|
||||
import {FormattedMessage, useIntl} from 'react-intl'
|
||||
import {FormattedMessage} from 'react-intl'
|
||||
|
||||
import {Board, IPropertyTemplate} from '../../blocks/board'
|
||||
import {Card} from '../../blocks/card'
|
||||
import {ContentBlock} from '../../blocks/contentBlock'
|
||||
import {useSortable} from '../../hooks/sortable'
|
||||
import mutator from '../../mutator'
|
||||
import {IUser} from '../../user'
|
||||
import {getMe} from '../../store/users'
|
||||
import {getCardContents} from '../../store/contents'
|
||||
import {useAppSelector} from '../../store/hooks'
|
||||
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'
|
||||
import {Utils} from '../../utils'
|
||||
import IconButton from '../../widgets/buttons/iconButton'
|
||||
import DeleteIcon from '../../widgets/icons/delete'
|
||||
import DuplicateIcon from '../../widgets/icons/duplicate'
|
||||
import LinkIcon from '../../widgets/icons/Link'
|
||||
import OptionsIcon from '../../widgets/icons/options'
|
||||
import Menu from '../../widgets/menu'
|
||||
import MenuWrapper from '../../widgets/menuWrapper'
|
||||
import Tooltip from '../../widgets/tooltip'
|
||||
import {Permission} from '../../constants'
|
||||
import {CardDetailProvider} from '../cardDetail/cardDetailContext'
|
||||
import ContentElement from '../content/contentElement'
|
||||
import ImageElement from '../content/imageElement'
|
||||
import {sendFlashMessage} from '../flashMessages'
|
||||
import PropertyValueElement from '../propertyValueElement'
|
||||
import './galleryCard.scss'
|
||||
import CardBadges from '../cardBadges'
|
||||
|
||||
import BoardPermissionGate from '../permissions/boardPermissionGate'
|
||||
import CardActionsMenu from '../cardActionsMenu/cardActionsMenu'
|
||||
|
||||
type Props = {
|
||||
board: Board
|
||||
@ -48,10 +38,8 @@ type Props = {
|
||||
|
||||
const GalleryCard = (props: Props) => {
|
||||
const {card, board} = props
|
||||
const intl = useIntl()
|
||||
const [isDragging, isOver, cardRef] = useSortable('card', card, props.isManualSort && !props.readonly, props.onDrop)
|
||||
const contents = useAppSelector(getCardContents(card.id))
|
||||
const me = useAppSelector<IUser|null>(getMe)
|
||||
|
||||
const visiblePropertyTemplates = props.visiblePropertyTemplates || []
|
||||
|
||||
@ -84,42 +72,14 @@ const GalleryCard = (props: Props) => {
|
||||
stopPropagationOnToggle={true}
|
||||
>
|
||||
<IconButton icon={<OptionsIcon/>}/>
|
||||
<Menu position='left'>
|
||||
<BoardPermissionGate permissions={[Permission.ManageBoardCards]}>
|
||||
<Menu.Text
|
||||
icon={<DeleteIcon/>}
|
||||
id='delete'
|
||||
name={intl.formatMessage({id: 'GalleryCard.delete', defaultMessage: 'Delete'})}
|
||||
onClick={() => mutator.deleteBlock(card, 'delete card')}
|
||||
/>
|
||||
<Menu.Text
|
||||
icon={<DuplicateIcon/>}
|
||||
id='duplicate'
|
||||
name={intl.formatMessage({id: 'GalleryCard.duplicate', defaultMessage: 'Duplicate'})}
|
||||
onClick={() => {
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})
|
||||
mutator.duplicateCard(card.id, board.id)
|
||||
}}
|
||||
/>
|
||||
</BoardPermissionGate>
|
||||
{me?.id !== 'single-user' &&
|
||||
<Menu.Text
|
||||
icon={<LinkIcon/>}
|
||||
id='copy'
|
||||
name={intl.formatMessage({id: 'GalleryCard.copyLink', defaultMessage: 'Copy link'})}
|
||||
onClick={() => {
|
||||
let cardLink = window.location.href
|
||||
|
||||
if (!cardLink.includes(card.id)) {
|
||||
cardLink += `/${card.id}`
|
||||
}
|
||||
|
||||
Utils.copyTextToClipboard(cardLink)
|
||||
sendFlashMessage({content: intl.formatMessage({id: 'GalleryCard.copiedLink', defaultMessage: 'Copied!'}), severity: 'high'})
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</Menu>
|
||||
<CardActionsMenu
|
||||
cardId={card!.id}
|
||||
onClickDelete={() => mutator.deleteBlock(card, 'delete card')}
|
||||
onClickDuplicate={() => {
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})
|
||||
mutator.duplicateCard(card.id, board.id)
|
||||
}}
|
||||
/>
|
||||
</MenuWrapper>
|
||||
}
|
||||
|
||||
|
@ -95,6 +95,7 @@ exports[`src/components/kanban/kanbanCard return kanbanCard and click on copy li
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
@ -248,6 +249,7 @@ exports[`src/components/kanban/kanbanCard return kanbanCard and click on delete
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
@ -401,6 +403,7 @@ exports[`src/components/kanban/kanbanCard return kanbanCard and click on duplica
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
|
@ -11,27 +11,16 @@ import mutator from '../../mutator'
|
||||
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'
|
||||
import {Utils} from '../../utils'
|
||||
import IconButton from '../../widgets/buttons/iconButton'
|
||||
import DeleteIcon from '../../widgets/icons/delete'
|
||||
import DuplicateIcon from '../../widgets/icons/duplicate'
|
||||
import LinkIcon from '../../widgets/icons/Link'
|
||||
import OptionsIcon from '../../widgets/icons/options'
|
||||
import Menu from '../../widgets/menu'
|
||||
import MenuWrapper from '../../widgets/menuWrapper'
|
||||
import Tooltip from '../../widgets/tooltip'
|
||||
import {Permission} from '../../constants'
|
||||
import {sendFlashMessage} from '../flashMessages'
|
||||
import PropertyValueElement from '../propertyValueElement'
|
||||
import {IUser} from '../../user'
|
||||
import {getMe} from '../../store/users'
|
||||
import {useAppSelector} from '../../store/hooks'
|
||||
|
||||
import BoardPermissionGate from '../permissions/boardPermissionGate'
|
||||
|
||||
import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox'
|
||||
import './kanbanCard.scss'
|
||||
import CardBadges from '../cardBadges'
|
||||
import OpenCardTourStep from '../onboardingTour/openCard/open_card'
|
||||
import CopyLinkTourStep from '../onboardingTour/copyLink/copy_link'
|
||||
import CardActionsMenu from '../cardActionsMenu/cardActionsMenu'
|
||||
|
||||
export const OnboardingCardClassName = 'onboardingCard'
|
||||
|
||||
@ -54,7 +43,6 @@ const KanbanCard = (props: Props) => {
|
||||
const [isDragging, isOver, cardRef] = useSortable('card', card, !props.readonly, props.onDrop)
|
||||
const visiblePropertyTemplates = props.visiblePropertyTemplates || []
|
||||
const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string}>()
|
||||
const me = useAppSelector<IUser|null>(getMe)
|
||||
let className = props.isSelected ? 'KanbanCard selected' : 'KanbanCard'
|
||||
if (props.isManualSort && isOver) {
|
||||
className += ' dragover'
|
||||
@ -116,54 +104,26 @@ const KanbanCard = (props: Props) => {
|
||||
stopPropagationOnToggle={true}
|
||||
>
|
||||
<IconButton icon={<OptionsIcon/>}/>
|
||||
<Menu position='left'>
|
||||
<BoardPermissionGate permissions={[Permission.ManageBoardCards]}>
|
||||
<Menu.Text
|
||||
icon={<DeleteIcon/>}
|
||||
id='delete'
|
||||
name={intl.formatMessage({id: 'KanbanCard.delete', defaultMessage: 'Delete'})}
|
||||
onClick={handleDeleteButtonOnClick}
|
||||
/>
|
||||
<Menu.Text
|
||||
icon={<DuplicateIcon/>}
|
||||
id='duplicate'
|
||||
name={intl.formatMessage({id: 'KanbanCard.duplicate', defaultMessage: 'Duplicate'})}
|
||||
onClick={() => {
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})
|
||||
mutator.duplicateCard(
|
||||
card.id,
|
||||
board.id,
|
||||
false,
|
||||
'duplicate card',
|
||||
false,
|
||||
async (newCardId) => {
|
||||
props.showCard(newCardId)
|
||||
},
|
||||
async () => {
|
||||
props.showCard(undefined)
|
||||
},
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</BoardPermissionGate>
|
||||
{me?.id !== 'single-user' &&
|
||||
<Menu.Text
|
||||
icon={<LinkIcon/>}
|
||||
id='copy'
|
||||
name={intl.formatMessage({id: 'KanbanCard.copyLink', defaultMessage: 'Copy link'})}
|
||||
onClick={() => {
|
||||
let cardLink = window.location.href
|
||||
|
||||
if (!cardLink.includes(card.id)) {
|
||||
cardLink += `/${card.id}`
|
||||
}
|
||||
|
||||
Utils.copyTextToClipboard(cardLink)
|
||||
sendFlashMessage({content: intl.formatMessage({id: 'KanbanCard.copiedLink', defaultMessage: 'Copied!'}), severity: 'high'})
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</Menu>
|
||||
<CardActionsMenu
|
||||
cardId={card!.id}
|
||||
onClickDelete={handleDeleteButtonOnClick}
|
||||
onClickDuplicate={() => {
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})
|
||||
mutator.duplicateCard(
|
||||
card.id,
|
||||
board.id,
|
||||
false,
|
||||
'duplicate card',
|
||||
false,
|
||||
async (newCardId) => {
|
||||
props.showCard(newCardId)
|
||||
},
|
||||
async () => {
|
||||
props.showCard(undefined)
|
||||
},
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</MenuWrapper>
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user