MM-61525, MM-61868: Autohide channel bookmarks when empty (#29454)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
cf392f7cca
Коммит
09add85a51
@@ -11,7 +11,7 @@ import type {IDMappedObjects} from '@mattermost/types/utilities';
|
||||
|
||||
import BookmarkItem from './bookmark_item';
|
||||
import BookmarksMenu from './channel_bookmarks_menu';
|
||||
import {useChannelBookmarkPermission, useChannelBookmarks, MAX_BOOKMARKS_PER_CHANNEL, useCanUploadFiles} from './utils';
|
||||
import {useChannelBookmarks, MAX_BOOKMARKS_PER_CHANNEL, useCanUploadFiles, useChannelBookmarkPermission} from './utils';
|
||||
|
||||
import './channel_bookmarks.scss';
|
||||
|
||||
@@ -23,12 +23,12 @@ function ChannelBookmarks({
|
||||
channelId,
|
||||
}: Props) {
|
||||
const {order, bookmarks, reorder} = useChannelBookmarks(channelId);
|
||||
const canReorder = useChannelBookmarkPermission(channelId, 'order');
|
||||
const canUploadFiles = useCanUploadFiles();
|
||||
const canAdd = useChannelBookmarkPermission(channelId, 'add');
|
||||
const hasBookmarks = Boolean(order?.length);
|
||||
const limitReached = order.length >= MAX_BOOKMARKS_PER_CHANNEL;
|
||||
|
||||
if (!hasBookmarks && !canAdd) {
|
||||
if (!hasBookmarks) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ function ChannelBookmarks({
|
||||
data-testid='channel-bookmarks-container'
|
||||
{...drop.droppableProps}
|
||||
>
|
||||
{order.map(makeItemRenderer(bookmarks, snap.isDraggingOver))}
|
||||
{order.map(makeItemRenderer(bookmarks, snap.isDraggingOver, !canReorder))}
|
||||
{drop.placeholder}
|
||||
<BookmarksMenu
|
||||
channelId={channelId}
|
||||
@@ -69,12 +69,13 @@ function ChannelBookmarks({
|
||||
);
|
||||
}
|
||||
|
||||
const makeItemRenderer = (bookmarks: IDMappedObjects<ChannelBookmark>, disableInteractions: boolean) => (id: string, index: number) => {
|
||||
const makeItemRenderer = (bookmarks: IDMappedObjects<ChannelBookmark>, disableInteractions: boolean, disableDrag: boolean) => (id: string, index: number) => {
|
||||
return (
|
||||
<Draggable
|
||||
key={id}
|
||||
draggableId={id}
|
||||
index={index}
|
||||
isDragDisabled={disableDrag}
|
||||
>
|
||||
{(drag, snap) => {
|
||||
return (
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import classNames from 'classnames';
|
||||
import type {ChangeEvent} from 'react';
|
||||
import React, {useCallback, useRef} from 'react';
|
||||
import React, {memo, useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {useDispatch} from 'react-redux';
|
||||
import styled, {css} from 'styled-components';
|
||||
@@ -15,73 +14,32 @@ import {
|
||||
} from '@mattermost/compass-icons/components';
|
||||
import type {ChannelBookmarkCreate} from '@mattermost/types/channel_bookmarks';
|
||||
|
||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||
|
||||
import {createBookmark} from 'actions/channel_bookmarks';
|
||||
import {openModal} from 'actions/views/modals';
|
||||
|
||||
import * as Menu from 'components/menu';
|
||||
|
||||
import {ModalIdentifiers} from 'utils/constants';
|
||||
import {clearFileInput} from 'utils/utils';
|
||||
|
||||
import ChannelBookmarkCreateModal from './channel_bookmarks_create_modal';
|
||||
import {MAX_BOOKMARKS_PER_CHANNEL} from './utils';
|
||||
import {MAX_BOOKMARKS_PER_CHANNEL, useChannelBookmarkPermission} from './utils';
|
||||
|
||||
type BookmarksMenuProps = {
|
||||
channelId: string;
|
||||
hasBookmarks: boolean;
|
||||
limitReached: boolean;
|
||||
canUploadFiles: boolean;};
|
||||
export default ({
|
||||
function BookmarksMenu({
|
||||
channelId,
|
||||
hasBookmarks,
|
||||
limitReached,
|
||||
canUploadFiles,
|
||||
}: BookmarksMenuProps) => {
|
||||
}: BookmarksMenuProps) {
|
||||
const {formatMessage} = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
const showLabel = !hasBookmarks;
|
||||
|
||||
const handleCreate = useCallback((file?: File) => {
|
||||
dispatch(openModal({
|
||||
modalId: ModalIdentifiers.CHANNEL_BOOKMARK_CREATE,
|
||||
dialogType: ChannelBookmarkCreateModal,
|
||||
dialogProps: {
|
||||
channelId,
|
||||
bookmarkType: file ? 'file' : 'link',
|
||||
file,
|
||||
onConfirm: async (data: ChannelBookmarkCreate) => dispatch(createBookmark(channelId, data)) as ActionResult<boolean>,
|
||||
},
|
||||
}));
|
||||
}, [channelId, dispatch]);
|
||||
|
||||
const handleFileChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files?.length) {
|
||||
const [file] = e.target.files;
|
||||
handleCreate(file);
|
||||
clearFileInput(e.target);
|
||||
}
|
||||
}, [handleCreate]);
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const fileInput = (
|
||||
<input
|
||||
type='file'
|
||||
id='bookmark-create-file-input'
|
||||
className='bookmark-create-file-input'
|
||||
ref={fileInputRef}
|
||||
onChange={handleFileChanged}
|
||||
/>
|
||||
);
|
||||
|
||||
const handleCreateLink = useCallback(() => {
|
||||
handleCreate();
|
||||
}, [handleCreate]);
|
||||
|
||||
const handleCreateFile = useCallback(() => {
|
||||
fileInputRef.current?.click();
|
||||
}, [fileInputRef.current]);
|
||||
const {handleCreateLink, handleCreateFile} = useBookmarkAddActions(channelId);
|
||||
const canAdd = useChannelBookmarkPermission(channelId, 'add');
|
||||
|
||||
const addBookmarkLabel = formatMessage({id: 'channel_bookmarks.addBookmark', defaultMessage: 'Add a bookmark'});
|
||||
|
||||
@@ -93,9 +51,14 @@ export default ({
|
||||
} else if (hasBookmarks) {
|
||||
addBookmarkTooltipText = addBookmarkLabel;
|
||||
}
|
||||
|
||||
const addLinkLabel = formatMessage({id: 'channel_bookmarks.addLink', defaultMessage: 'Add a link'});
|
||||
const attachFileLabel = formatMessage({id: 'channel_bookmarks.attachFile', defaultMessage: 'Attach a file'});
|
||||
|
||||
if (!canAdd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuButtonContainer
|
||||
withLabel={showLabel}
|
||||
@@ -129,7 +92,6 @@ export default ({
|
||||
onClick={handleCreateLink}
|
||||
leadingElement={<LinkVariantIcon size={18}/>}
|
||||
labels={<span>{addLinkLabel}</span>}
|
||||
aria-label={addLinkLabel}
|
||||
/>
|
||||
{canUploadFiles && (
|
||||
<Menu.Item
|
||||
@@ -138,14 +100,14 @@ export default ({
|
||||
onClick={handleCreateFile}
|
||||
leadingElement={<PaperclipIcon size={18}/>}
|
||||
labels={<span>{attachFileLabel}</span>}
|
||||
aria-label={attachFileLabel}
|
||||
/>
|
||||
)}
|
||||
</Menu.Container>
|
||||
{fileInput}
|
||||
</MenuButtonContainer>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default memo(BookmarksMenu);
|
||||
|
||||
const MenuButtonContainer = styled.div<{withLabel: boolean}>`
|
||||
position: sticky;
|
||||
@@ -153,3 +115,46 @@ const MenuButtonContainer = styled.div<{withLabel: boolean}>`
|
||||
${({withLabel}) => !withLabel && css`padding: 0 1rem;`}
|
||||
background: linear-gradient(to right, rgba(var(--center-channel-bg-rgb), .16), rgba(var(--center-channel-bg-rgb), 1) 25%);
|
||||
`;
|
||||
|
||||
export const useBookmarkAddActions = (channelId: string) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleCreate = useCallback((file?: File) => {
|
||||
dispatch(openModal({
|
||||
modalId: ModalIdentifiers.CHANNEL_BOOKMARK_CREATE,
|
||||
dialogType: ChannelBookmarkCreateModal,
|
||||
dialogProps: {
|
||||
channelId,
|
||||
bookmarkType: file ? 'file' : 'link',
|
||||
file,
|
||||
onConfirm: async (data: ChannelBookmarkCreate) => dispatch(createBookmark(channelId, data)),
|
||||
},
|
||||
}));
|
||||
}, [channelId, dispatch]);
|
||||
|
||||
const handleCreateLink = useCallback(() => {
|
||||
handleCreate();
|
||||
}, [handleCreate]);
|
||||
|
||||
const handleCreateFile = useCallback(() => {
|
||||
const input: HTMLInputElement = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.id = 'bookmark-create-file-input';
|
||||
input.hidden = true;
|
||||
|
||||
input.addEventListener('change', () => {
|
||||
const file = input.files?.[0];
|
||||
if (file) {
|
||||
handleCreate(file);
|
||||
}
|
||||
input.remove();
|
||||
});
|
||||
input.addEventListener('cancel', input.remove);
|
||||
|
||||
document.getElementById('root-portal')?.appendChild(input);
|
||||
|
||||
input.click();
|
||||
}, [handleCreate]);
|
||||
|
||||
return {handleCreateLink, handleCreateFile};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// Purpose of this file to exists is only required until channel header dropdown is migrated to new menus
|
||||
import type {ComponentProps} from 'react';
|
||||
import React, {memo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import {
|
||||
LinkVariantIcon,
|
||||
PaperclipIcon,
|
||||
} from '@mattermost/compass-icons/components';
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
|
||||
import {getChannelBookmarks} from 'mattermost-redux/selectors/entities/channel_bookmarks';
|
||||
|
||||
import {useBookmarkAddActions} from 'components/channel_bookmarks/channel_bookmarks_menu';
|
||||
import {MAX_BOOKMARKS_PER_CHANNEL, useCanUploadFiles, useChannelBookmarkPermission} from 'components/channel_bookmarks/utils';
|
||||
import Menu from 'components/widgets/menu/menu';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
type Props = {
|
||||
channel: Channel;
|
||||
inHeaderDropdown?: boolean;
|
||||
};
|
||||
|
||||
const ChannelBookmarksSubmenu = (props: Props) => {
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
const {handleCreateLink, handleCreateFile} = useBookmarkAddActions(props.channel.id);
|
||||
const canAdd = useChannelBookmarkPermission(props.channel.id, 'add');
|
||||
const canUploadFiles = useCanUploadFiles();
|
||||
const limitReached = useSelector((state: GlobalState) => {
|
||||
const bookmarks = getChannelBookmarks(state, props.channel.id);
|
||||
return bookmarks && Object.keys(bookmarks).length >= MAX_BOOKMARKS_PER_CHANNEL;
|
||||
});
|
||||
|
||||
if (!canAdd || limitReached) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const items: ComponentProps<typeof Menu.ItemSubMenu>['subMenu'] = [
|
||||
{
|
||||
id: 'channelBookmarksAddLink',
|
||||
icon: <LinkVariantIcon size={16}/>,
|
||||
direction: 'right',
|
||||
text: formatMessage({id: 'channel_bookmarks.addLink', defaultMessage: 'Add a link'}),
|
||||
action: handleCreateLink,
|
||||
},
|
||||
];
|
||||
|
||||
if (canUploadFiles) {
|
||||
items.push({
|
||||
id: 'channelBookmarksAttachFile',
|
||||
icon: <PaperclipIcon size={16}/>,
|
||||
direction: 'right',
|
||||
text: formatMessage({id: 'channel_bookmarks.attachFile', defaultMessage: 'Attach a file'}),
|
||||
action: handleCreateFile,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu.ItemSubMenu
|
||||
id={`channel-menu-${props.channel.id}-bookmarks`}
|
||||
subMenu={items}
|
||||
text={formatMessage({id: 'sidebar_left.sidebar_channel_menu.bookmarks', defaultMessage: 'Bookmarks Bar'})}
|
||||
direction={'right'}
|
||||
styleSelectableItem={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ChannelBookmarksSubmenu);
|
||||
@@ -22,6 +22,7 @@ describe('components/ChannelHeaderDropdown', () => {
|
||||
penultimateViewedChannelName: 'test-channel',
|
||||
pluginMenuItems: [],
|
||||
isLicensedForLDAPGroups: false,
|
||||
isChannelBookmarksEnabled: false,
|
||||
};
|
||||
test('should match snapshot with no plugin items', () => {
|
||||
const wrapper = shallow(<ChannelHeaderDropdown {...defaultProps}/>);
|
||||
|
||||
@@ -10,6 +10,7 @@ import {Permissions} from 'mattermost-redux/constants';
|
||||
import {isGuest} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import AddGroupsToChannelModal from 'components/add_groups_to_channel_modal';
|
||||
import ChannelBookmarksSubmenu from 'components/channel_bookmarks_sub_menu';
|
||||
import ChannelGroupsManageModal from 'components/channel_groups_manage_modal';
|
||||
import ChannelInviteModal from 'components/channel_invite_modal';
|
||||
import ChannelMoveToSubMenuOld from 'components/channel_move_to_sub_menu_old';
|
||||
@@ -52,6 +53,7 @@ export type Props = {
|
||||
penultimateViewedChannelName: string;
|
||||
pluginMenuItems: PluginComponent[];
|
||||
isLicensedForLDAPGroups: boolean;
|
||||
isChannelBookmarksEnabled: boolean;
|
||||
}
|
||||
|
||||
export default class ChannelHeaderDropdown extends React.PureComponent<Props> {
|
||||
@@ -67,6 +69,7 @@ export default class ChannelHeaderDropdown extends React.PureComponent<Props> {
|
||||
isMobile,
|
||||
penultimateViewedChannelName,
|
||||
isLicensedForLDAPGroups,
|
||||
isChannelBookmarksEnabled,
|
||||
} = this.props;
|
||||
|
||||
if (!channel) {
|
||||
@@ -238,6 +241,7 @@ export default class ChannelHeaderDropdown extends React.PureComponent<Props> {
|
||||
</Menu.Group>
|
||||
|
||||
<Menu.Group divider={divider}>
|
||||
{isChannelBookmarksEnabled && <ChannelBookmarksSubmenu channel={channel}/>}
|
||||
<ChannelPermissionGate
|
||||
channelId={channel.id}
|
||||
teamId={channel.team_id}
|
||||
|
||||
@@ -23,6 +23,8 @@ import {
|
||||
import {getPenultimateViewedChannelName} from 'selectors/local_storage';
|
||||
import {getChannelHeaderMenuPluginComponents} from 'selectors/plugins';
|
||||
|
||||
import {getIsChannelBookmarksEnabled} from 'components/channel_bookmarks/utils';
|
||||
|
||||
import {Constants} from 'utils/constants';
|
||||
import * as Utils from 'utils/utils';
|
||||
|
||||
@@ -69,6 +71,7 @@ const mapStateToProps = (state: GlobalState) => ({
|
||||
penultimateViewedChannelName: getPenultimateViewedChannelName(state) || getRedirectChannelNameForTeam(state, getCurrentTeamId(state)),
|
||||
pluginMenuItems: getChannelHeaderMenuPluginComponents(state),
|
||||
isLicensedForLDAPGroups: state.entities.general.license.LDAPGroups === 'true',
|
||||
isChannelBookmarksEnabled: getIsChannelBookmarksEnabled(state),
|
||||
});
|
||||
|
||||
const mobileMapStateToProps = (state: GlobalState) => {
|
||||
|
||||
@@ -5138,6 +5138,7 @@
|
||||
"sidebar_left.sidebar_category.newLabel": "new",
|
||||
"sidebar_left.sidebar_channel_menu_convert_to_channel": "Convert to Private Channel",
|
||||
"sidebar_left.sidebar_channel_menu.addMembers": "Add Members",
|
||||
"sidebar_left.sidebar_channel_menu.bookmarks": "Bookmarks Bar",
|
||||
"sidebar_left.sidebar_channel_menu.channels": "Channels",
|
||||
"sidebar_left.sidebar_channel_menu.copyLink": "Copy Link",
|
||||
"sidebar_left.sidebar_channel_menu.dropdownAriaLabel": "Edit channel menu",
|
||||
|
||||
Ссылка в новой задаче
Block a user