MM-61525, MM-61868: Autohide channel bookmarks when empty (#29454)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
cf392f7cca
Коммит
09add85a51
46
e2e-tests/cypress/package-lock.json
сгенерированный
46
e2e-tests/cypress/package-lock.json
сгенерированный
@@ -11,8 +11,8 @@
|
||||
"@babel/eslint-parser": "7.24.1",
|
||||
"@babel/eslint-plugin": "7.23.5",
|
||||
"@cypress/request": "3.0.1",
|
||||
"@mattermost/client": "9.6.0",
|
||||
"@mattermost/types": "9.6.0",
|
||||
"@mattermost/client": "10.2.0",
|
||||
"@mattermost/types": "10.2.0",
|
||||
"@testing-library/cypress": "10.0.1",
|
||||
"@types/async": "3.2.24",
|
||||
"@types/authenticator": "1.1.4",
|
||||
@@ -1723,16 +1723,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mattermost/client": {
|
||||
"version": "9.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/client/-/client-9.6.0.tgz",
|
||||
"integrity": "sha512-H1SKLKWUSqbGb7OcSWS9PdIp9eFUPaotGhrOnjradpaAv5fLPmfTzkLL5ZwRTZ9j+hiOT+D/p3vpNBqjCItiNQ==",
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/client/-/client-10.2.0.tgz",
|
||||
"integrity": "sha512-rtrC72Gj66+CFuVGryex1z+IvBTHAEFY//z13i/2sm/tPDuWREJ0jJcxgB0XULrInDVN1k5oibu2aeqVkhrRNA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"form-data": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mattermost/types": "*",
|
||||
"typescript": "^4.3"
|
||||
"@mattermost/types": "^9.3.0 || ^10.0.0",
|
||||
"typescript": "^4.3.0 || ^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
@@ -1741,9 +1741,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mattermost/client/node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
||||
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
@@ -1755,12 +1755,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mattermost/types": {
|
||||
"version": "9.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/types/-/types-9.6.0.tgz",
|
||||
"integrity": "sha512-x3K4M+Qu7rutb7QgjzZsJwPoCMojvcBH46vv3woeiYq7NIIg6vTg4XkDB31PktGRJRr9BtaXuLi0b0hLY6XE3A==",
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/types/-/types-10.2.0.tgz",
|
||||
"integrity": "sha512-Qd8rnnvn4nfJ4wObG/a2sdfy7NshqMVGnd73AzP9/ZDjE/esm8cIL89Ks3n0FJ83wdw96Omd4lFuSxAm1ftVjw==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"typescript": "^4.3"
|
||||
"typescript": "^4.3.0 || ^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
@@ -12619,18 +12619,18 @@
|
||||
}
|
||||
},
|
||||
"@mattermost/client": {
|
||||
"version": "9.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/client/-/client-9.6.0.tgz",
|
||||
"integrity": "sha512-H1SKLKWUSqbGb7OcSWS9PdIp9eFUPaotGhrOnjradpaAv5fLPmfTzkLL5ZwRTZ9j+hiOT+D/p3vpNBqjCItiNQ==",
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/client/-/client-10.2.0.tgz",
|
||||
"integrity": "sha512-rtrC72Gj66+CFuVGryex1z+IvBTHAEFY//z13i/2sm/tPDuWREJ0jJcxgB0XULrInDVN1k5oibu2aeqVkhrRNA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"form-data": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
||||
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
@@ -12641,9 +12641,9 @@
|
||||
}
|
||||
},
|
||||
"@mattermost/types": {
|
||||
"version": "9.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/types/-/types-9.6.0.tgz",
|
||||
"integrity": "sha512-x3K4M+Qu7rutb7QgjzZsJwPoCMojvcBH46vv3woeiYq7NIIg6vTg4XkDB31PktGRJRr9BtaXuLi0b0hLY6XE3A==",
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/types/-/types-10.2.0.tgz",
|
||||
"integrity": "sha512-Qd8rnnvn4nfJ4wObG/a2sdfy7NshqMVGnd73AzP9/ZDjE/esm8cIL89Ks3n0FJ83wdw96Omd4lFuSxAm1ftVjw==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
"@babel/eslint-parser": "7.24.1",
|
||||
"@babel/eslint-plugin": "7.23.5",
|
||||
"@cypress/request": "3.0.1",
|
||||
"@mattermost/client": "9.6.0",
|
||||
"@mattermost/types": "9.6.0",
|
||||
"@mattermost/client": "10.2.0",
|
||||
"@mattermost/types": "10.2.0",
|
||||
"@testing-library/cypress": "10.0.1",
|
||||
"@types/async": "3.2.24",
|
||||
"@types/authenticator": "1.1.4",
|
||||
|
||||
@@ -25,209 +25,368 @@ describe('Channel Bookmarks', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
let user1: UserProfile;
|
||||
let admin: UserProfile;
|
||||
let channel: Channel;
|
||||
let publicChannel: Channel;
|
||||
let privateChannel: Channel;
|
||||
|
||||
const BOOKMARK_LIMIT = 50;
|
||||
|
||||
before(() => {
|
||||
cy.apiRequireLicense();
|
||||
|
||||
cy.apiGetMe().then(({user: adminUser}) => {
|
||||
admin = adminUser;
|
||||
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
cy.apiInitSetup().then(({team, user, channel}) => {
|
||||
testTeam = team;
|
||||
user1 = user;
|
||||
publicChannel = channel;
|
||||
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
cy.getCurrentChannelId().then((channelId) => {
|
||||
cy.makeClient().then(async (client) => {
|
||||
channel = await client.getChannel(channelId);
|
||||
});
|
||||
cy.apiCreateChannel(testTeam.id, 'private-channel', 'private channel', 'P').then((result) => {
|
||||
privateChannel = result.channel;
|
||||
cy.apiAddUserToChannel(privateChannel.id, user1.id);
|
||||
});
|
||||
|
||||
cy.visit(`/${testTeam.name}/channels/${publicChannel.name}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('create link bookmark', () => {
|
||||
// # Create link
|
||||
const {link, realLink} = createLinkBookmark();
|
||||
describe('functionality', () => {
|
||||
it('bookmarks bar hidden when empty', () => {
|
||||
// # Go to channel menu
|
||||
cy.uiGetChannelInfoButton();
|
||||
cy.makeClient().then(async (client) => {
|
||||
const bookmarks = await client.getChannelBookmarks(publicChannel.id);
|
||||
cy.wrap(bookmarks.length).should('eq', 0);
|
||||
});
|
||||
|
||||
cy.findByTestId('channel-bookmarks-container').within(() => {
|
||||
// * Verify bookmarks bar not present
|
||||
cy.findByTestId('channel-bookmarks-container').should('not.exist');
|
||||
});
|
||||
|
||||
it('create link bookmark from channel menu', () => {
|
||||
// # Create link
|
||||
const {link, realLink} = createLinkBookmark({fromChannelMenu: true});
|
||||
|
||||
// * Verify bar now visible
|
||||
cy.findByTestId('channel-bookmarks-container').within(() => {
|
||||
// * Verify href
|
||||
cy.findByRole('link', {name: link}).should('have.attr', 'href', realLink);
|
||||
cy.findByRole('link', {name: link}).should('have.attr', 'href', realLink);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('create link bookmark, with emoji and custom title', () => {
|
||||
const {realLink, displayName, emojiName} = createLinkBookmark({displayName: 'custom display name', emojiName: 'smiling_face_with_3_hearts'});
|
||||
it('create link bookmark', () => {
|
||||
// # Create link
|
||||
const {link, realLink} = createLinkBookmark();
|
||||
|
||||
cy.findByTestId('channel-bookmarks-container').within(() => {
|
||||
cy.findByTestId('channel-bookmarks-container').within(() => {
|
||||
// * Verify href
|
||||
cy.findByRole('link', {name: link}).should('have.attr', 'href', realLink);
|
||||
});
|
||||
});
|
||||
|
||||
it('create link bookmark, with emoji and custom title', () => {
|
||||
const {realLink, displayName, emojiName} = createLinkBookmark({displayName: 'custom display name', emojiName: 'smiling_face_with_3_hearts'});
|
||||
|
||||
cy.findByTestId('channel-bookmarks-container').within(() => {
|
||||
// * Verify emoji, displayname, and href
|
||||
cy.findByRole('link', {name: `:${emojiName}: ${displayName}`}).should('have.attr', 'href', realLink);
|
||||
cy.findByRole('link', {name: `:${emojiName}: ${displayName}`}).should('have.attr', 'href', realLink);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('create file bookmark, and open preview', () => {
|
||||
it('create file bookmark from channel menu', () => {
|
||||
// # Create bookmark
|
||||
const {file} = createFileBookmark({file: 'small-image.png'});
|
||||
const {file} = createFileBookmark({file: 'mp4-video-file.mp4', fromChannelMenu: true});
|
||||
|
||||
// * Verify preview icon
|
||||
cy.findByRole('link', {name: file}).as('link').find('.file-icon.image');
|
||||
|
||||
// # Open preview
|
||||
cy.get('@link').click();
|
||||
|
||||
// * Verify preview opened
|
||||
cy.get('.file-preview-modal').findByRole('heading', {name: file});
|
||||
cy.get('.file-preview-modal__file-details-user-name').should('have.text', admin.username);
|
||||
cy.get('.file-preview-modal__channel').should('have.text', `Shared in ~${channel.display_name}`);
|
||||
cy.get('.icon-close').click();
|
||||
});
|
||||
|
||||
it('create file bookmark, progress and cancel upload', () => {
|
||||
const file = 'powerpointx-file.pptx';
|
||||
|
||||
cy.intercept(
|
||||
{method: 'POST', pathname: 'files', middleware: true, times: 1},
|
||||
() => {
|
||||
return new Promise((resolve) =>
|
||||
setTimeout(() => resolve(), 2000),
|
||||
);
|
||||
}).as('uploadRequest');
|
||||
// * Verify uploaded
|
||||
cy.findByRole('link', {name: file});
|
||||
});
|
||||
|
||||
it('create file bookmark, and open preview', () => {
|
||||
// # Create bookmark
|
||||
createFileBookmark({file, save: false});
|
||||
const {file} = createFileBookmark({file: 'small-image.png'});
|
||||
|
||||
// # Cancel upload
|
||||
cy.get('a.file-preview__remove').click();
|
||||
// * Verify preview icon
|
||||
cy.findByRole('link', {name: file}).as('link').find('.file-icon.image');
|
||||
|
||||
// * Verify empty preview container
|
||||
cy.get('.file-preview__container.empty');
|
||||
// # Open preview
|
||||
cy.get('@link').click();
|
||||
|
||||
// * Verify upload cancelled
|
||||
cy.wait('@uploadRequest').its('state').should('eq', 'Errored');
|
||||
// * Verify preview opened
|
||||
cy.get('.file-preview-modal').findByRole('heading', {name: file});
|
||||
cy.get('.file-preview-modal__file-details-user-name').should('have.text', admin.username);
|
||||
cy.get('.file-preview-modal__channel').should('have.text', `Shared in ~${publicChannel.display_name}`);
|
||||
cy.get('.icon-close').click();
|
||||
});
|
||||
|
||||
// * Verify cannot save
|
||||
cy.findByRole('button', {name: 'Add bookmark'}).should('be.disabled');
|
||||
it('create file bookmark, progress and cancel upload', () => {
|
||||
const file = 'powerpointx-file.pptx';
|
||||
|
||||
// # Try upload file again
|
||||
cy.get('#bookmark-create-file-input-in-modal').attachFile(file);
|
||||
cy.intercept(
|
||||
{method: 'POST', pathname: 'files', middleware: true, times: 1},
|
||||
() => {
|
||||
return new Promise((resolve) =>
|
||||
setTimeout(() => resolve(), 2000),
|
||||
);
|
||||
}).as('uploadRequest');
|
||||
|
||||
// * Verify uploaded
|
||||
cy.findByTestId('titleInput').should('have.value', file);
|
||||
cy.findByRole('link', {name: `file thumbnail ${file}`});
|
||||
// # Create bookmark
|
||||
createFileBookmark({file, save: false});
|
||||
|
||||
// # Save
|
||||
editModalCreate();
|
||||
// # Cancel upload
|
||||
cy.get('a.file-preview__remove').click();
|
||||
|
||||
// * Verify bookmark created
|
||||
cy.findByRole('link', {name: file});
|
||||
});
|
||||
// * Verify empty preview container
|
||||
cy.get('.file-preview__container.empty');
|
||||
|
||||
it('create file bookmark, with emoji and custom title', () => {
|
||||
// * Verify upload cancelled
|
||||
cy.wait('@uploadRequest').its('state').should('eq', 'Errored');
|
||||
|
||||
// * Verify cannot save
|
||||
cy.findByRole('button', {name: 'Add bookmark'}).should('be.disabled');
|
||||
|
||||
// # Try upload file again
|
||||
cy.get('#bookmark-create-file-input-in-modal').attachFile(file);
|
||||
|
||||
// * Verify uploaded
|
||||
cy.findByTestId('titleInput').should('have.value', file);
|
||||
cy.findByRole('link', {name: `file thumbnail ${file}`});
|
||||
|
||||
// # Save
|
||||
editModalCreate();
|
||||
|
||||
// * Verify bookmark created
|
||||
cy.findByRole('link', {name: file});
|
||||
});
|
||||
|
||||
it('create file bookmark, with emoji and custom title', () => {
|
||||
// # Create bookmark
|
||||
const {file, displayName, emojiName} = createFileBookmark({file: 'm4a-audio-file.m4a', displayName: 'custom displayname small-image', emojiName: 'smiling_face_with_3_hearts'});
|
||||
const {file, displayName, emojiName} = createFileBookmark({file: 'm4a-audio-file.m4a', displayName: 'custom displayname small-image', emojiName: 'smiling_face_with_3_hearts'});
|
||||
|
||||
// * Verify emoji and custom display name
|
||||
cy.findByRole('link', {name: `:${emojiName}: ${displayName}`}).click();
|
||||
// * Verify emoji and custom display name
|
||||
cy.findByRole('link', {name: `:${emojiName}: ${displayName}`}).click();
|
||||
|
||||
// * Verify preview opened
|
||||
cy.get('.file-preview-modal').findByRole('heading', {name: file});
|
||||
cy.get('.icon-close').click();
|
||||
});
|
||||
// * Verify preview opened
|
||||
cy.get('.file-preview-modal').findByRole('heading', {name: file});
|
||||
cy.get('.icon-close').click();
|
||||
});
|
||||
|
||||
it('edit link bookmark', () => {
|
||||
it('edit link bookmark', () => {
|
||||
// # Create link
|
||||
const {displayName} = createLinkBookmark();
|
||||
const {displayName} = createLinkBookmark();
|
||||
|
||||
const nextLink = 'google.com/q=test001';
|
||||
const realNextLink = `http://${nextLink}`;
|
||||
const nextDisplayName = 'Next custom display name';
|
||||
const nextEmojiName = 'handshake';
|
||||
const nextLink = 'google.com/q=test001';
|
||||
const realNextLink = `http://${nextLink}`;
|
||||
const nextDisplayName = 'Next custom display name';
|
||||
const nextEmojiName = 'handshake';
|
||||
|
||||
// # Open edit
|
||||
openEditModal(displayName);
|
||||
// # Open edit
|
||||
openEditModal(displayName);
|
||||
|
||||
// # Change link, displayname, emoji
|
||||
editTextInput('linkInput', nextLink);
|
||||
editTextInput('titleInput', nextDisplayName);
|
||||
selectEmoji(nextEmojiName);
|
||||
// # Change link, displayname, emoji
|
||||
editTextInput('linkInput', nextLink);
|
||||
editTextInput('titleInput', nextDisplayName);
|
||||
selectEmoji(nextEmojiName);
|
||||
|
||||
// # Save
|
||||
editModalSave();
|
||||
// # Save
|
||||
editModalSave();
|
||||
|
||||
// * Verify changes
|
||||
cy.findAllByRole('link', {name: `:${nextEmojiName}: ${nextDisplayName}`}).should('have.attr', 'href', realNextLink);
|
||||
});
|
||||
// * Verify changes
|
||||
cy.findAllByRole('link', {name: `:${nextEmojiName}: ${nextDisplayName}`}).should('have.attr', 'href', realNextLink);
|
||||
});
|
||||
|
||||
it('edit link bookmark, only display name and emoji', () => {
|
||||
it('edit link bookmark, only display name and emoji', () => {
|
||||
// # Create link
|
||||
const {displayName, realLink} = createLinkBookmark();
|
||||
const {displayName, realLink} = createLinkBookmark();
|
||||
|
||||
const nextDisplayName = 'Next custom display name 2';
|
||||
const nextEmojiName = 'handshake';
|
||||
const nextDisplayName = 'Next custom display name 2';
|
||||
const nextEmojiName = 'handshake';
|
||||
|
||||
// # Open edit
|
||||
openEditModal(displayName);
|
||||
// # Open edit
|
||||
openEditModal(displayName);
|
||||
|
||||
// # Change link, displayname, emoji
|
||||
editTextInput('titleInput', nextDisplayName);
|
||||
selectEmoji(nextEmojiName);
|
||||
// # Change link, displayname, emoji
|
||||
editTextInput('titleInput', nextDisplayName);
|
||||
selectEmoji(nextEmojiName);
|
||||
|
||||
// # Save
|
||||
editModalSave();
|
||||
// # Save
|
||||
editModalSave();
|
||||
|
||||
// * Verify changes
|
||||
cy.findAllByRole('link', {name: `:${nextEmojiName}: ${nextDisplayName}`}).should('have.attr', 'href', realLink);
|
||||
});
|
||||
// * Verify changes
|
||||
cy.findAllByRole('link', {name: `:${nextEmojiName}: ${nextDisplayName}`}).should('have.attr', 'href', realLink);
|
||||
});
|
||||
|
||||
it('delete bookmark', () => {
|
||||
const {displayName} = createLinkBookmark();
|
||||
it('delete bookmark', () => {
|
||||
const {displayName} = createLinkBookmark();
|
||||
|
||||
// * Verify bookmark exists
|
||||
cy.findByRole('link', {name: displayName});
|
||||
// * Verify bookmark exists
|
||||
cy.findByRole('link', {name: displayName});
|
||||
|
||||
// # Start delete bookmark flow
|
||||
openDotMenu(displayName);
|
||||
cy.findByRole('menuitem', {name: 'Delete'}).click();
|
||||
cy.findByRole('dialog', {name: 'Delete bookmark'}).within(() => {
|
||||
// # Start delete bookmark flow
|
||||
openDotMenu(displayName);
|
||||
cy.findByRole('menuitem', {name: 'Delete'}).click();
|
||||
cy.findByRole('dialog', {name: 'Delete bookmark'}).within(() => {
|
||||
// * Verify delete dialog contents
|
||||
cy.findByRole('heading', {name: 'Delete bookmark'});
|
||||
cy.contains(`Are you sure you want to delete the bookmark ${displayName}?`);
|
||||
cy.findByRole('heading', {name: 'Delete bookmark'});
|
||||
cy.contains(`Are you sure you want to delete the bookmark ${displayName}?`);
|
||||
|
||||
// # Delete bookmark
|
||||
cy.findByRole('button', {name: 'Yes, delete'}).click();
|
||||
// # Delete bookmark
|
||||
cy.findByRole('button', {name: 'Yes, delete'}).click();
|
||||
});
|
||||
|
||||
// * Verify bookmark deleted
|
||||
cy.findByRole('link', {name: displayName}).should('not.exist');
|
||||
});
|
||||
|
||||
// * Verify bookmark deleted
|
||||
cy.findByRole('link', {name: displayName}).should('not.exist');
|
||||
});
|
||||
it('reorder bookmark', () => {
|
||||
const {displayName: name1} = createFileBookmark({file: 'm4a-audio-file.m4a', displayName: 'custom displayname 1'});
|
||||
const {displayName: name2} = createFileBookmark({file: 'm4a-audio-file.m4a', displayName: 'custom displayname 2'});
|
||||
|
||||
it('reorder bookmark', () => {
|
||||
const {displayName: name1} = createFileBookmark({file: 'm4a-audio-file.m4a', displayName: 'custom displayname 1'});
|
||||
const {displayName: name2} = createFileBookmark({file: 'm4a-audio-file.m4a', displayName: 'custom displayname 2'});
|
||||
// # Start reorder bookmark flow
|
||||
cy.findByTestId('channel-bookmarks-container').within(() => {
|
||||
cy.findAllByRole('link').should('be.visible').as('bookmarks');
|
||||
cy.get('@bookmarks').eq(-1).scrollIntoView();
|
||||
cy.get('@bookmarks').eq(-2).should('contain', name1);
|
||||
cy.get('@bookmarks').eq(-1).should('contain', name2);
|
||||
|
||||
// # Start reorder bookmark flow
|
||||
cy.findByTestId('channel-bookmarks-container').within(() => {
|
||||
cy.findAllByRole('link').should('be.visible').as('bookmarks');
|
||||
cy.get('@bookmarks').eq(-1).scrollIntoView();
|
||||
cy.get('@bookmarks').eq(-2).should('contain', name1);
|
||||
cy.get('@bookmarks').eq(-1).should('contain', name2);
|
||||
// # Perform drag using keyboard
|
||||
cy.get(`a:contains(${name1})`).
|
||||
trigger('keydown', {keyCode: SpaceKeyCode}).
|
||||
trigger('keydown', {keyCode: RightArrowKeyCode, force: true}).wait(TIMEOUTS.THREE_SEC).
|
||||
trigger('keydown', {keyCode: SpaceKeyCode, force: true}).wait(TIMEOUTS.THREE_SEC);
|
||||
|
||||
// # Perform drag using keyboard
|
||||
cy.get(`a:contains(${name1})`).
|
||||
trigger('keydown', {keyCode: SpaceKeyCode}).
|
||||
trigger('keydown', {keyCode: RightArrowKeyCode, force: true}).wait(TIMEOUTS.THREE_SEC).
|
||||
trigger('keydown', {keyCode: SpaceKeyCode, force: true}).wait(TIMEOUTS.THREE_SEC);
|
||||
|
||||
// * Verify correct order
|
||||
cy.findAllByRole('link').should('be.visible').as('bookmarks-after');
|
||||
cy.get('@bookmarks-after').eq(-2).should('contain', name2);
|
||||
cy.get('@bookmarks-after').eq(-1).should('contain', name1);
|
||||
// * Verify correct order
|
||||
cy.findAllByRole('link').should('be.visible').as('bookmarks-after');
|
||||
cy.get('@bookmarks-after').eq(-2).should('contain', name2);
|
||||
cy.get('@bookmarks-after').eq(-1).should('contain', name1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('manage bookmarks - permissions enforcement', () => {
|
||||
before(() => {
|
||||
// # Prep: disable manage bookmarks for users - public and private channels
|
||||
cy.uiResetPermissionsToDefault();
|
||||
cy.findByTestId('all_users-public_channel-manage_public_channel_bookmarks-checkbox').click();
|
||||
cy.findByTestId('all_users-private_channel-manage_private_channel_bookmarks-checkbox').click();
|
||||
cy.uiSaveConfig();
|
||||
cy.visit(`/${testTeam.name}/channels/${publicChannel.name}`);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
// # Reset permissions
|
||||
cy.apiAdminLogin();
|
||||
cy.uiResetPermissionsToDefault();
|
||||
cy.visit(`/${testTeam.name}/channels/${publicChannel.name}`);
|
||||
});
|
||||
|
||||
it('can add bookmark: public channel, admin', () => {
|
||||
// # To public channel with bookmark
|
||||
createLinkBookmark({fromChannelMenu: true});
|
||||
|
||||
// * Verify admin can create in public channel
|
||||
verifyCanCreate();
|
||||
});
|
||||
|
||||
it('can add bookmark: private channel, admin', () => {
|
||||
// # To private channel with bookmark
|
||||
cy.visit(`/${testTeam.name}/channels/${privateChannel.name}`);
|
||||
createLinkBookmark({fromChannelMenu: true});
|
||||
|
||||
// * Verify admin can create in private channel
|
||||
verifyCanCreate();
|
||||
});
|
||||
|
||||
it('cannot add bookmark: private channel, non-admin', () => {
|
||||
// # Switch to non-admin user
|
||||
cy.apiLogin(user1);
|
||||
|
||||
// # private channel
|
||||
cy.visit(`/${testTeam.name}/channels/${privateChannel.name}`);
|
||||
|
||||
// * Verify non-admin user cannot create in private channel
|
||||
verifyCannotCreate();
|
||||
});
|
||||
|
||||
it('cannot add bookmark: public channel, non-admin', () => {
|
||||
// # To public channel with bookmark
|
||||
cy.visit(`/${testTeam.name}/channels/${publicChannel.name}`);
|
||||
|
||||
// * Verify non-admin user cannot create in public channel
|
||||
verifyCannotCreate();
|
||||
});
|
||||
|
||||
const verifyCanCreate = () => {
|
||||
// * Verify can access create UI - bookmarks bar
|
||||
promptAddLink();
|
||||
cy.uiCloseModal('Add a bookmark');
|
||||
|
||||
// * Verify can access create UI - channel menu
|
||||
promptAddLink(true);
|
||||
cy.uiCloseModal('Add a bookmark');
|
||||
};
|
||||
|
||||
const verifyCannotCreate = () => {
|
||||
// * Verify cannot access create UI - channel menu
|
||||
cy.uiOpenChannelMenu();
|
||||
cy.findByRole('menuitem', {name: 'Bookmarks Bar submenu icon'}).should('not.exist');
|
||||
|
||||
// * Verify cannot access create UI - bookmarks bar
|
||||
cy.get('#channelBookmarksPlusMenuButton').should('not.exist');
|
||||
};
|
||||
});
|
||||
|
||||
describe('limits enforced', () => {
|
||||
it('max bookmarks', () => {
|
||||
// # Create links, fill to max
|
||||
makeBookmarks(publicChannel);
|
||||
|
||||
// * Verify add bookmark button is disabled
|
||||
cy.findAllByRole('button', {name: 'Add a bookmark'}).should('be.disabled');
|
||||
|
||||
// * Verify create bookmark submenu in channel menu is not shown
|
||||
cy.uiOpenChannelMenu();
|
||||
cy.findByRole('menuitem', {name: 'Bookmarks Bar submenu icon'}).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
function makeBookmarks(channel: Channel, n?: number) {
|
||||
cy.makeClient().then(async (client) => {
|
||||
const nToMake = n ?? BOOKMARK_LIMIT - (await client.getChannelBookmarks(channel.id))?.length;
|
||||
|
||||
await Promise.allSettled(Array(nToMake).fill(0).map(() => {
|
||||
return client.createChannelBookmark(publicChannel.id, {
|
||||
type: 'link',
|
||||
display_name: 'google.com',
|
||||
link_url: `https://google.com/?q=test${getRandomId(7)}`,
|
||||
}, '');
|
||||
}));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function promptAddLink() {
|
||||
cy.get('#channelBookmarksPlusMenuButton').click();
|
||||
cy.get('#channelBookmarksAddLink').click();
|
||||
function promptAddLink(fromChannelMenu = false) {
|
||||
if (fromChannelMenu) {
|
||||
cy.uiOpenChannelMenu();
|
||||
|
||||
cy.findByRole('menuitem', {name: 'Bookmarks Bar submenu icon'}).trigger('mouseover');
|
||||
cy.findByRole('menuitem', {name: 'Add a link not selected'}).click();
|
||||
} else {
|
||||
cy.get('#channelBookmarksPlusMenuButton').click();
|
||||
cy.get('#channelBookmarksAddLink').click();
|
||||
}
|
||||
}
|
||||
|
||||
function promptAddFile(fromChannelMenu = false) {
|
||||
if (fromChannelMenu) {
|
||||
cy.uiOpenChannelMenu();
|
||||
|
||||
cy.findByRole('menuitem', {name: 'Bookmarks Bar submenu icon'}).trigger('mouseover');
|
||||
cy.findByRole('menuitem', {name: 'Attach a file not selected'}).click();
|
||||
} else {
|
||||
cy.get('#channelBookmarksPlusMenuButton').click();
|
||||
cy.get('#channelBookmarksAttachFile').click();
|
||||
}
|
||||
}
|
||||
|
||||
function openEditModal(name: string) {
|
||||
@@ -256,11 +415,12 @@ function createLinkBookmark({
|
||||
displayName = '',
|
||||
emojiName = '', // e.g. smile
|
||||
save = true,
|
||||
fromChannelMenu = false,
|
||||
} = {}) {
|
||||
const realLink = `http://${link}`;
|
||||
|
||||
// # Add link
|
||||
promptAddLink();
|
||||
promptAddLink(fromChannelMenu);
|
||||
|
||||
// # Enter link
|
||||
editTextInput('linkInput', link);
|
||||
@@ -290,8 +450,10 @@ function createFileBookmark({
|
||||
displayName = '',
|
||||
emojiName = '', // e.g. smile
|
||||
save = true,
|
||||
fromChannelMenu = false,
|
||||
} = {}) {
|
||||
cy.get('#bookmark-create-file-input').attachFile(file);
|
||||
promptAddFile(fromChannelMenu);
|
||||
cy.get('#root-portal #bookmark-create-file-input').attachFile(file);
|
||||
|
||||
if (displayName) {
|
||||
// # Enter displayname
|
||||
|
||||
@@ -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