mirror of
https://github.com/mattermost/focalboard.git
synced 2025-03-29 21:01:01 +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.range.label": "Range",
|
||||||
"Calculations.Options.sum.displayName": "Sum",
|
"Calculations.Options.sum.displayName": "Sum",
|
||||||
"Calculations.Options.sum.label": "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-checkboxes": "Checkboxes",
|
||||||
"CardBadges.title-comments": "Comments",
|
"CardBadges.title-comments": "Comments",
|
||||||
"CardBadges.title-description": "This card has a description",
|
"CardBadges.title-description": "This card has a description",
|
||||||
@ -87,8 +92,6 @@
|
|||||||
"CardDetailProperty.property-deleted": "Deleted {propertyName} successfully!",
|
"CardDetailProperty.property-deleted": "Deleted {propertyName} successfully!",
|
||||||
"CardDetailProperty.property-name-change-subtext": "type from \"{oldPropType}\" to \"{newPropType}\"",
|
"CardDetailProperty.property-name-change-subtext": "type from \"{oldPropType}\" to \"{newPropType}\"",
|
||||||
"CardDetial.limited-link": "Learn more about our plans.",
|
"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-button-text": "Delete",
|
||||||
"CardDialog.delete-confirmation-dialog-heading": "Confirm card delete!",
|
"CardDialog.delete-confirmation-dialog-heading": "Confirm card delete!",
|
||||||
"CardDialog.editing-template": "You're editing a template.",
|
"CardDialog.editing-template": "You're editing a template.",
|
||||||
@ -144,17 +147,9 @@
|
|||||||
"FindBoardsDialog.NoResultsSubtext": "Check the spelling or try another search.",
|
"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.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",
|
"FindBoardsDialog.Title": "Find Boards",
|
||||||
"GalleryCard.copiedLink": "Copied!",
|
|
||||||
"GalleryCard.copyLink": "Copy link",
|
|
||||||
"GalleryCard.delete": "Delete",
|
|
||||||
"GalleryCard.duplicate": "Duplicate",
|
|
||||||
"GroupBy.hideEmptyGroups": "Hide {count} empty groups",
|
"GroupBy.hideEmptyGroups": "Hide {count} empty groups",
|
||||||
"GroupBy.showHiddenGroups": "Show {count} hidden groups",
|
"GroupBy.showHiddenGroups": "Show {count} hidden groups",
|
||||||
"GroupBy.ungroup": "Ungroup",
|
"GroupBy.ungroup": "Ungroup",
|
||||||
"KanbanCard.copiedLink": "Copied!",
|
|
||||||
"KanbanCard.copyLink": "Copy link",
|
|
||||||
"KanbanCard.delete": "Delete",
|
|
||||||
"KanbanCard.duplicate": "Duplicate",
|
|
||||||
"KanbanCard.untitled": "Untitled",
|
"KanbanCard.untitled": "Untitled",
|
||||||
"Mutator.new-board-from-template": "new board from template",
|
"Mutator.new-board-from-template": "new board from template",
|
||||||
"Mutator.new-card-from-template": "new card 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
|
<div
|
||||||
class="EventContent"
|
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
|
<div
|
||||||
class="octo-icontitle"
|
class="octo-icontitle"
|
||||||
>
|
>
|
||||||
@ -2987,6 +3001,20 @@ exports[`components/calendar/toolbar return calendar, with date property not set
|
|||||||
<div
|
<div
|
||||||
class="EventContent"
|
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
|
<div
|
||||||
class="octo-icontitle"
|
class="octo-icontitle"
|
||||||
>
|
>
|
||||||
@ -5945,6 +5973,20 @@ exports[`components/calendar/toolbar return calendar, with date property set 1`]
|
|||||||
<div
|
<div
|
||||||
class="EventContent"
|
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
|
<div
|
||||||
class="octo-icontitle"
|
class="octo-icontitle"
|
||||||
>
|
>
|
||||||
@ -7513,6 +7555,20 @@ exports[`components/calendar/toolbar return calendar, without permissions 1`] =
|
|||||||
<div
|
<div
|
||||||
class="EventContent"
|
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
|
<div
|
||||||
class="octo-icontitle"
|
class="octo-icontitle"
|
||||||
>
|
>
|
||||||
|
@ -22,6 +22,11 @@ import {useHasCurrentBoardPermissions} from '../../hooks/permissions'
|
|||||||
import CardBadges from '../cardBadges'
|
import CardBadges from '../cardBadges'
|
||||||
|
|
||||||
import './fullcalendar.scss'
|
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
|
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 renderEventContent = (eventProps: EventContentArg): JSX.Element|null => {
|
||||||
const {event} = eventProps
|
const {event} = eventProps
|
||||||
|
const card = cards.find((o) => o.id === event.id) || cards[0]
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='EventContent'
|
className='EventContent'
|
||||||
onClick={() => props.showCard(event.id)}
|
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'>
|
<div className='octo-icontitle'>
|
||||||
{ event.extendedProps.icon ? <div className='octo-icon'>{event.extendedProps.icon}</div> : undefined }
|
{ event.extendedProps.icon ? <div className='octo-icon'>{event.extendedProps.icon}</div> : undefined }
|
||||||
<div
|
<div
|
||||||
className='fc-event-title'
|
className='fc-event-title'
|
||||||
key='__title'
|
key='__title'
|
||||||
>{event.title || intl.formatMessage({id: 'KanbanCard.untitled', defaultMessage: 'Untitled'})}</div>
|
>{event.title || intl.formatMessage({id: 'CalendarCard.untitled', defaultMessage: 'Untitled'})}</div>
|
||||||
</div>
|
</div>
|
||||||
{visiblePropertyTemplates.map((template) => (
|
{visiblePropertyTemplates.map((template) => (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@ -141,14 +162,14 @@ const CalendarFullView = (props: Props): JSX.Element|null => {
|
|||||||
<PropertyValueElement
|
<PropertyValueElement
|
||||||
board={board}
|
board={board}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
card={cards.find((o) => o.id === event.id) || cards[0]}
|
card={card}
|
||||||
propertyTemplate={template}
|
propertyTemplate={template}
|
||||||
showEmptyPlaceholder={false}
|
showEmptyPlaceholder={false}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
{visibleBadges &&
|
{visibleBadges &&
|
||||||
<CardBadges card={cards.find((o) => o.id === event.id) || cards[0]}/> }
|
<CardBadges card={card}/> }
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,10 @@
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: unset;
|
background-color: unset;
|
||||||
|
|
||||||
|
.optionsMenu {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,6 +23,20 @@
|
|||||||
align-items: flex-start;
|
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 {
|
.octo-tooltip {
|
||||||
display: flex;
|
display: flex;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
@ -180,7 +198,6 @@
|
|||||||
background-color: rgb(var(--center-channel-bg-rgb));
|
background-color: rgb(var(--center-channel-bg-rgb));
|
||||||
box-shadow: var(--elevation-1);
|
box-shadow: var(--elevation-1);
|
||||||
margin: 0 8px 10px;
|
margin: 0 8px 10px;
|
||||||
overflow: hidden;
|
|
||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
|
|
||||||
&:hover::before {
|
&: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 TelemetryClient, {TelemetryActions, TelemetryCategory} from '../telemetry/telemetryClient'
|
||||||
import {Utils} from '../utils'
|
import {Utils} from '../utils'
|
||||||
import CompassIcon from '../widgets/icons/compassIcon'
|
import CompassIcon from '../widgets/icons/compassIcon'
|
||||||
import DeleteIcon from '../widgets/icons/delete'
|
|
||||||
import LinkIcon from '../widgets/icons/Link'
|
|
||||||
import Menu from '../widgets/menu'
|
import Menu from '../widgets/menu'
|
||||||
|
|
||||||
import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../components/confirmationDialogBox'
|
import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../components/confirmationDialogBox'
|
||||||
@ -32,9 +30,9 @@ import BoardPermissionGate from './permissions/boardPermissionGate'
|
|||||||
|
|
||||||
import CardDetail from './cardDetail/cardDetail'
|
import CardDetail from './cardDetail/cardDetail'
|
||||||
import Dialog from './dialog'
|
import Dialog from './dialog'
|
||||||
import {sendFlashMessage} from './flashMessages'
|
|
||||||
|
|
||||||
import './cardDialog.scss'
|
import './cardDialog.scss'
|
||||||
|
import CardActionsMenu from './cardActionsMenu/cardActionsMenu'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
board: Board
|
board: Board
|
||||||
@ -110,32 +108,10 @@ const CardDialog = (props: Props): JSX.Element => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
<Menu position='left'>
|
<CardActionsMenu
|
||||||
<BoardPermissionGate permissions={[Permission.ManageBoardCards]}>
|
cardId={props.cardId}
|
||||||
<Menu.Text
|
onClickDelete={handleDeleteButtonOnClick}
|
||||||
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'})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
{!isTemplate &&
|
{!isTemplate &&
|
||||||
<BoardPermissionGate permissions={[Permission.ManageBoardProperties]}>
|
<BoardPermissionGate permissions={[Permission.ManageBoardProperties]}>
|
||||||
<Menu.Text
|
<Menu.Text
|
||||||
@ -149,7 +125,7 @@ const CardDialog = (props: Props): JSX.Element => {
|
|||||||
/>
|
/>
|
||||||
</BoardPermissionGate>
|
</BoardPermissionGate>
|
||||||
}
|
}
|
||||||
</Menu>
|
</CardActionsMenu>
|
||||||
)
|
)
|
||||||
|
|
||||||
const followActionButton = (following: boolean): React.ReactNode => {
|
const followActionButton = (following: boolean): React.ReactNode => {
|
||||||
|
@ -282,6 +282,7 @@ exports[`src/components/gallery/Gallery should match snapshot 1`] = `
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="menu-spacer hideOnWidescreen"
|
class="menu-spacer hideOnWidescreen"
|
||||||
@ -413,6 +414,7 @@ exports[`src/components/gallery/Gallery should match snapshot without permission
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="menu-spacer hideOnWidescreen"
|
class="menu-spacer hideOnWidescreen"
|
||||||
|
@ -123,6 +123,7 @@ exports[`src/components/gallery/GalleryCard with a comment content should match
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="menu-spacer hideOnWidescreen"
|
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 />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="menu-spacer hideOnWidescreen"
|
class="menu-spacer hideOnWidescreen"
|
||||||
@ -457,6 +459,7 @@ exports[`src/components/gallery/GalleryCard with many contents should match snap
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="menu-spacer hideOnWidescreen"
|
class="menu-spacer hideOnWidescreen"
|
||||||
@ -609,6 +612,7 @@ exports[`src/components/gallery/GalleryCard with many images content should matc
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="menu-spacer hideOnWidescreen"
|
class="menu-spacer hideOnWidescreen"
|
||||||
@ -927,6 +931,7 @@ exports[`src/components/gallery/GalleryCard without block content should match s
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="menu-spacer hideOnWidescreen"
|
class="menu-spacer hideOnWidescreen"
|
||||||
|
@ -1,37 +1,27 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import React, {useMemo} from 'react'
|
import React, {useMemo} from 'react'
|
||||||
import {FormattedMessage, useIntl} from 'react-intl'
|
import {FormattedMessage} from 'react-intl'
|
||||||
|
|
||||||
import {Board, IPropertyTemplate} from '../../blocks/board'
|
import {Board, IPropertyTemplate} from '../../blocks/board'
|
||||||
import {Card} from '../../blocks/card'
|
import {Card} from '../../blocks/card'
|
||||||
import {ContentBlock} from '../../blocks/contentBlock'
|
import {ContentBlock} from '../../blocks/contentBlock'
|
||||||
import {useSortable} from '../../hooks/sortable'
|
import {useSortable} from '../../hooks/sortable'
|
||||||
import mutator from '../../mutator'
|
import mutator from '../../mutator'
|
||||||
import {IUser} from '../../user'
|
|
||||||
import {getMe} from '../../store/users'
|
|
||||||
import {getCardContents} from '../../store/contents'
|
import {getCardContents} from '../../store/contents'
|
||||||
import {useAppSelector} from '../../store/hooks'
|
import {useAppSelector} from '../../store/hooks'
|
||||||
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'
|
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'
|
||||||
import {Utils} from '../../utils'
|
|
||||||
import IconButton from '../../widgets/buttons/iconButton'
|
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 OptionsIcon from '../../widgets/icons/options'
|
||||||
import Menu from '../../widgets/menu'
|
|
||||||
import MenuWrapper from '../../widgets/menuWrapper'
|
import MenuWrapper from '../../widgets/menuWrapper'
|
||||||
import Tooltip from '../../widgets/tooltip'
|
import Tooltip from '../../widgets/tooltip'
|
||||||
import {Permission} from '../../constants'
|
|
||||||
import {CardDetailProvider} from '../cardDetail/cardDetailContext'
|
import {CardDetailProvider} from '../cardDetail/cardDetailContext'
|
||||||
import ContentElement from '../content/contentElement'
|
import ContentElement from '../content/contentElement'
|
||||||
import ImageElement from '../content/imageElement'
|
import ImageElement from '../content/imageElement'
|
||||||
import {sendFlashMessage} from '../flashMessages'
|
|
||||||
import PropertyValueElement from '../propertyValueElement'
|
import PropertyValueElement from '../propertyValueElement'
|
||||||
import './galleryCard.scss'
|
import './galleryCard.scss'
|
||||||
import CardBadges from '../cardBadges'
|
import CardBadges from '../cardBadges'
|
||||||
|
import CardActionsMenu from '../cardActionsMenu/cardActionsMenu'
|
||||||
import BoardPermissionGate from '../permissions/boardPermissionGate'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
board: Board
|
board: Board
|
||||||
@ -48,10 +38,8 @@ type Props = {
|
|||||||
|
|
||||||
const GalleryCard = (props: Props) => {
|
const GalleryCard = (props: Props) => {
|
||||||
const {card, board} = props
|
const {card, board} = props
|
||||||
const intl = useIntl()
|
|
||||||
const [isDragging, isOver, cardRef] = useSortable('card', card, props.isManualSort && !props.readonly, props.onDrop)
|
const [isDragging, isOver, cardRef] = useSortable('card', card, props.isManualSort && !props.readonly, props.onDrop)
|
||||||
const contents = useAppSelector(getCardContents(card.id))
|
const contents = useAppSelector(getCardContents(card.id))
|
||||||
const me = useAppSelector<IUser|null>(getMe)
|
|
||||||
|
|
||||||
const visiblePropertyTemplates = props.visiblePropertyTemplates || []
|
const visiblePropertyTemplates = props.visiblePropertyTemplates || []
|
||||||
|
|
||||||
@ -84,42 +72,14 @@ const GalleryCard = (props: Props) => {
|
|||||||
stopPropagationOnToggle={true}
|
stopPropagationOnToggle={true}
|
||||||
>
|
>
|
||||||
<IconButton icon={<OptionsIcon/>}/>
|
<IconButton icon={<OptionsIcon/>}/>
|
||||||
<Menu position='left'>
|
<CardActionsMenu
|
||||||
<BoardPermissionGate permissions={[Permission.ManageBoardCards]}>
|
cardId={card!.id}
|
||||||
<Menu.Text
|
onClickDelete={() => mutator.deleteBlock(card, 'delete card')}
|
||||||
icon={<DeleteIcon/>}
|
onClickDuplicate={() => {
|
||||||
id='delete'
|
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})
|
||||||
name={intl.formatMessage({id: 'GalleryCard.delete', defaultMessage: 'Delete'})}
|
mutator.duplicateCard(card.id, board.id)
|
||||||
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>
|
|
||||||
</MenuWrapper>
|
</MenuWrapper>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ exports[`src/components/kanban/kanbanCard return kanbanCard and click on copy li
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="menu-spacer hideOnWidescreen"
|
class="menu-spacer hideOnWidescreen"
|
||||||
@ -248,6 +249,7 @@ exports[`src/components/kanban/kanbanCard return kanbanCard and click on delete
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="menu-spacer hideOnWidescreen"
|
class="menu-spacer hideOnWidescreen"
|
||||||
@ -401,6 +403,7 @@ exports[`src/components/kanban/kanbanCard return kanbanCard and click on duplica
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="menu-spacer hideOnWidescreen"
|
class="menu-spacer hideOnWidescreen"
|
||||||
|
@ -11,27 +11,16 @@ import mutator from '../../mutator'
|
|||||||
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'
|
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'
|
||||||
import {Utils} from '../../utils'
|
import {Utils} from '../../utils'
|
||||||
import IconButton from '../../widgets/buttons/iconButton'
|
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 OptionsIcon from '../../widgets/icons/options'
|
||||||
import Menu from '../../widgets/menu'
|
|
||||||
import MenuWrapper from '../../widgets/menuWrapper'
|
import MenuWrapper from '../../widgets/menuWrapper'
|
||||||
import Tooltip from '../../widgets/tooltip'
|
import Tooltip from '../../widgets/tooltip'
|
||||||
import {Permission} from '../../constants'
|
|
||||||
import {sendFlashMessage} from '../flashMessages'
|
|
||||||
import PropertyValueElement from '../propertyValueElement'
|
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 ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox'
|
||||||
import './kanbanCard.scss'
|
import './kanbanCard.scss'
|
||||||
import CardBadges from '../cardBadges'
|
import CardBadges from '../cardBadges'
|
||||||
import OpenCardTourStep from '../onboardingTour/openCard/open_card'
|
import OpenCardTourStep from '../onboardingTour/openCard/open_card'
|
||||||
import CopyLinkTourStep from '../onboardingTour/copyLink/copy_link'
|
import CopyLinkTourStep from '../onboardingTour/copyLink/copy_link'
|
||||||
|
import CardActionsMenu from '../cardActionsMenu/cardActionsMenu'
|
||||||
|
|
||||||
export const OnboardingCardClassName = 'onboardingCard'
|
export const OnboardingCardClassName = 'onboardingCard'
|
||||||
|
|
||||||
@ -54,7 +43,6 @@ const KanbanCard = (props: Props) => {
|
|||||||
const [isDragging, isOver, cardRef] = useSortable('card', card, !props.readonly, props.onDrop)
|
const [isDragging, isOver, cardRef] = useSortable('card', card, !props.readonly, props.onDrop)
|
||||||
const visiblePropertyTemplates = props.visiblePropertyTemplates || []
|
const visiblePropertyTemplates = props.visiblePropertyTemplates || []
|
||||||
const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string}>()
|
const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string}>()
|
||||||
const me = useAppSelector<IUser|null>(getMe)
|
|
||||||
let className = props.isSelected ? 'KanbanCard selected' : 'KanbanCard'
|
let className = props.isSelected ? 'KanbanCard selected' : 'KanbanCard'
|
||||||
if (props.isManualSort && isOver) {
|
if (props.isManualSort && isOver) {
|
||||||
className += ' dragover'
|
className += ' dragover'
|
||||||
@ -116,54 +104,26 @@ const KanbanCard = (props: Props) => {
|
|||||||
stopPropagationOnToggle={true}
|
stopPropagationOnToggle={true}
|
||||||
>
|
>
|
||||||
<IconButton icon={<OptionsIcon/>}/>
|
<IconButton icon={<OptionsIcon/>}/>
|
||||||
<Menu position='left'>
|
<CardActionsMenu
|
||||||
<BoardPermissionGate permissions={[Permission.ManageBoardCards]}>
|
cardId={card!.id}
|
||||||
<Menu.Text
|
onClickDelete={handleDeleteButtonOnClick}
|
||||||
icon={<DeleteIcon/>}
|
onClickDuplicate={() => {
|
||||||
id='delete'
|
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})
|
||||||
name={intl.formatMessage({id: 'KanbanCard.delete', defaultMessage: 'Delete'})}
|
mutator.duplicateCard(
|
||||||
onClick={handleDeleteButtonOnClick}
|
card.id,
|
||||||
/>
|
board.id,
|
||||||
<Menu.Text
|
false,
|
||||||
icon={<DuplicateIcon/>}
|
'duplicate card',
|
||||||
id='duplicate'
|
false,
|
||||||
name={intl.formatMessage({id: 'KanbanCard.duplicate', defaultMessage: 'Duplicate'})}
|
async (newCardId) => {
|
||||||
onClick={() => {
|
props.showCard(newCardId)
|
||||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})
|
},
|
||||||
mutator.duplicateCard(
|
async () => {
|
||||||
card.id,
|
props.showCard(undefined)
|
||||||
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>
|
|
||||||
</MenuWrapper>
|
</MenuWrapper>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user