Rewrite: Room Header (#19808)

pull/19929/head
Guilherme Gazzo 5 years ago committed by GitHub
parent 8b6bfbb10b
commit 08059e4e18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      app/apps/client/gameCenter/gameCenter.js
  2. 31
      app/apps/client/gameCenter/tabBar.js
  3. 28
      app/apps/client/gameCenter/tabBar.ts
  4. 28
      app/autotranslate/client/lib/tabBar.js
  5. 19
      app/autotranslate/client/lib/tabBar.ts
  6. 16
      app/channel-settings/client/startup/tabBar.js
  7. 14
      app/channel-settings/client/startup/tabBar.ts
  8. 1
      app/discussion/client/index.js
  9. 17
      app/discussion/client/tabBar.js
  10. 18
      app/discussion/client/tabBar.ts
  11. 11
      app/discussion/client/views/DiscussionTabbar.js
  12. 37
      app/e2e/client/tabbar.js
  13. 28
      app/e2e/client/tabbar.ts
  14. 92
      app/lib/client/defaultTabBars.js
  15. 1
      app/lib/client/index.js
  16. 23
      app/livechat/client/externalFrame/tabBar.js
  17. 18
      app/livechat/client/externalFrame/tabBar.ts
  18. 29
      app/livechat/client/hooks/onCreateRoomTabBar.js
  19. 2
      app/livechat/client/index.js
  20. 19
      app/livechat/client/tabBar.ts
  21. 26
      app/livechat/client/ui.js
  22. 5
      app/livechat/client/views/app/tabbar/contactChatHistory.js
  23. 28
      app/livechat/lib/LivechatRoomType.js
  24. 30
      app/livestream/client/tabBar.js
  25. 29
      app/livestream/client/tabBar.tsx
  26. 4
      app/mentions-flextab/client/actionButton.js
  27. 14
      app/mentions-flextab/client/tabBar.js
  28. 10
      app/mentions-flextab/client/tabBar.ts
  29. 2
      app/mentions-flextab/client/views/mentionsFlexTab.js
  30. 22
      app/message-pin/client/tabBar.js
  31. 16
      app/message-pin/client/tabBar.ts
  32. 4
      app/message-pin/client/views/pinnedMessages.js
  33. 12
      app/message-snippet/client/router.js
  34. 22
      app/message-snippet/client/tabBar/tabBar.js
  35. 16
      app/message-snippet/client/tabBar/tabBar.ts
  36. 14
      app/message-star/client/tabBar.js
  37. 10
      app/message-star/client/tabBar.ts
  38. 3
      app/message-star/client/views/starredMessages.js
  39. 1
      app/otr/client/rocketchat.otr.js
  40. 27
      app/otr/client/tabBar.js
  41. 31
      app/otr/client/tabBar.ts
  42. 15
      app/push-notifications/client/tabBar.js
  43. 12
      app/push-notifications/client/tabBar.ts
  44. 12
      app/reactions/client/init.js
  45. 2
      app/search/client/provider/result.js
  46. 4
      app/search/client/style/style.css
  47. 2
      app/theme/client/imports/components/contextual-bar.css
  48. 268
      app/theme/client/imports/components/header.css
  49. 33
      app/theme/client/imports/general/base.css
  50. 270
      app/theme/client/imports/general/base_old.css
  51. 12
      app/theme/client/imports/general/rtl.css
  52. 29
      app/theme/client/imports/general/theme_old.css
  53. 4
      app/theme/client/imports/general/variables.css
  54. 17
      app/threads/client/components/ThreadComponent.tsx
  55. 4
      app/threads/client/components/ThreadSkeleton.tsx
  56. 23
      app/threads/client/components/ThreadView.tsx
  57. 2
      app/threads/client/flextab/thread.js
  58. 37
      app/threads/client/flextab/threadlist.js
  59. 40
      app/threads/client/flextab/threadlist.tsx
  60. 3
      app/threads/client/flextab/threads.html
  61. 16
      app/threads/client/flextab/threads.js
  62. 4
      app/threads/client/messageAction/replyInThread.js
  63. 3
      app/threads/client/threads.css
  64. 18
      app/ui-clean-history/client/lib/startup.js
  65. 18
      app/ui-clean-history/client/lib/startup.ts
  66. 0
      app/ui-flextab/README.md
  67. 101
      app/ui-flextab/client/flexTabBar.html
  68. 260
      app/ui-flextab/client/flexTabBar.js
  69. 3
      app/ui-flextab/client/index.js
  70. 1
      app/ui-flextab/index.js
  71. 8
      app/ui-sidenav/client/roomList.js
  72. 2
      app/ui-utils/client/index.js
  73. 2
      app/ui-utils/client/lib/Layout.js
  74. 12
      app/ui-utils/client/lib/MessageAction.js
  75. 44
      app/ui-utils/client/lib/ReactionListContent.js
  76. 86
      app/ui-utils/client/lib/RocketChatTabBar.js
  77. 104
      app/ui-utils/client/lib/TabBar.js
  78. 59
      app/ui-utils/client/lib/popover.js
  79. 28
      app/ui/client/components/contextualBar.html
  80. 31
      app/ui/client/components/contextualBar.js
  81. 4
      app/ui/client/components/header/header.js
  82. 75
      app/ui/client/components/header/headerRoom.html
  83. 251
      app/ui/client/components/header/headerRoom.js
  84. 3
      app/ui/client/index.js
  85. 13
      app/ui/client/lib/iframeCommands.js
  86. 317
      app/ui/client/views/app/lib/getCommonRoomEvents.js
  87. 17
      app/ui/client/views/app/room.html
  88. 1233
      app/ui/client/views/app/room.js
  89. 90
      app/videobridge/client/tabBar.js
  90. 71
      app/videobridge/client/tabBar.tsx
  91. 10
      app/videobridge/client/views/videoFlexTab.js
  92. 41
      client/admin/federationDashboard/OverviewSection.js
  93. 153
      client/components/Header/Header.stories.js
  94. 69
      client/components/Header/Header.tsx
  95. 3
      client/components/Header/index.js
  96. 9
      client/components/ScrollableContentWrapper.tsx
  97. 0
      client/components/Skeleton.js
  98. 78
      client/components/VerticalBar.js
  99. 2
      client/contexts/AvatarUrlContext.ts
  100. 32
      client/contexts/LayoutContext.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -3,6 +3,7 @@ import { ReactiveVar } from 'meteor/reactive-var';
import { modal } from '../../../ui-utils/client';
import { APIClient, t, handleError } from '../../../utils/client';
import './gameCenter.html';
const getExternalComponents = async (instance) => {
try {

@ -1,31 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { APIClient } from '../../../utils/client';
import { TabBar } from '../../../ui-utils/client';
import { settings } from '../../../settings/client';
import './gameCenter.html';
Meteor.startup(function() {
Tracker.autorun(async function() {
if (!settings.get('Apps_Game_Center_enabled')) {
return TabBar.removeButton('gameCenter');
}
const { externalComponents } = await APIClient.get('apps/externalComponents');
if (!externalComponents.length) {
return TabBar.removeButton('gameCenter');
}
TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'gameCenter',
i18nTitle: 'Apps_Game_Center',
icon: 'game',
template: 'GameCenter',
order: -1,
});
});
});

@ -0,0 +1,28 @@
import { useMemo } from 'react';
import { useSetting } from '../../../../client/contexts/SettingsContext';
import { addAction } from '../../../../client/views/room/lib/Toolbox';
import { useEndpointData } from '../../../../client/hooks/useEndpointData';
import { AsyncStatePhase } from '../../../../client/hooks/useAsyncState';
addAction('game-center', () => {
const enabled = useSetting('Apps_Game_Center_enabled');
const { value = { externalComponents: [] }, phase: state, error } = useEndpointData('/apps/externalComponents');
const hasExternalComponents = value && value.externalComponents.length > 0;
const hasError = !!error;
return useMemo(() =>
(enabled
&& state === AsyncStatePhase.LOADING
&& !hasError
&& hasExternalComponents
? {
groups: ['channel', 'group', 'direct'],
id: 'game-center',
title: 'Apps_Game_Center',
icon: 'game',
template: 'GameCenter',
order: -1,
} : null), [enabled, hasError, hasExternalComponents, state]);
});

@ -1,28 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { hasPermission } from '../../../authorization';
import { settings } from '../../../settings';
import { TabBar } from '../../../ui-utils';
Meteor.startup(() => {
Tracker.autorun(() => {
const isEnabled = settings.get('AutoTranslate_Enabled') && hasPermission('auto-translate');
if (!isEnabled) {
TabBar.removeButton('autotranslate');
return;
}
TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'autotranslate',
i18nTitle: 'Auto_Translate',
icon: 'language',
template: 'AutoTranslate',
full: true,
order: 20,
});
});
});

@ -0,0 +1,19 @@
import { lazy, useMemo } from 'react';
import { addAction } from '../../../../client/views/room/lib/Toolbox';
import { usePermission } from '../../../../client/contexts/AuthorizationContext';
import { useSetting } from '../../../../client/contexts/SettingsContext';
addAction('autotranslate', () => {
const hasPermission = usePermission('auto-translate');
const autoTranslateEnabled = useSetting('AutoTranslate_Enabled');
return useMemo(() => (hasPermission && autoTranslateEnabled ? {
groups: ['channel', 'group', 'direct'],
id: 'autotranslate',
title: 'Auto_Translate',
icon: 'language',
template: lazy(() => import('../../../../client/views/room/contextualBar/AutoTranslate')),
order: 20,
full: true,
} : null), [autoTranslateEnabled, hasPermission]);
});

@ -1,16 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { TabBar } from '../../../ui-utils';
Meteor.startup(() => {
TabBar.addButton({
groups: ['channel', 'group'],
id: 'channel-settings',
anonymous: true,
i18nTitle: 'Room_Info',
icon: 'info-circled',
template: 'RoomInfo',
order: 7,
full: true,
});
});

@ -0,0 +1,14 @@
import { FC, lazy, LazyExoticComponent } from 'react';
import { addAction } from '../../../../client/views/room/lib/Toolbox';
addAction('channel-settings', {
groups: ['channel', 'group'],
id: 'channel-settings',
anonymous: true,
full: true,
title: 'Room_Info',
icon: 'info-circled',
template: lazy(() => import('../../../../client/views/room/contextualBar/Info')) as LazyExoticComponent<FC>,
order: 7,
});

@ -1,6 +1,5 @@
// Templates
import './views/creationDialog/CreateDiscussion';
import './views/DiscussionTabbar';
// Other UI extensions
import './lib/messageTypes/discussionMessage';

@ -1,17 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { TabBar } from '../../ui-utils/client';
import { settings } from '../../settings';
Meteor.startup(function() {
return TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'discussions',
i18nTitle: 'Discussions',
icon: 'discussion',
template: 'discussionsTabbar',
full: true,
order: 1,
condition: () => settings.get('Discussion_enabled'),
});
});

@ -0,0 +1,18 @@
import { useMemo, lazy, LazyExoticComponent, FC } from 'react';
import { addAction } from '../../../client/views/room/lib/Toolbox';
import { useSetting } from '../../../client/contexts/SettingsContext';
addAction('discussions', () => {
const discussionEnabled = useSetting('Discussion_enabled');
return useMemo(() => (discussionEnabled ? {
groups: ['channel', 'group', 'direct'],
id: 'discussions',
title: 'Discussions',
icon: 'discussion',
template: lazy(() => import('../../../client/views/room/contextualBar/Discussions')) as LazyExoticComponent<FC>,
full: true,
order: 1,
} : null), [discussionEnabled]);
});

@ -1,11 +0,0 @@
import { Template } from 'meteor/templating';
import './DiscussionTabbar.html';
Template.discussionsTabbar.helpers({
close() {
const { data } = Template.instance();
const { tabBar } = data;
return () => tabBar.close();
},
});

@ -1,37 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { Tracker } from 'meteor/tracker';
import { hasAllPermission } from '../../authorization';
import { call, TabBar } from '../../ui-utils';
import { ChatRoom } from '../../models';
import { settings } from '../../settings';
Meteor.startup(() => {
Tracker.autorun(() => {
if (settings.get('E2E_Enable')) {
TabBar.addButton({
groups: ['direct', 'group'],
id: 'e2e',
i18nTitle: 'E2E',
icon: 'key',
class: () => (ChatRoom.findOne(Session.get('openedRoom')) || {}).encrypted && 'enabled',
action: () => {
const room = ChatRoom.findOne(Session.get('openedRoom'));
call('saveRoomSettings', room._id, 'encrypted', !room.encrypted);
},
order: 13,
condition: () => {
const session = Session.get('openedRoom');
const room = ChatRoom.findOne(session);
if (room && room.t === 'd') {
return true;
}
return hasAllPermission('edit-room', session);
},
});
} else {
TabBar.removeButton('e2e');
}
});
});

@ -0,0 +1,28 @@
import { useMemo } from 'react';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { addAction } from '../../../client/views/room/lib/Toolbox';
import { useSetting } from '../../../client/contexts/SettingsContext';
import { usePermission } from '../../../client/contexts/AuthorizationContext';
import { useMethod } from '../../../client/contexts/ServerContext';
addAction('e2e', ({ room }) => {
const e2eEnabled = useSetting('E2E_Enable');
const hasPermission = usePermission('edit-room', room._id);
const toggleE2E = useMethod('saveRoomSettings');
const action = useMutableCallback(() => {
toggleE2E(room._id, 'encrypted', !room.encrypted);
});
const enabledOnRoom = !!room.encrypted;
return useMemo(() => (e2eEnabled && hasPermission ? {
groups: ['direct', 'group'],
id: 'e2e',
title: enabledOnRoom ? 'E2E_disable' : 'E2E_enable',
icon: 'key',
order: 13,
action,
} : null), [action, e2eEnabled, enabledOnRoom, hasPermission]);
});

@ -1,92 +0,0 @@
import { Session } from 'meteor/session';
import { TabBar } from '../../ui-utils';
import { Rooms } from '../../models';
import { hasAllPermission } from '../../authorization';
import { roomTypes } from '../../utils/client';
TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'rocket-search',
i18nTitle: 'Search_Messages',
icon: 'magnifier',
template: 'RocketSearch',
order: 4,
});
TabBar.addButton({
groups: ['direct'],
id: 'user-info',
i18nTitle: 'User_Info',
icon: 'user',
full: true,
template: 'UserInfoWithData',
order: 5,
condition() {
const rid = Session.get('openedRoom');
const room = Rooms.findOne({
_id: rid,
});
return room && !roomTypes.getConfig(room.t).isGroupChat(room);
},
});
TabBar.addButton({
groups: ['direct'],
id: 'user-info-group',
i18nTitle: 'Members',
icon: 'team',
template: 'membersList',
order: 5,
condition() {
const rid = Session.get('openedRoom');
const room = Rooms.findOne({
_id: rid,
});
return room && roomTypes.getConfig(room.t).isGroupChat(room);
},
});
TabBar.addButton({
groups: ['channel', 'group'],
id: 'members-list',
i18nTitle: 'Members',
icon: 'team',
template: 'membersList',
order: 5,
full: 1,
condition() {
const rid = Session.get('openedRoom');
const room = Rooms.findOne({
_id: rid,
});
if (!room || !room.broadcast) {
return true;
}
return hasAllPermission('view-broadcast-member-list', rid);
},
});
TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'uploaded-files-list',
i18nTitle: 'Files',
icon: 'clip',
template: 'channelFilesList',
order: 6,
full: true,
});
TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'keyboard-shortcut-list',
i18nTitle: 'Keyboard_Shortcuts_Title',
icon: 'keyboard',
template: 'KeyboardShortcuts',
full: true,
order: 99,
});

@ -1,7 +1,6 @@
import '../lib/startup/settingsOnLoadSiteUrl';
import '../lib/MessageTypes';
import './CustomTranslations';
import './defaultTabBars';
import './OAuthProxy';
import './UserDeleted';
import './lib/startup/commands';

@ -1,23 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { TabBar } from '../../../ui-utils/client';
import { settings } from '../../../settings/client';
Meteor.startup(function() {
Tracker.autorun(function() {
if (!settings.get('Omnichannel_External_Frame_Enabled')) {
return TabBar.removeButton('omnichannelExternalFrame');
}
TabBar.addButton({
groups: ['live'],
id: 'omnichannelExternalFrame',
i18nTitle: 'Omnichannel_External_Frame',
icon: 'cube',
template: 'ExternalFrameContainer',
order: -1,
});
});
});

@ -0,0 +1,18 @@
import { useMemo } from 'react';
import { useSetting } from '../../../../client/contexts/SettingsContext';
import { addAction } from '../../../../client/views/room/lib/Toolbox';
addAction('omnichannel-external-frame', () => {
const enabled = useSetting('Omnichannel_External_Frame_Enabled');
return useMemo(() => (enabled
? {
groups: ['live'],
id: 'omnichannel-external-frame',
title: 'Omnichannel_External_Frame',
icon: 'cube',
template: 'ExternalFrameContainer',
order: -1,
} : null), [enabled]);
});

@ -1,29 +0,0 @@
import { callbacks } from '../../../callbacks';
callbacks.add('onCreateRoomTabBar', (info) => {
const { tabBar, room } = info;
if (!tabBar) {
return info;
}
if (!room || !room.t || room.t !== 'l') {
return info;
}
const button = tabBar.getButtons().find((button) => button.id === 'visitor-info');
if (!button) {
return info;
}
const { template, i18nTitle: label, icon } = button;
tabBar.setTemplate(template);
tabBar.setData({
label,
icon,
});
tabBar.open();
return info;
});

@ -2,7 +2,7 @@ import '../lib/messageTypes';
import './roomType';
import './route';
import './ui';
import './hooks/onCreateRoomTabBar';
import './tabBar';
import './startup/notifyUnreadRooms';
import './views/app/dialog/closeRoom';
import './stylesheets/livechat.css';

@ -0,0 +1,19 @@
import { addAction } from '../../../client/views/room/lib/Toolbox';
addAction('visitor-info', {
groups: ['live'],
id: 'visitor-info',
title: 'Visitor_Info',
icon: 'info-circled',
template: 'visitorInfo',
order: 0,
});
addAction('contact-chat-history', {
groups: ['live'],
id: 'contact-chat-history',
title: 'Contact_Chat_History',
icon: 'clock',
template: 'contactChatHistory',
order: 11,
});

@ -2,7 +2,7 @@ import { Tracker } from 'meteor/tracker';
import { settings } from '../../settings';
import { hasAllPermission } from '../../authorization';
import { AccountBox, TabBar, MessageTypes } from '../../ui-utils';
import { AccountBox, MessageTypes } from '../../ui-utils';
Tracker.autorun((c) => {
// import omnichannel tabbar templates right away if omnichannel enabled
@ -21,30 +21,6 @@ AccountBox.addItem({
condition: () => settings.get('Livechat_enabled') && hasAllPermission('view-livechat-manager'),
});
TabBar.addButton({
groups: ['live'],
id: 'visitor-info',
i18nTitle: 'Visitor_Info',
icon: 'info-circled',
template: 'visitorInfo',
order: 0,
});
TabBar.addButton({
groups: ['live'],
id: 'contact-chat-history',
i18nTitle: 'Contact_Chat_History',
icon: 'clock',
template: 'contactChatHistory',
order: 11,
});
TabBar.addGroup('message-search', ['live']);
TabBar.addGroup('starred-messages', ['live']);
TabBar.addGroup('uploaded-files-list', ['live']);
TabBar.addGroup('push-notifications', ['live']);
TabBar.addGroup('video', ['live']);
MessageTypes.registerType({
id: 'livechat-close',
system: true,

@ -60,11 +60,6 @@ Template.contactChatHistory.onCreated(async function() {
this.returnChatHistoryList = () => {
this.showChatHistoryMessages.set(false);
this.chatHistoryMessagesContext.set();
this.tabBar.setData({
label: 'Contact_Chat_History',
icon: 'clock',
});
};
this.autorun(async () => {

@ -17,7 +17,7 @@ class LivechatRoomRoute extends RoomTypeRouteConfig {
constructor() {
super({
name: 'live',
path: '/live/:id',
path: '/live/:id/:tab?/:context?',
});
}
@ -122,19 +122,19 @@ export default class LivechatRoomType extends RoomTypeConfig {
if (!room || !room.v || room.v.username !== username) {
return false;
}
const button = instance.tabBar.getButtons().find((button) => button.id === 'visitor-info');
if (!button) {
return false;
}
const { template, i18nTitle: label, icon } = button;
instance.tabBar.setTemplate(template);
instance.tabBar.setData({
label,
icon,
});
instance.tabBar.open();
// const button = instance.tabBar.getButtons({ room }).find((button) => button.id === 'visitor-info');
// if (!button) {
// return false;
// }
// const { template, i18nTitle: label, icon } = button;
// instance.tabBar.setTemplate(template);
// instance.tabBar.setData({
// label,
// icon,
// });
instance.tabBar.openUserInfo();
return true;
}
}

@ -1,30 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { Session } from 'meteor/session';
import { TabBar } from '../../ui-utils';
import { Rooms } from '../../models';
import { settings } from '../../settings';
Meteor.startup(function() {
Tracker.autorun(function() {
TabBar.removeButton('livestream');
if (settings.get('Livestream_enabled')) {
const live = Rooms.findOne({
_id: Session.get('openedRoom'),
'streamingOptions.type': 'livestream',
'streamingOptions.id': { $exists: 1 },
}, { fields: { streamingOptions: 1 } });
return TabBar.addButton({
groups: ['channel', 'group'],
id: 'livestream',
i18nTitle: 'Livestream',
icon: 'podcast',
template: 'liveStreamTab',
order: live ? -1 : 15,
class: () => live && 'live',
});
}
});
});

@ -0,0 +1,29 @@
import React, { useMemo } from 'react';
import { Option, Badge } from '@rocket.chat/fuselage';
import { useSetting } from '../../../client/contexts/SettingsContext';
import { useTranslation } from '../../../client/contexts/TranslationContext';
import { addAction } from '../../../client/views/room/lib/Toolbox';
import Header from '../../../client/components/Header';
addAction('livestream', ({ room }) => {
const enabled = useSetting('Livestream_enabled');
const t = useTranslation();
const isLive = room && room.streamingOptions && room.streamingOptions.id && room.streamingOptions.type === 'livestream';
return useMemo(() => (enabled ? {
groups: ['channel', 'group'],
id: 'livestream',
title: 'Livestream',
icon: 'podcast',
template: 'liveStreamTab',
order: isLive ? -1 : 15,
renderAction: (props): React.ReactNode => <Header.ToolBoxAction {...props}>
{isLive ? <Header.Badge title={t('Livestream_live_now')} variant='danger'>!</Header.Badge> : null}
</Header.ToolBoxAction>,
renderOption: ({ label: { title, icon }, ...props }: any): React.ReactNode => <Option label={title} title={title} icon={icon} {...props}>
{isLive ? <Badge title={t('Livestream_live_now')} variant='danger'>!</Badge> : null }
</Option>,
} : null), [enabled, isLive, t]);
});

@ -12,7 +12,9 @@ Meteor.startup(function() {
icon: 'jump',
label: 'Jump_to_message',
context: ['mentions', 'threads'],
action() {
action(e) {
e.preventDefault();
e.stopPropagation();
const { msg: message } = messageArgs(this);
if (window.matchMedia('(max-width: 500px)').matches) {
Template.instance().tabBar.close();

@ -1,14 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { TabBar } from '../../ui-utils';
Meteor.startup(function() {
return TabBar.addButton({
groups: ['channel', 'group'],
id: 'mentions',
i18nTitle: 'Mentions',
icon: 'at',
template: 'mentionsFlexTab',
order: 9,
});
});

@ -0,0 +1,10 @@
import { addAction } from '../../../client/views/room/lib/Toolbox';
addAction('mentions', {
groups: ['channel', 'group'],
id: 'mentions',
title: 'Mentions',
icon: 'at',
template: 'mentionsFlexTab',
order: 9,
});

@ -8,6 +8,7 @@ import { messageContext } from '../../../ui-utils/client/lib/messageContext';
import { upsertMessageBulk } from '../../../ui-utils/client/lib/RoomHistoryManager';
import { APIClient } from '../../../utils/client';
import { Messages, Users } from '../../../models/client';
import { getCommonRoomEvents } from '../../../ui/client/views/app/lib/getCommonRoomEvents';
const LIMIT_DEFAULT = 50;
@ -73,6 +74,7 @@ Template.mentionsFlexTab.onDestroyed(function() {
});
Template.mentionsFlexTab.events({
...getCommonRoomEvents(),
'scroll .js-list': _.throttle(function(e, instance) {
if (e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight && instance.hasMore.get()) {
return instance.limit.set(instance.limit.get() + 50);

@ -1,22 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { settings } from '../../settings';
import { TabBar } from '../../ui-utils';
Meteor.startup(function() {
return Tracker.autorun(function() {
if (settings.get('Message_AllowPinning')) {
TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'pinned-messages',
i18nTitle: 'Pinned_Messages',
icon: 'pin',
template: 'pinnedMessages',
order: 11,
});
} else {
TabBar.removeButton('pinned-messages');
}
});
});

@ -0,0 +1,16 @@
import { useMemo } from 'react';
import { addAction } from '../../../client/views/room/lib/Toolbox';
import { useSetting } from '../../../client/contexts/SettingsContext';
addAction('pinned-messages', () => {
const pinningAllowed = useSetting('Message_AllowPinning');
return useMemo(() => (pinningAllowed ? {
groups: ['channel', 'group', 'direct'],
id: 'pinned-messages',
title: 'Pinned_Messages',
icon: 'pin',
template: 'pinnedMessages',
order: 11,
} : null), [pinningAllowed]);
});

@ -7,9 +7,12 @@ import { upsertMessageBulk } from '../../../ui-utils/client/lib/RoomHistoryManag
import { messageContext } from '../../../ui-utils/client/lib/messageContext';
import { APIClient } from '../../../utils/client';
import { Messages } from '../../../models/client';
import { getCommonRoomEvents } from '../../../ui/client/views/app/lib/getCommonRoomEvents';
const LIMIT_DEFAULT = 50;
Template.pinnedMessages.events(getCommonRoomEvents());
Template.pinnedMessages.helpers({
hasMessages() {
return Template.instance().messages.find().count();
@ -74,6 +77,7 @@ Template.mentionsFlexTab.onDestroyed(function() {
});
Template.pinnedMessages.events({
...getCommonRoomEvents(),
'scroll .js-list': _.throttle(function(e, instance) {
if (e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight && instance.hasMore.get()) {
return instance.limit.set(instance.limit.get() + 50);

@ -1,19 +1,9 @@
import { FlowRouter } from 'meteor/kadira:flow-router';
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
import { TabBar } from '../../ui-utils';
FlowRouter.route('/snippet/:snippetId/:snippetName', {
name: 'snippetView',
action() {
BlazeLayout.render('main', { center: 'snippetPage', flexTabBar: null });
BlazeLayout.render('main', { center: 'snippetPage' });
},
triggersEnter: [function() {
TabBar.hide();
}],
triggersExit: [
function() {
TabBar.show();
},
],
});

@ -1,22 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { settings } from '../../../settings';
import { TabBar } from '../../../ui-utils';
Meteor.startup(function() {
Tracker.autorun(function() {
if (settings.get('Message_AllowSnippeting')) {
TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'snippeted-messages',
i18nTitle: 'snippet-message',
icon: 'code',
template: 'snippetedMessages',
order: 20,
});
} else {
TabBar.removeButton('snippeted-messages');
}
});
});

@ -0,0 +1,16 @@
import { useMemo } from 'react';
import { addAction } from '../../../../client/views/room/lib/Toolbox';
import { useSetting } from '../../../../client/contexts/SettingsContext';
addAction('snippeted-messages', () => {
const snippetingEnabled = useSetting('Message_AllowSnippeting');
return useMemo(() => (snippetingEnabled ? {
groups: ['channel', 'group', 'direct'],
id: 'snippeted-messages',
title: 'snippet-message',
icon: 'code',
template: 'snippetedMessages',
order: 20,
} : null), [snippetingEnabled]);
});

@ -1,14 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { TabBar } from '../../ui-utils';
Meteor.startup(function() {
TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'starred-messages',
i18nTitle: 'Starred_Messages',
icon: 'star',
template: 'starredMessages',
order: 10,
});
});

@ -0,0 +1,10 @@
import { addAction } from '../../../client/views/room/lib/Toolbox';
addAction('starred-messages', {
groups: ['channel', 'group', 'direct', 'live'],
id: 'starred-messages',
title: 'Starred_Messages',
icon: 'star',
template: 'starredMessages',
order: 10,
});

@ -8,9 +8,11 @@ import { messageContext } from '../../../ui-utils/client/lib/messageContext';
import { Messages } from '../../../models/client';
import { upsertMessageBulk } from '../../../ui-utils/client/lib/RoomHistoryManager';
import { APIClient } from '../../../utils/client';
import { getCommonRoomEvents } from '../../../ui/client/views/app/lib/getCommonRoomEvents';
const LIMIT_DEFAULT = 50;
Template.starredMessages.events(getCommonRoomEvents());
Template.starredMessages.helpers({
hasMessages() {
return Template.instance().messages.find().count();
@ -73,6 +75,7 @@ Template.mentionsFlexTab.onDestroyed(function() {
});
Template.starredMessages.events({
...getCommonRoomEvents(),
'scroll .js-list': _.throttle(function(e, instance) {
if (e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight) {
return instance.limit.set(instance.limit.get() + 50);

@ -11,6 +11,7 @@ class OTRClass {
constructor() {
this.enabled = new ReactiveVar(false);
this.instancesByRoomId = {};
this.crypto = null;
}
isEnabled() {

@ -1,27 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { OTR } from './rocketchat.otr';
import { settings } from '../../settings';
import { TabBar } from '../../ui-utils';
Meteor.startup(function() {
Tracker.autorun(function() {
if (settings.get('OTR_Enable') && window.crypto) {
OTR.crypto = window.crypto.subtle || window.crypto.webkitSubtle;
OTR.enabled.set(true);
TabBar.addButton({
groups: ['direct'],
id: 'otr',
i18nTitle: 'OTR',
icon: 'key',
template: 'OTR',
order: 13,
full: true,
});
} else {
OTR.enabled.set(false);
TabBar.removeButton('otr');
}
});
});

@ -0,0 +1,31 @@
import { useMemo, lazy, LazyExoticComponent, FC, useEffect } from 'react';
import { OTR } from './rocketchat.otr';
import { useSetting } from '../../../client/contexts/SettingsContext';
import { addAction } from '../../../client/views/room/lib/Toolbox';
addAction('otr', () => {
const enabled = useSetting('OTR_Enable');
const shouldAddAction = enabled && window.crypto;
useEffect(() => {
if (shouldAddAction) {
OTR.crypto = window.crypto.subtle;
OTR.enabled.set(true);
} else {
OTR.enabled.set(false);
}
}, [shouldAddAction]);
return useMemo(() => (shouldAddAction
? {
groups: ['direct'],
id: 'otr',
title: 'OTR',
icon: 'key',
template: lazy(() => import('../../../client/views/room/contextualBar/OTR')) as LazyExoticComponent<FC>,
order: 13,
full: true,
} : null), [shouldAddAction]);
});

@ -1,15 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { TabBar } from '../../ui-utils';
Meteor.startup(function() {
TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'push-notifications',
i18nTitle: 'Notifications_Preferences',
icon: 'bell',
template: 'NotificationsPreferences',
full: true,
order: 8,
});
});

@ -0,0 +1,12 @@
import { lazy } from 'react';
import { addAction } from '../../../client/views/room/lib/Toolbox';
addAction('push-notifications', {
groups: ['channel', 'group', 'direct', 'live'],
id: 'push-notifications',
title: 'Notifications_Preferences',
icon: 'bell',
template: lazy(() => import('../../../client/views/room/contextualBar/NotificationPreferences')),
order: 8,
});

@ -3,14 +3,14 @@ import { Blaze } from 'meteor/blaze';
import { Template } from 'meteor/templating';
import { roomTypes } from '../../utils/client';
import { Rooms } from '../../models';
import { Rooms, Subscriptions } from '../../models';
import { MessageAction } from '../../ui-utils';
import { messageArgs } from '../../ui-utils/client/lib/messageArgs';
import { EmojiPicker } from '../../emoji';
import { tooltip } from '../../ui/client/components/tooltip';
Template.roomOld.events({
'click .add-reaction'(event, instance) {
export const EmojiEvents = {
'click .add-reaction'(event) {
event.preventDefault();
event.stopPropagation();
const data = Blaze.getData(event.currentTarget);
@ -22,7 +22,7 @@ Template.roomOld.events({
return false;
}
if (!instance.subscription.get()) {
if (!Subscriptions.findOne({ rid })) {
return false;
}
@ -58,7 +58,9 @@ Template.roomOld.events({
event.stopPropagation();
tooltip.hide();
},
});
};
Template.roomOld.events(EmojiEvents);
Meteor.startup(function() {
MessageAction.addButton({

@ -10,6 +10,7 @@ import { messageContext } from '../../../ui-utils/client/lib/messageContext';
import { MessageAction, RoomHistoryManager } from '../../../ui-utils';
import { messageArgs } from '../../../ui-utils/client/lib/messageArgs';
import { Rooms } from '../../../models/client';
import { getCommonRoomEvents } from '../../../ui/client/views/app/lib/getCommonRoomEvents';
Meteor.startup(function() {
MessageAction.addButton({
@ -84,6 +85,7 @@ Template.DefaultSearchResultTemplate.onCreated(function() {
});
Template.DefaultSearchResultTemplate.events({
...getCommonRoomEvents(),
'change #global-search'(e, t) {
t.data.parentPayload.searchAll = e.target.checked;
t.data.payload.limit = t.pageSize;

@ -6,10 +6,6 @@
padding: 0 !important;
}
.defaultProvider {
padding: 24px;
}
.rocket-search-tab {
display: flex;
flex-direction: column;

@ -8,7 +8,7 @@
overflow: hidden;
flex-direction: column;
flex: 0 0 var(--flex-tab-width);
flex: 1 0 var(--flex-tab-width);
width: var(--flex-tab-width);
height: 100%;

@ -1,60 +1,6 @@
.rc-header {
font-size: var(--text-heading-size);
.rc-badge {
position: absolute;
z-index: 1;
top: -2px;
left: var(--badge-size);
display: flex;
min-width: var(--badge-size);
height: var(--badge-size);
padding: 0 5px;
text-align: center;
color: white;
border-radius: calc(4 * var(--badge-font-size));
background-color: var(--rc-color-button-primary);
box-shadow: 0 0 0 2px #ffffff;
font-size: var(--badge-font-size);
font-weight: 600;
align-items: center;
justify-content: center;
&--user-mentions {
background-color: var(--badge-user-mentions-background);
}
&--group-mentions {
background-color: var(--badge-group-mentions-background);
}
}
&__first-icon {
display: flex;
width: 48px;
padding: 0 0.25rem;
cursor: pointer;
justify-content: center;
}
&--room {
padding: 1.25rem;
border-bottom: 2px solid var(--color-gray-lightest);
font-size: var(--header-title-font-size);
}
&__wrap {
z-index: 2;
@ -212,203 +158,7 @@
text-overflow: ellipsis;
}
&__status {
display: flex;
align-items: center;
&-bullet {
width: var(--header-title-status-bullet-size);
height: var(--header-title-status-bullet-size);
margin-right: 0.25rem;
border-radius: var(--header-title-status-bullet-radius);
&--online {
background-color: var(--rc-status-online);
}
&--away {
background-color: var(--rc-status-away);
}
&--busy {
background-color: var(--rc-status-busy);
}
&--invisible {
background-color: var(--rc-status-invisible);
}
&--offline {
background-color: var(--rc-status-invisible);
}
}
}
&__toggle-favorite {
padding: 0 0.25rem;
color: var(--header-toggle-favorite-color);
& > .rc-header__icon {
font-size: 2rem;
}
&:hover {
color: var(--header-toggle-favorite-star-color);
}
&--checked {
color: var(--header-toggle-favorite-star-color);
}
}
&__toggle-encryption {
color: var(--header-toggle-encryption-on-color);
&.empty {
color: var(--header-toggle-encryption-off-color);
& .rc-header__icon {
fill: none;
}
}
&:hover {
color: var(--header-toggle-encryption-on-color);
}
}
&__icon {
font-size: 1rem;
}
&__image {
width: 32px;
height: 32px;
flex-shrink: 0;
}
.rc-button {
height: 36px;
min-height: 36px;
margin: 0 0.25rem;
}
}
.rc-room-actions {
display: flex;
&__action,
&__more-action {
position: relative;
display: flex;
flex-direction: column;
margin: 0 6px;
cursor: pointer;
transition: all 0.3s;
font-size: 20px;
align-items: center;
&.active,
&:hover {
color: var(--rc-color-link-active);
}
&.enabled {
color: var(--header-toggle-encryption-on-color);
}
&.live {
position: relative;
}
&.live::before {
position: absolute;
z-index: 1;
right: -2px;
bottom: -1px;
display: block;
width: 10px;
width: var(--sidebar-account-status-bullet-size);
height: 10px;
height: var(--sidebar-account-status-bullet-size);
content: '';
animation: blink 1.5s ease-in-out infinite;
border-radius: 50%;
border-radius: var(--sidebar-account-status-bullet-radius);
background-color: #f5455c;
}
}
&__more {
&-action {
flex: 0 0 80px;
max-width: 80px;
margin: 8px;
}
&-container {
display: flex;
max-width: 480px;
margin: 0 -8px;
flex-wrap: wrap;
justify-content: space-between;
}
}
&__button {
color: inherit;
font-size: inherit;
}
&__description {
display: inline-block;
overflow: hidden;
width: 100%;
padding: 8px 0;
text-align: center;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 12px;
font-weight: 600;
}
& + & {
border-left: 1px var(--color-gray) solid;
.rtl & {
border-right: 1px var(--color-gray) solid;
border-left: 0;
}
}
}
.tab-button-icon--star {
fill: none;
}
.tab-bugtton-icon--team {
font-size: 28px;
}
@ -432,25 +182,11 @@
}
}
&__favorite {
order: 1;
}
&__data {
display: flex;
flex-direction: row;
align-items: center;
}
&__status {
margin: 0 0.5rem;
}
&__image {
width: 20px;
height: 20px;
}
&--burger {
display: flex;
@ -460,10 +196,6 @@
}
}
.embedded-view .room-container .rc-header--burger {
display: none;
}
.burger {
position: relative;

@ -105,39 +105,6 @@ button {
}
}
.flex-tab-bar {
& .tab-button {
position: relative;
cursor: pointer;
}
& .tab-button-icon {
color: var(--rc-color-primary-dark);
font-size: 20px;
&--star {
width: 17px;
height: 16px;
fill: none;
}
&--language {
width: 16px;
height: 16px;
fill: currentColor;
stroke: none;
}
&--hubot {
width: 14px;
height: 16px;
}
}
}
.rc-icon {
overflow: hidden;

@ -2427,272 +2427,6 @@
flex-grow: 1;
}
.rc-old .flex-tab-container {
z-index: 2;
display: flex;
flex: 0 0 auto;
transform: box-shadow 0.3s;
box-shadow: -1px 0 0 1px #cccccc26;
&.opened {
box-shadow: -1px 0 5px 2px #cccccc26;
& > .flex-tab-bar {
box-shadow: -1px 0 5px 2px #cccccc26;
}
}
& .flex-tab {
position: relative;
display: none;
overflow-x: visible;
width: var(--flex-tab-width);
& .control {
& .header {
margin: 5px 0 15px;
padding: 5px 30px 20px;
text-align: center;
& h2 {
font-size: 20px;
font-weight: 300;
line-height: 25px;
}
}
& .button {
min-height: 36px;
margin: 0 1px;
}
& .more {
position: absolute;
top: 0;
left: 0;
width: 30px;
height: 60px;
cursor: pointer;
transition: transform 0.25s ease-out 0.475s, background 0.075s ease-out 0.5s;
transform: translateX(-27px);
border-width: 0 0 1px;
& i {
height: 12.5px;
margin-top: 1px;
transition: transform 0.3s ease-out;
transform-origin: 50%, 50%, 0;
vertical-align: top;
}
}
& .search-form {
width: 100%;
& .icon-plus {
position: absolute;
top: 11px;
left: 8px;
font-size: 13px;
}
}
& .info-tabs {
position: absolute;
top: 0;
right: 20px;
height: 60px;
text-align: right;
& a {
display: inline-block;
float: left;
height: 60px;
padding: 0 15px;
vertical-align: middle;
border-width: 0 0 0 1px;
line-height: 60px;
&:last-child {
border-width: 0 1px 0 0;
}
}
}
}
& .content {
overflow-x: hidden;
overflow-y: auto;
width: 100%;
height: 100%;
-webkit-overflow-scrolling: touch;
& > div {
overflow-y: auto;
transition: transform 0.45s cubic-bezier(0.5, 0, 0, 1), opacity 0.125s ease-out 0.1s;
}
& > .animated-hidden {
transform: translateX(calc(100% + 40px));
opacity: 0;
}
& .section {
margin: 20px;
padding: 20px;
border: 1px solid #dddddd;
border-radius: var(--border-radius);
background-color: #ffffff;
}
& > .animated {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
& > .title {
height: var(--header-min-height);
& h2 {
padding: 0 20px;
font-size: 20px;
font-weight: 300;
line-height: var(--header-min-height);
}
}
}
& .channel-settings .button {
visibility: initial;
}
& footer {
position: absolute;
z-index: 100;
bottom: 0;
left: 0;
width: 100%;
height: var(--footer-min-height);
padding: 9px 15px 0;
text-align: right;
}
& .social {
text-align: center;
& h4 {
position: absolute;
top: -12px;
left: 0;
width: 100%;
font-size: 13px;
font-weight: 300;
}
& .share {
min-height: 40px;
border-radius: 50%;
line-height: 20px;
&::before {
border-radius: 50%;
}
& span {
display: none;
}
}
}
}
& .flex-tab-bar {
z-index: 1;
min-width: 40px;
transition: box-shadow 0.3s;
box-shadow: -1px 0 0 1px #cccccc26;
& .tab-button {
position: relative;
cursor: pointer;
text-align: center;
& button {
height: 38px;
}
& .counter {
position: absolute;
top: 4px;
right: 4px;
width: 13px;
height: 13px;
text-align: center;
border-radius: 50%;
font-size: 9px;
font-weight: bold;
line-height: 13px;
}
&.active {
border-width: 0 3px 0 0;
& button {
margin-left: 3px;
}
& .counter {
margin-right: -3px;
}
}
}
}
&.opened .flex-tab {
display: block;
}
}
.rc-old .list-view {
& .list {
position: relative;
@ -3919,10 +3653,6 @@
& .messages-container {
border-width: 0;
& .flex-tab-container {
display: none;
}
& .messages-box {
margin-top: 0;
}

@ -100,18 +100,6 @@
}
}
& .flex-tab-container {
border-width: 0 1px 0 0;
}
& .flex-tab-bar .tab-button.active {
margin-right: -1px;
margin-left: auto;
border-right: unset;
border-left: 3px solid #ff0000;
}
& .flex-tab .control {
padding: 12px 30px;

@ -312,10 +312,6 @@ textarea {
}
}
.toggle-favorite {
color: var(--component-color);
}
.upload-progress-progress {
background-color: var(--success-background);
}
@ -377,31 +373,6 @@ textarea {
}
}
.flex-tab-bar {
.tab-button {
&:hover {
background-color: var(--secondary-background-color);
}
&.active {
border-right-color: var(--selection-color);
background-color: var(--secondary-background-color);
}
&.attention {
animation-name: blink;
animation-duration: 1000ms;
animation-iteration-count: infinite;
animation-direction: alternate;
}
}
.counter {
color: white;
background: var(--secondary-font-color);
}
}
i.status-online {
color: var(--rc-status-online);
}

@ -346,10 +346,6 @@
*/
--header-height: 77px;
--header-padding: 16px;
--header-toggle-favorite-color: var(--color-gray-medium);
--header-toggle-favorite-star-color: var(--rc-color-alert-light);
--header-toggle-encryption-off-color: var(--color-gray-medium);
--header-toggle-encryption-on-color: var(--rc-color-alert-message-secondary);
--header-title-username-color-darker: var(--color-dark);
--header-title-font-size: var(--text-default-size);
--header-title-font-size--subtitle: var(--text-small-size);

@ -8,13 +8,16 @@ import { ChatMessage } from '../../../models/client';
import { useRoute } from '../../../../client/contexts/RouterContext';
import { roomTypes } from '../../../utils/client';
import { normalizeThreadTitle } from '../lib/normalizeThreadTitle';
import { useUserId } from '../../../../client/contexts/UserContext';
import { useUserId, useUserSubscription } from '../../../../client/contexts/UserContext';
import { useEndpoint, useMethod } from '../../../../client/contexts/ServerContext';
import { useToastMessageDispatch } from '../../../../client/contexts/ToastMessagesContext';
import ThreadSkeleton from './ThreadSkeleton';
import ThreadView from './ThreadView';
import { IMessage } from '../../../../definition/IMessage';
import { IRoom } from '../../../../definition/IRoom';
import { useTabBarOpenUserInfo } from '../../../../client/views/room/providers/ToolboxProvider';
const subscriptionFields = {};
const useThreadMessage = (tmid: string): IMessage => {
const [message, setMessage] = useState<IMessage>(() => Tracker.nonreactive(() => ChatMessage.findOne({ _id: tmid })));
@ -56,16 +59,17 @@ const ThreadComponent: FC<{
mid: string;
jump: unknown;
room: IRoom;
subscription: unknown;
}> = ({
mid,
jump,
room,
subscription,
}) => {
const subscription = useUserSubscription(room._id, subscriptionFields);
const channelRoute = useRoute(roomTypes.getConfig(room.t).route.name);
const threadMessage = useThreadMessage(mid);
const open = useTabBarOpenUserInfo();
const ref = useRef<Element>(null);
const uid = useUserId();
@ -102,6 +106,8 @@ const ThreadComponent: FC<{
jump,
following,
subscription,
rid: room._id,
openProfileTab: open,
}));
useEffect(() => {
@ -115,15 +121,16 @@ const ThreadComponent: FC<{
jump,
following,
subscription,
rid: room._id,
openProfileTab: open,
};
});
}, [following, jump, subscription, threadMessage]);
}, [following, jump, open, room._id, subscription, threadMessage]);
useEffect(() => {
if (!ref.current || !viewData.mainMessage) {
return;
}
const view = Blaze.renderWithData(Template.thread, viewData, ref.current);
return (): void => {

@ -21,13 +21,13 @@ const ThreadSkeleton: FC<ThreadSkeletonProps> = ({ expanded, onClose }) => {
return <>
{expanded && <Modal.Backdrop onClick={onClose} />}
<Box width='380px' flexGrow={1} position={expanded ? 'static' : 'relative'}>
<Box flexGrow={1} position={expanded ? 'static' : 'relative'}>
<VerticalBar.Skeleton
className='rcx-thread-view'
position='absolute'
display='flex'
flexDirection='column'
width={expanded ? 'full' : 380}
width={'full'}
maxWidth={855}
overflow='hidden'
zIndex={100}

@ -2,6 +2,7 @@ import React, { useCallback, useMemo, forwardRef } from 'react';
import { Modal, Box } from '@rocket.chat/fuselage';
import { useTranslation } from '../../../../client/contexts/TranslationContext';
import { useLayoutContextualBarExpanded } from '../../../../client/providers/LayoutProvider';
import VerticalBar from '../../../../client/components/VerticalBar';
type ThreadViewProps = {
@ -21,6 +22,8 @@ const ThreadView = forwardRef<Element, ThreadViewProps>(({
onToggleFollow,
onClose,
}, ref) => {
const hasExpand = useLayoutContextualBarExpanded();
const style = useMemo(() => (document.dir === 'rtl'
? {
left: 0,
@ -48,29 +51,29 @@ const ThreadView = forwardRef<Element, ThreadViewProps>(({
}, [following, onToggleFollow]);
return <>
{expanded && <Modal.Backdrop onClick={onClose}/>}
{hasExpand && expanded && <Modal.Backdrop onClick={onClose}/>}
<Box width='380px' flexGrow={1} position={expanded ? 'static' : 'relative'}>
<Box flexGrow={1} position={expanded ? 'static' : 'relative'}>
<VerticalBar
className='rcx-thread-view'
position='absolute'
position={hasExpand && expanded ? 'fixed' : 'absolute'}
display='flex'
flexDirection='column'
width={expanded ? 'full' : 380}
maxWidth={855}
width={'full'}
maxWidth={hasExpand && expanded ? 855 : null}
overflow='hidden'
zIndex={100}
insetBlock={0}
// insetInlineEnd={0}
// borderStartStartRadius={4}
style={style} // workaround due to a RTL bug in Fuselage
>
<VerticalBar.Header>
<VerticalBar.Icon name='thread' />
<VerticalBar.Text dangerouslySetInnerHTML={{ __html: title }} />
<VerticalBar.Action aria-label={expandLabel} name={expandIcon} onClick={handleExpandActionClick} />
<VerticalBar.Action aria-label={followLabel} name={followIcon} onClick={handleFollowActionClick} />
<VerticalBar.Close onClick={onClose} />
{hasExpand && <VerticalBar.Action aria-label={expandLabel} name={expandIcon} onClick={handleExpandActionClick} />}
<VerticalBar.Actions>
<VerticalBar.Action aria-label={followLabel} name={followIcon} onClick={handleFollowActionClick} />
<VerticalBar.Close onClick={onClose} />
</VerticalBar.Actions>
</VerticalBar.Header>
<VerticalBar.Content
ref={ref}

@ -20,6 +20,7 @@ import { getUserPreference } from '../../../utils';
import { settings } from '../../../settings/client';
import { callbacks } from '../../../callbacks/client';
import './messageBoxFollow';
import { getCommonRoomEvents } from '../../../ui/client/views/app/lib/getCommonRoomEvents';
createTemplateForComponent('Checkbox', async () => {
const { CheckBox } = await import('@rocket.chat/fuselage');
@ -38,6 +39,7 @@ createTemplateForComponent('ThreadComponent', () => import('../components/Thread
Template.thread.events({
...dropzoneEvents,
...getCommonRoomEvents(),
'click .js-close'(e) {
e.preventDefault();
e.stopPropagation();

@ -1,37 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { TabBar } from '../../../ui-utils/client';
import { Subscriptions } from '../../../models/client';
Meteor.startup(function() {
return TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'thread',
full: true,
i18nTitle: 'Threads',
icon: 'thread',
template: 'threads',
badge: () => {
const subscription = Subscriptions.findOne({ rid: Session.get('openedRoom') }, { fields: { tunread: 1, tunreadUser: 1, tunreadGroup: 1 } });
if (!subscription?.tunread?.length) {
return;
}
const badgeClass = (() => {
if (subscription.tunreadUser?.length > 0) {
return 'rc-badge--user-mentions';
}
if (subscription.tunreadGroup?.length > 0) {
return 'rc-badge--group-mentions';
}
})();
return {
body: subscription.tunread.length > 99 ? '99+' : subscription.tunread.length,
class: badgeClass,
};
},
order: 2,
});
});

@ -0,0 +1,40 @@
import React, { useMemo, lazy, LazyExoticComponent, FC } from 'react';
import { BadgeProps } from '@rocket.chat/fuselage';
import { addAction } from '../../../../client/views/room/lib/Toolbox';
import { useSetting } from '../../../../client/contexts/SettingsContext';
import Header from '../../../../client/components/Header';
import { ISubscription } from '../../../../definition/ISubscription';
const getVariant = (tunreadUser: number, tunreadGroup: number): BadgeProps['variant'] => {
if (tunreadUser > 0) {
return 'danger';
}
if (tunreadGroup > 0) {
return 'warning';
}
return 'primary';
};
const template = lazy(() => import('../../../../client/views/room/contextualBar/Threads')) as LazyExoticComponent<FC>;
addAction('thread', (options) => {
const room = options.room as unknown as ISubscription;
const threadsEnabled = useSetting('Threads_enabled');
return useMemo(() => (threadsEnabled ? {
groups: ['channel', 'group', 'direct'],
id: 'thread',
full: true,
title: 'Threads',
icon: 'thread',
template,
renderAction: (props) => {
const unread = room.tunread?.length > 99 ? '99+' : room.tunread?.length;
const variant = getVariant(room.tunreadUser?.length, room.tunreadGroup?.length);
return <Header.ToolBoxAction {...props} >
{ unread > 0 && <Header.Badge variant={variant}>{unread}</Header.Badge> }
</Header.ToolBoxAction>;
},
order: 2,
} : null), [threadsEnabled, room.tunread?.length, room.tunreadUser?.length, room.tunreadGroup?.length]);
});

@ -1,3 +0,0 @@
<template name="threads">
{{> ThreadsList threads=threads rid=rid onClose=close }}
</template>

@ -1,17 +1 @@
import { Template } from 'meteor/templating';
import './threads.html';
import '../threads.css';
Template.threads.helpers({
rid() {
const { rid } = Template.instance().data;
return rid;
},
close() {
const { data } = Template.instance();
const { tabBar } = data;
return () => tabBar.close();
},
});

@ -16,9 +16,9 @@ Meteor.startup(function() {
icon: 'thread',
label: 'Reply_in_thread',
context: ['message', 'message-mobile'],
action() {
action(e) {
const { msg: message } = messageArgs(this);
e.stopPropagation();
FlowRouter.setParams({
tab: 'thread',
context: message.tmid || message._id,

@ -214,6 +214,9 @@
}
.contextual-bar__content.thread,
.contextual-bar__content.discussions,
.contextual-bar__content.channel-settings,
.contextual-bar__content.keyboard-shortcut-list,
.contextual-bar__content.threads {
padding: 0;
}

@ -1,18 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { TabBar } from '../../../ui-utils';
import { hasAllPermission } from '../../../authorization';
Meteor.startup(() => {
TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'clean-history',
anonymous: true,
i18nTitle: 'Prune_Messages',
icon: 'eraser',
template: 'cleanHistory',
order: 250,
condition: () => hasAllPermission('clean-channel-history', Session.get('openedRoom')),
});
});

@ -0,0 +1,18 @@
import { useMemo } from 'react';
import { addAction } from '../../../../client/views/room/lib/Toolbox';
import { usePermission } from '../../../../client/contexts/AuthorizationContext';
addAction('clean-history', ({ room }) => {
const hasPermission = usePermission('clean-channel-history', room._id);
return useMemo(() => (hasPermission ? {
groups: ['channel', 'group', 'direct'],
id: 'clean-history',
anonymous: true,
title: 'Prune_Messages',
icon: 'eraser',
template: 'cleanHistory',
order: 250,
} : null), [hasPermission]);
});

@ -1,101 +0,0 @@
<template name="flexTabBar">
{{#unless embeddedVersion}}
<div class="flex-tab-container border-component-color {{opened}}">
<section class="flex-tab border-component-color content-background-color">
<div class="content flex-tab__content">
<header class="contextual-bar__header {{#unless headerData}}contextual-bar__header--empty{{/unless}}">
{{#with headerData}}
<div class="contextual-bar__header-data">
{{> icon block="contextual-bar__header-icon" icon=icon}}
<h1 class="contextual-bar__header-title">{{_ label}}</h1>
</div>
{{/with}}
<button class="contextual-bar__header-close js-close close-flex-tab" aria-label="{{_ "Close"}}">
{{> icon block="contextual-bar__header-close-icon" icon="plus"}}
</button>
</header>
{{> Template.dynamic template=template data=flexData}}
</div>
</section>
<div class="flex-tab-bar content-background-color" role="toolbar">
{{#each buttons}}
<div class="tab-button {{active}} {{visible}} {{class}}" title="{{title}}">
<button aria-label="{{title}}">
{{> icon block="tab-button-icon" icon=icon }}
</button>
</div>
{{/each}}
</div>
</div>
{{/unless}}
</template>
<template name="RoomsActionTab">
{{# with postButtons}}
<div class="rc-room-actions iframe-toolbar">
{{#each .}}
<div class="rc-room-actions__action tab-button {{active}} {{visible}} {{class}} js-iframe-action" data-id="{{id}}">
<button class="rc-room-actions__button" title="{{title}}">
{{> icon block="tab-button-icon" icon=icon }}
</button>
</div>
{{/each}}
</div>
{{/with}}
<div class="rc-room-actions">
{{#each buttons}}
<div class="rc-room-actions__action tab-button {{active}} {{visible}} {{class}} js-action" data-id="{{id}}">
{{#with badge}}
<span class="rc-badge {{class}}">{{body}}</span>
{{/with}}
<button class="rc-room-actions__button" title="{{title}}">
{{> icon block="tab-button-icon" icon=icon }}
</button>
</div>
{{/each}}
{{# with moreButtons}}
<div class="rc-room-actions__action {{opened}}">
<button class="rc-room-actions__button js-more" title="{{_ 'More'}}">
{{> icon block="tab-button-icon" icon="menu" }}
</button>
</div>
{{/with}}
</div>
</template>
<template name="RoomsActionMore">
<div class="rc-popover__header">
<div class="rc-popover__title">
{{_"More actions"}}
</div>
{{> icon block="rc-popover__close js-close" icon="plus" }}
</div>
<div class="rc-popover__container rc-room-actions__more-container">
<!-- <div class=""> -->
{{#each buttons}}
<div class="rc-room-actions__more-action tab-button {{active}} {{visible}} {{class}} js-action" title="{{title}}">
<button class="rc-room-actions__button">
{{> icon block="tab-button-icon" icon=icon }}
</button>
<span class="rc-room-actions__description">{{title}}</span>
</div>
{{/each}}
<!-- </div> -->
</div>
</template>
<!-- <template name="RoomsActionContent">
{{#if template}}
<div class="rc-room-actions__content {{opened}}">
<section class="flex-tab border-component-color">
<header class="rc-room-actions__content-header">
{{> icon block="rc-room-actions__content-header-icon" icon="clip"}}
<h1 class="rc-room-actions__content-header-title">Title</h1>
</header>
<main class="rc-room-actions__content-main">
{{> Template.dynamic template=template data=flexData}}
</main>
</section>
</div>
{{/if}}
</template> -->

@ -1,260 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import _ from 'underscore';
import { hasAllPermission } from '../../authorization';
import { popover, TabBar, Layout } from '../../ui-utils';
import { t } from '../../utils';
import { settings } from '../../settings';
const commonHelpers = {
title() {
return t(this.i18nTitle) || this.title;
},
active() {
if (this.template === Template.instance().tabBar.getTemplate() && Template.instance().tabBar.getState() === 'opened') {
return 'active';
}
},
};
function canShowAddUsersButton(rid) {
const canAddToChannel = hasAllPermission(
'add-user-to-any-c-room', rid,
);
const canAddToGroup = hasAllPermission(
'add-user-to-any-p-room', rid,
);
const canAddToJoinedRoom = hasAllPermission(
'add-user-to-joined-room', rid,
);
if (
!canAddToJoinedRoom
&& !canAddToChannel
&& Template.instance().tabBar.currentGroup() === 'channel'
) {
return false;
}
if (
!canAddToJoinedRoom
&& !canAddToGroup
&& Template.instance().tabBar.currentGroup() === 'group'
) {
return false;
}
return true;
}
const filterButtons = (button, anonymous, rid) => {
if (!Meteor.userId() && !anonymous) {
return false;
}
if (button.groups.indexOf(Template.instance().tabBar.currentGroup()) === -1) {
return false;
}
if (button.id === 'addUsers' && !canShowAddUsersButton(rid)) {
return false;
}
if (button.id === 'thread' && !settings.get('Threads_enabled')) {
return false;
}
if (button.id === 'gameCenter' && !settings.get('Apps_Game_Center_enabled')) {
return false;
}
return true;
};
Template.flexTabBar.helpers({
headerData() {
return Template.instance().tabBar.getData();
},
...commonHelpers,
buttons() {
return TabBar.getButtons().filter((button) =>
filterButtons(button, this.anonymous, this.data && this.data.rid),
);
},
opened() {
return Template.instance().tabBar.getState();
},
template() {
return Template.instance().tabBar.getTemplate();
},
flexData() {
return Object.assign(Template.currentData().data || {}, {
tabBar: Template.instance().tabBar,
});
},
embeddedVersion() {
return Layout.isEmbedded();
},
});
const commonEvents = {
'click .js-action'(e, t) {
$('button', e.currentTarget).blur();
e.preventDefault();
const $flexTab = $('.flex-tab-container .flex-tab');
if (t.tabBar.getState() === 'opened' && t.tabBar.getTemplate() === this.template) {
$flexTab.attr('template', '');
return t.tabBar.close();
}
if (this.action) {
return this.action();
}
$flexTab.attr('template', this.template);
t.tabBar.setData({
...this,
label: this.i18nTitle,
icon: this.icon,
});
t.tabBar.open(this);
popover.close();
},
};
const action = function(e, t) {
$('button', e.currentTarget).blur();
e.preventDefault();
const $flexTab = $('.flex-tab-container .flex-tab');
if (this.actionDefault) {
return this.actionDefault();
}
if (t.tabBar.getState() === 'opened' && t.tabBar.getTemplate() === this.template) {
$flexTab.attr('template', '');
return t.tabBar.close();
}
$flexTab.attr('template', this.template);
t.tabBar.setData({
...this,
label: this.i18nTitle,
icon: this.icon,
});
t.tabBar.open(this);
popover.close();
};
Template.flexTabBar.events({
'click .tab-button'(e, t) {
e.preventDefault();
const $flexTab = $('.flex-tab-container .flex-tab');
if (t.tabBar.getState() === 'opened' && t.tabBar.getTemplate() === this.template) {
$flexTab.attr('template', '');
return t.tabBar.close();
}
$flexTab.attr('template', this.template);
t.tabBar.setData({
label: this.i18nTitle,
icon: this.icon,
});
t.tabBar.open(this.id);
},
'click .close-flex-tab'(event, t) {
t.tabBar.close();
},
});
Template.flexTabBar.onCreated(function() {
this.tabBar = Template.currentData().tabBar;
});
Template.RoomsActionMore.events({
...commonEvents,
});
Template.RoomsActionMore.helpers({
...commonHelpers,
});
Template.RoomsActionMore.onCreated(function() {
this.tabBar = Template.currentData().tabBar;
});
Template.RoomsActionTab.events({
...commonEvents,
'click .js-more'(e, t) {
$(e.currentTarget).blur();
e.preventDefault();
const buttons = TabBar.getButtons().filter((button) => filterButtons(button, t.anonymous, t.data.rid));
const groups = [{ items: (t.small.get() ? buttons : buttons.slice(TabBar.size)).map((item) => ({
...item,
name: TAPi18n.__(item.i18nTitle),
actionDefault: item.action !== action && item.action,
action,
})) }];
const columns = [groups];
columns[0] = { groups };
const config = {
columns,
popoverClass: 'message-box',
data: {
rid: this._id,
buttons: t.small.get() ? buttons : buttons.slice(TabBar.size),
tabBar: t.tabBar,
},
currentTarget: e.currentTarget,
offsetHorizontal: -e.currentTarget.clientWidth,
offsetVertical: e.currentTarget.clientHeight + 10,
};
popover.open(config);
},
});
Template.RoomsActionTab.onDestroyed(function() {
$(window).off('resize', this.refresh);
});
Template.RoomsActionTab.onCreated(function() {
this.small = new ReactiveVar(window.matchMedia('(max-width: 500px)').matches);
this.refresh = _.throttle(() => {
this.small.set(window.matchMedia('(max-width: 500px)').matches);
}, 100);
$(window).on('resize', this.refresh);
this.tabBar = Template.currentData().tabBar;
});
Template.RoomsActionTab.helpers({
...commonHelpers,
postButtons() {
const toolbar = Session.get('toolbarButtons') || {};
return Object.keys(toolbar.buttons || []).map((key) => ({ id: key, ...toolbar.buttons[key] }));
},
active() {
if (this.template === Template.instance().tabBar.getTemplate() && Template.instance().tabBar.getState() === 'opened') {
return 'active';
}
},
buttons() {
if (Template.instance().small.get()) {
return [];
}
const buttons = TabBar.getButtons()
.filter((button) => filterButtons(button, Template.instance().anonymous, Template.instance().data.rid));
return buttons.length <= TabBar.size ? buttons : buttons.slice(0, TabBar.size);
},
moreButtons() {
if (Template.instance().small.get()) {
return true;
}
const buttons = TabBar.getButtons()
.filter((button) => filterButtons(button, Template.instance().anonymous, Template.instance().data.rid));
return buttons.length > TabBar.size;
},
});

@ -1,3 +0,0 @@
import './flexTabBar.html';
import './flexTabBar';
// import './tabs/uploadedFilesList';

@ -1 +0,0 @@
export * from './client/index';

@ -150,8 +150,11 @@ const mergeSubRoom = (subscription) => {
v: 1,
streamingOptions: 1,
usernames: 1,
description: 1,
topic: 1,
encrypted: 1,
// autoTranslate: 1,
// autoTranslateLanguage: 1,
description: 1,
announcement: 1,
broadcast: 1,
archived: 1,
@ -177,7 +180,7 @@ const mergeSubRoom = (subscription) => {
subscription.lm = subscription.lr ? new Date(Math.max(subscription.lr, lastRoomUpdate)) : lastRoomUpdate;
subscription.streamingOptions = room.streamingOptions;
subscription.encrypted = room.encrypted;
subscription.description = room.description;
subscription.cl = room.cl;
subscription.topic = room.topic;
@ -197,6 +200,7 @@ const mergeRoomSub = (room) => {
rid: room._id,
}, {
$set: {
encrypted: room.encrypted,
description: room.description,
cl: room.cl,
topic: room.topic,

@ -15,8 +15,6 @@ export { Layout } from './lib/Layout';
export { IframeLogin, iframeLogin } from './lib/IframeLogin';
export { fireGlobalEvent } from './lib/fireGlobalEvent';
export { getAvatarAsPng } from './lib/avatar';
export { TabBar, TABBAR_DEFAULT_VISIBLE_ICON_COUNT } from './lib/TabBar';
export { RocketChatTabBar } from './lib/RocketChatTabBar';
export { popout } from './lib/popout';
export { messageProperties } from '../lib/MessageProperties';
export { MessageTypes } from '../lib/MessageTypes';

@ -9,6 +9,6 @@ export const Layout = new class RocketChatLayout {
}
isEmbedded() {
return this.layout === 'embedded';
return FlowRouter.getQueryParam('layout') === 'embedded';
}
}();

@ -363,14 +363,12 @@ Meteor.startup(async function() {
icon: 'emoji',
label: 'Reactions',
context: ['message', 'message-mobile', 'threads'],
action(_, roomInstance) {
action(_, { tabBar, rid }) {
const { msg: { reactions } } = messageArgs(this);
modal.open({
template: createTemplateForComponent('reactionList', () => import('./ReactionListContent'), {
renderContainerView: () => HTML.DIV({ style: 'margin: -16px; height: 100%; display: flex; flex-direction: column; overflow: hidden;' }), // eslint-disable-line new-cap
}),
data: { reactions, roomInstance, onClose: () => modal.close() },
template: 'reactionList',
data: { reactions, tabBar, rid, onClose: () => modal.close() },
});
},
condition({ msg: { reactions } }) {
@ -380,3 +378,7 @@ Meteor.startup(async function() {
group: 'menu',
});
});
createTemplateForComponent('reactionList', () => import('./ReactionListContent'), {
renderContainerView: () => HTML.DIV({ style: 'margin: -16px; height: 100%; display: flex; flex-direction: column; overflow: hidden;' }), // eslint-disable-line new-cap
});

@ -4,15 +4,12 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '../../../../client/contexts/TranslationContext';
import { useSetting } from '../../../../client/contexts/SettingsContext';
import { useSession } from '../../../../client/contexts/SessionContext';
import Emoji from '../../../../client/components/Emoji';
import ScrollableContentWrapper from '../../../../client/components/ScrollableContentWrapper';
import { openUserCard } from '../../../ui/client/lib/UserCard';
import { openProfileTabOrOpenDM } from '../../../ui/client/views/app/room';
export function Reactions({ reactions, roomInstance, onClose }) {
export function Reactions({ reactions, onClick }) {
const useRealName = useSetting('UI_Use_Real_Name');
return <ScrollableContentWrapper>
<Box>
{Object.entries(reactions).map(([reaction, { names = [], usernames }]) => <Box key={reaction}>
@ -23,8 +20,7 @@ export function Reactions({ reactions, roomInstance, onClose }) {
key={username}
displayName={useRealName ? names[i] || username : username}
username={username}
roomInstance={roomInstance}
onClose={onClose}
onClick={onClick}
/>)}
</Box>
</Box>
@ -33,28 +29,32 @@ export function Reactions({ reactions, roomInstance, onClose }) {
</ScrollableContentWrapper>;
}
export function Username({ username, displayName, roomInstance, onClose }) {
const openedRoom = useSession('openedRoom');
const handleUserCard = useMutableCallback((e) => openUserCard({
username,
rid: openedRoom,
target: e.currentTarget,
open: (e) => {
e.preventDefault();
onClose();
openProfileTabOrOpenDM(e, roomInstance, username);
},
}));
export function Username({ username, onClick, displayName }) {
return (
<Box marginInlineEnd='x4' onClick={handleUserCard} key={displayName}>
<Box marginInlineEnd='x4' data-username={username} onClick={onClick} key={displayName}>
<Tag>{displayName}</Tag>
</Box>
);
}
export default function ReactionListContent({ reactions, roomInstance, onClose }) {
export default function ReactionListContent({ rid, reactions, tabBar, onClose }) {
const t = useTranslation();
const onClick = useMutableCallback((e) => {
const { username } = e.currentTarget.dataset;
if (!username) {
return;
}
openUserCard({
username,
rid,
target: e.currentTarget,
open: (e) => {
e.preventDefault();
onClose();
tabBar.openUserInfo(username);
},
});
});
return <>
<Modal.Header>
@ -62,7 +62,7 @@ export default function ReactionListContent({ reactions, roomInstance, onClose }
<Modal.Close onClick={onClose}/>
</Modal.Header>
<Modal.Content fontScale='p1'>
<Reactions reactions={reactions} roomInstance={roomInstance} onClose={onClose}/>
<Reactions reactions={reactions} onClick={onClick} onClose={onClose}/>
</Modal.Content>
<Modal.Footer>
<ButtonGroup align='end'>

@ -1,86 +0,0 @@
import { ReactiveVar } from 'meteor/reactive-var';
import { Tracker } from 'meteor/tracker';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { TabBar } from './TabBar';
export class RocketChatTabBar {
constructor() {
this.template = new ReactiveVar();
this.id = new ReactiveVar();
this.group = new ReactiveVar();
this.state = new ReactiveVar();
this.data = new ReactiveVar();
}
getTemplate() {
return this.template.get();
}
getId() {
return this.id.get();
}
setTemplate(template) {
this.template.set(template);
}
currentGroup() {
return this.group.get();
}
showGroup(group) {
this.group.set(group);
}
extendsData(data) {
this.data.set({ ...this.data.get(), ...data });
}
setData(d) {
this.data.set(d);
}
getData() {
return this.data.get();
}
getButtons() {
return TabBar.getButtons();
}
getState() {
return this.state.get();
}
open(button) {
this.state.set('opened');
Tracker.afterFlush(() => {
$('.contextual-bar__container').scrollTop(0).find('input[type=text]:first').focus();
});
const current = FlowRouter.current();
FlowRouter.go(current.route.name, { ...current.params, tab: null, context: null });
if (!button) {
return;
}
if (typeof button !== 'object' || !button.id) {
button = TabBar.getButton(button);
}
$('.flex-tab, .contextual-bar').css('width', button.width ? `${ button.width }px` : '');
this.template.set(button.template);
this.id.set(button.id);
return button;
}
close() {
this.state.set('');
$('.flex-tab, .contextual-bar').css('width', '');
this.template.set();
this.id.set();
}
}

@ -1,104 +0,0 @@
import _ from 'underscore';
import { ReactiveVar } from 'meteor/reactive-var';
import { Tracker } from 'meteor/tracker';
export const TABBAR_DEFAULT_VISIBLE_ICON_COUNT = 6;
export const TabBar = new class TabBar {
get size() {
return this._size.get();
}
set size(s) {
this._size.set(s);
}
constructor() {
this.buttons = new ReactiveVar({});
this._size = new ReactiveVar(TABBAR_DEFAULT_VISIBLE_ICON_COUNT);
this.extraGroups = {};
}
show() {
$('.flex-tab-bar').show();
}
hide() {
$('.flex-tab-bar').hide();
}
addButton(config) {
if (!config || !config.id) {
return false;
}
const btns = this.buttons.curValue;
btns[config.id] = config;
if (this.extraGroups[config.id]) {
btns[config.id].groups = _.union(btns[config.id].groups || [], this.extraGroups[config.id]);
}
// When you add a button with an order value of -1
// we assume you want to force the visualization of your button
// so we increase the number of buttons that are shown so you
// don't end up hiding any of the default ones
if (config.order === -1) {
Tracker.nonreactive(() => this.size++);
}
this.buttons.set(btns);
}
removeButton(id) {
const btns = this.buttons.curValue;
// Here we decrease the shown count as your
// button is no longer present
if (btns[id] && btns[id].order === -1) {
Tracker.nonreactive(() => this.size--);
}
delete btns[id];
this.buttons.set(btns);
}
updateButton(id, config) {
const btns = this.buttons.curValue;
if (btns[id]) {
btns[id] = _.extend(btns[id], config);
this.buttons.set(btns);
}
}
getButtons() {
const buttons = _.toArray(this.buttons.get()).filter((button) => !button.condition || button.condition());
return _.sortBy(buttons, 'order');
}
getButton(id) {
return _.findWhere(this.buttons.get(), { id });
}
addGroup(id, groups) {
const btns = this.buttons.curValue;
if (btns[id]) {
btns[id].groups = _.union(btns[id].groups || [], groups);
this.buttons.set(btns);
} else {
this.extraGroups[id] = _.union(this.extraGroups[id] || [], groups);
}
}
removeGroup(id, groups) {
const btns = this.buttons.curValue;
if (btns[id]) {
btns[id].groups = _.difference(btns[id].groups || [], groups);
this.buttons.set(btns);
} else {
this.extraGroups[id] = _.difference(this.extraGroups[id] || [], groups);
}
}
}();

@ -1,16 +1,11 @@
import './popover.html';
import { Meteor } from 'meteor/meteor';
import { Blaze } from 'meteor/blaze';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Template } from 'meteor/templating';
import _ from 'underscore';
import { hide, leave } from './ChannelActions';
import { messageBox } from './messageBox';
import { MessageAction } from './MessageAction';
import { RoomManager } from './RoomManager';
import { ChatSubscription } from '../../../models/client';
import { isRtl, handleError } from '../../../utils/client';
import { isRtl } from '../../../utils/client';
export const popover = {
renderedPopover: null,
@ -174,58 +169,14 @@ Template.popover.events({
'click [data-type="message-action"]'(e, t) {
const button = MessageAction.getButtonById(e.currentTarget.dataset.id);
if ((button != null ? button.action : undefined) != null) {
button.action.call(t.data.data, e, t.data.instance);
e.stopPropagation();
e.preventDefault();
const { tabBar, rid } = t.data.instance;
button.action.call(t.data.data, e, { tabBar, rid });
popover.close();
return false;
}
},
'click [data-type="sidebar-item"]'(e, instance) {
popover.close();
const { rid, name, template } = instance.data.data;
const action = e.currentTarget.dataset.id;
if (action === 'hide') {
hide(template, rid, name);
}
if (action === 'leave') {
leave(template, rid, name);
}
if (action === 'read') {
Meteor.call('readMessages', rid);
return false;
}
if (action === 'unread') {
Meteor.call('unreadMessages', null, rid, function(error) {
if (error) {
return handleError(error);
}
const subscription = ChatSubscription.findOne({ rid });
if (subscription == null) {
return;
}
RoomManager.close(subscription.t + subscription.name);
FlowRouter.go('home');
});
return false;
}
if (action === 'favorite') {
Meteor.call('toggleFavorite', rid, !$(e.currentTarget).hasClass('rc-popover__item--star-filled'), function(err) {
popover.close();
if (err) {
handleError(err);
}
});
return false;
}
},
});
Template.popover.helpers({

@ -1,28 +0,0 @@
<template name="contextualBar">
{{#if template}}
{{#if full}}
{{> Template.dynamic template=template data=flexData}}
{{else}}
<div class="contextual-bar">
<div class="contextual-bar-wrap">
<header class="contextual-bar__header">
{{#with headerData}}
<div class="contextual-bar__header-data">
{{> icon block="contextual-bar__header-icon" icon=icon}}
<h1 class="contextual-bar__header-title">{{_ label}}
<sub class="contextual-bar__header-description">{{ description }}</sub>
</h1>
</div>
{{/with}}
<button class="contextual-bar__header-close js-close" aria-label="{{_ "Close"}}">
{{> icon block="contextual-bar__header-close-icon" icon="plus"}}
</button>
</header>
<section class="contextual-bar__content flex-tab {{id}}">
{{> Template.dynamic template=template data=flexData}}
</section>
</div>
</div>
{{/if}}
{{/if}}
</template>

@ -1,31 +0,0 @@
import { Template } from 'meteor/templating';
Template.contextualBar.events({
'click .js-close'(e, t) {
t.tabBar.close();
},
});
Template.contextualBar.onCreated(function() {
this.tabBar = Template.currentData().tabBar;
});
Template.contextualBar.helpers({
id() {
return Template.instance().tabBar.getId();
},
template() {
return Template.instance().tabBar.getTemplate();
},
headerData() {
return Template.instance().tabBar.getData();
},
flexData() {
const { tabBar } = Template.instance();
return {
tabBar,
...tabBar.getData(),
...Template.currentData().data,
};
},
});

@ -1,6 +1,6 @@
import { Template } from 'meteor/templating';
import { TabBar, fireGlobalEvent } from '../../../../ui-utils';
import { fireGlobalEvent } from '../../../../ui-utils';
import './header.html';
Template.header.helpers({
@ -8,7 +8,7 @@ Template.header.helpers({
return Template.instance().data.back;
},
buttons() {
return TabBar.getButtons();
console.log('asdasd');
},
});

@ -1,75 +0,0 @@
<template name="headerRoom">
<header class="rc-header rc-header--room" >
<div class="rc-header__wrap">
<div class="rc-header__block rc-header--burger">
{{> burger}}
</div>
{{#if isDiscussion}}
<div class="rc-header__block rc-header__block--action js-open-parent-channel" title={{_ "Back_to_room"}}>
<span class="rc-header__first-icon">{{> icon block="rc-header__icon rc-header__icon" icon="back"}}</span>
</div>
{{/if}}
{{#if isToggleFavoriteButtonVisible}}
<div class="rc-header__block rc-header__favorite rc-header__block--action">
<button title="{{toggleFavoriteButtonIconLabel}}" type="button" class="rc-header__toggle-favorite {{#if isToggleFavoriteButtonChecked}}rc-header__toggle-favorite--checked{{/if}} rc-header__first-icon js-favorite">
{{> icon block="rc-header__icon" icon=toggleFavoriteButtonIcon}}
</button>
</div>
{{/if}}
<!-- TODO: fix it style and helper -->
{{#if tokenAccessChannel}}
<i class="icon-tokenpass" aria-label="{{_ "Tokenpass_Channel_Label"}}"></i>
{{/if}}
<div class="rc-header__content rc-header__block">
<div class="rc-header__block">
<div class="rc-header__image">
{{> avatar url=avatarBackground}}
</div>
</div>
<div class="rc-header__data">
{{#unless secondaryName}}
<div class="rc-header__name">{{> icon block="rc-header__icon" icon=roomIcon}}{{roomName}}</div>
{{else}}
<div class="rc-header__name">{{roomName}} <div class="rc-header__username">@{{secondaryName}}</div></div>
{{/unless}}
{{#if hasPresence}}
{{# userPresence uid=uid}}<span class="rc-header__status">
<div class="rc-header__status-bullet rc-header__status-bullet--{{userStatus}}" title="{{_ userStatus}}"></div>
<div class="rc-header__visual-status">{{userStatusText}}</div>
</span>{{/userPresence}}
{{else}}
{{#if roomTopic}}<span class="rc-header__topic">{{{roomTopic}}}</span>{{/if}}
{{/if}}
</div>
{{#if sentimentSmile}}
<span class="sentiment">{{sentimentSmile}}</span>
{{/if}}
{{#if isTranslated}}
<i class="icon-language" title="{{_ "Translated"}}"></i>
{{/if}}
</div>
{{#if isSection}}
<span class="rc-header__block">{{_ sectionName}}</span>
{{/if}}
{{#if encryptionState}}
<div class="rc-header__block rc-header__block-action rc-header__encryption">
<div class="rc-room-actions">
<button title="{{_ 'E2E_Enabled'}}" class="rc-room-actions__action tab-button rc-header__toggle-encryption js-toggle-encryption {{encryptionState}}">{{> icon icon="key"}}</button>
</div>
</div>
{{/if}}
{{#if Template.contentBlock}}
{{> Template.contentBlock}}
{{/if}}
</div>
</header>
</template>

@ -1,251 +0,0 @@
import toastr from 'toastr';
import { Meteor } from 'meteor/meteor';
import { ReactiveDict } from 'meteor/reactive-dict';
import { ReactiveVar } from 'meteor/reactive-var';
import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { t, roomTypes, handleError } from '../../../../utils';
import { TabBar, fireGlobalEvent, call } from '../../../../ui-utils';
import { ChatSubscription, Rooms, ChatRoom } from '../../../../models';
import { settings } from '../../../../settings';
import { emoji } from '../../../../emoji';
import { Markdown } from '../../../../markdown/client';
import { hasAllPermission } from '../../../../authorization';
import { getUidDirectMessage } from '../../../../ui-utils/client/lib/getUidDirectMessage';
import './headerRoom.html';
const getUserStatus = (id) => {
const roomData = Session.get(`roomData${ id }`);
return roomTypes.getUserStatus(roomData.t, id);
};
const getUserStatusText = (id) => {
const roomData = Session.get(`roomData${ id }`);
return roomTypes.getUserStatusText(roomData.t, id);
};
Template.headerRoom.helpers({
isDiscussion: () => Template.instance().state.get('discussion'),
hasPresence() {
const room = Rooms.findOne(this._id);
return !roomTypes.getConfig(room.t).isGroupChat(room);
},
isToggleFavoriteButtonVisible: () => Template.instance().state.get('favorite') !== null,
isToggleFavoriteButtonChecked: () => Template.instance().state.get('favorite'),
toggleFavoriteButtonIconLabel: () => (Template.instance().state.get('favorite') ? t('Unfavorite') : t('Favorite')),
toggleFavoriteButtonIcon: () => (Template.instance().state.get('favorite') ? 'star-filled' : 'star'),
uid() {
return getUidDirectMessage(this._id);
},
back() {
return Template.instance().data.back;
},
avatarBackground() {
const roomData = Session.get(`roomData${ this._id }`);
if (!roomData) { return ''; }
return roomTypes.getConfig(roomData.t).getAvatarPath(roomData);
},
buttons() {
return TabBar.getButtons();
},
isTranslated() {
const sub = ChatSubscription.findOne({ rid: this._id }, { fields: { autoTranslate: 1, autoTranslateLanguage: 1 } });
return settings.get('AutoTranslate_Enabled') && ((sub != null ? sub.autoTranslate : undefined) === true) && (sub.autoTranslateLanguage != null);
},
roomName() {
const roomData = Session.get(`roomData${ this._id }`);
if (!roomData) { return ''; }
return roomTypes.getRoomName(roomData.t, roomData);
},
secondaryName() {
const roomData = Session.get(`roomData${ this._id }`);
if (!roomData) { return ''; }
return roomTypes.getSecondaryRoomName(roomData.t, roomData);
},
roomTopic() {
const roomData = Session.get(`roomData${ this._id }`);
if (!roomData || !roomData.topic) { return ''; }
let roomTopic = Markdown.parse(roomData.topic.replace(/\n/mg, ' '));
// &#39; to apostrophe (') for emojis such as :')
roomTopic = roomTopic.replace(/&#39;/g, '\'');
roomTopic = Object.keys(emoji.packages).reduce((topic, emojiPackage) => emoji.packages[emojiPackage].render(topic), roomTopic);
// apostrophe (') back to &#39;
return roomTopic.replace(/\'/g, '&#39;');
},
roomIcon() {
const roomData = Session.get(`roomData${ this._id }`);
if (!(roomData != null ? roomData.t : undefined)) { return ''; }
return roomTypes.getIcon(roomData);
},
tokenAccessChannel() {
return Template.instance().hasTokenpass.get();
},
encryptionState() {
const room = ChatRoom.findOne(this._id);
return settings.get('E2E_Enable') && room && room.encrypted && 'encrypted';
},
userStatus() {
return getUserStatus(this._id) || 'offline';
},
userStatusText() {
const statusText = getUserStatusText(this._id);
if (statusText) {
return statusText;
}
const presence = getUserStatus(this._id);
if (presence) {
return t(presence);
}
const oldStatusText = Template.instance().userOldStatusText.get();
if (oldStatusText) {
return oldStatusText;
}
return t('offline');
},
fixedHeight() {
return Template.instance().data.fixedHeight;
},
fullpage() {
return Template.instance().data.fullpage;
},
isChannel() {
return Template.instance().currentChannel != null;
},
isSection() {
return Template.instance().data.sectionName != null;
},
});
Template.headerRoom.events({
'click .iframe-toolbar .js-iframe-action'(e) {
fireGlobalEvent('click-toolbar-button', { id: this.id });
e.currentTarget.querySelector('button').blur();
return false;
},
'click .js-favorite'(event, instance) {
event.stopPropagation();
event.preventDefault();
event.currentTarget.blur();
return Meteor.call(
'toggleFavorite',
this._id,
!instance.state.get('favorite'),
(err) => err && handleError(err),
);
},
'click .js-open-parent-channel'(event, t) {
event.preventDefault();
const { prid } = t.currentChannel;
FlowRouter.goToRoomById(prid);
},
'click .js-toggle-encryption'(event) {
event.stopPropagation();
event.preventDefault();
const room = ChatRoom.findOne(this._id);
if (hasAllPermission('edit-room', this._id) || (room && room.t)) {
call('saveRoomSettings', this._id, 'encrypted', !(room && room.encrypted)).then(() => {
toastr.success(
t('Encrypted_setting_changed_successfully'),
);
});
}
},
});
const loadUserStatusText = () => {
const instance = Template.instance();
if (!instance || !instance.data || !instance.data._id) {
return;
}
const id = instance.data._id;
if (Rooms.findOne(id).t !== 'd') {
return;
}
const userId = getUidDirectMessage(id);
// If the user is already on the local collection, the method call is not necessary
const found = Meteor.users.findOne(userId, { fields: { _id: 1 } });
if (found) {
return;
}
Meteor.call('getUserStatusText', userId, (error, result) => {
if (!error) {
instance.userOldStatusText.set(result);
}
});
};
Template.headerRoom.onCreated(function() {
this.state = new ReactiveDict();
const isFavoritesEnabled = () => settings.get('Favorite_Rooms');
const isDiscussion = (rid) => {
const room = ChatRoom.findOne({ _id: rid });
return !!(room && room.prid);
};
this.autorun(() => {
const { _id: rid } = Template.currentData();
this.state.set({
rid,
discussion: isDiscussion(rid),
});
if (!this.state.get('discussion') && isFavoritesEnabled()) {
const subscription = ChatSubscription.findOne({ rid }, { fields: { f: 1 } });
this.state.set('favorite', !!(subscription && subscription.f));
} else {
this.state.set('favorite', null);
}
});
this.currentChannel = (this.data && this.data._id && Rooms.findOne(this.data._id)) || undefined;
this.hasTokenpass = new ReactiveVar(false);
this.userOldStatusText = new ReactiveVar(null);
if (settings.get('API_Tokenpass_URL') !== '') {
Meteor.call('getChannelTokenpass', this.data._id, (error, result) => {
if (!error) {
this.hasTokenpass.set(!!(result && result.tokens && result.tokens.length > 0));
}
});
}
loadUserStatusText();
});

@ -48,9 +48,6 @@ import './components/popupList';
import './components/selectDropdown.html';
import './components/header/header';
import './components/header/headerRoom';
import './components/contextualBar.html';
import './components/contextualBar';
import './components/tooltip';
import './lib/Tooltip';

@ -1,6 +1,5 @@
import { Meteor } from 'meteor/meteor';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Session } from 'meteor/session';
import { ServiceConfiguration } from 'meteor/service-configuration';
import s from 'underscore.string';
@ -8,6 +7,7 @@ import { AccountBox } from '../../../ui-utils';
import { settings } from '../../../settings';
import { callbacks } from '../../../callbacks';
import { baseURI } from '../../../utils/client/lib/baseuri.js';
import { add, remove } from '../../../../client/views/room/lib/Toolbox/IframeButtons';
const commands = {
go(data) {
@ -69,16 +69,11 @@ const commands = {
});
},
'set-toolbar-button'({ id, icon, label: i18nTitle }) {
const toolbar = Session.get('toolbarButtons') || { buttons: {} };
toolbar.buttons[id] = { icon, i18nTitle };
Session.set('toolbarButtons', toolbar);
'set-toolbar-button'({ id, icon, label }) {
add(id, { id, icon, label });
},
'remove-toolbar-button'({ id }) {
const toolbar = Session.get('toolbarButtons') || { buttons: {} };
delete toolbar.buttons[id];
Session.set('toolbarButtons', toolbar);
remove(id);
},
};

@ -0,0 +1,317 @@
import Clipboard from 'clipboard';
import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { FlowRouter } from 'meteor/kadira:flow-router';
import {
fireGlobalEvent,
popover,
Layout,
MessageAction,
} from '../../../../../ui-utils/client';
import { call } from '../../../../../ui-utils/client/lib/callMethod';
import { promises } from '../../../../../promises/client';
import { isURL } from '../../../../../utils/lib/isURL';
import { openUserCard } from '../../../lib/UserCard';
import { messageArgs } from '../../../../../ui-utils/client/lib/messageArgs';
import { ChatMessage, Rooms } from '../../../../../models';
import { t, roomTypes } from '../../../../../utils/client';
import { chatMessages } from '../room';
import { EmojiEvents } from '../../../../../reactions/client/init';
const mountPopover = (e, i, outerContext) => {
let context = $(e.target).parents('.message').data('context');
if (!context) {
context = 'message';
}
const messageContext = messageArgs(outerContext);
let menuItems = MessageAction.getButtons(messageContext, context, 'menu').map((item) => ({
icon: item.icon,
name: t(item.label),
type: 'message-action',
id: item.id,
modifier: item.color,
}));
if (window.matchMedia('(max-width: 500px)').matches) {
const messageItems = MessageAction.getButtons(messageContext, context, 'message').map((item) => ({
icon: item.icon,
name: t(item.label),
type: 'message-action',
id: item.id,
modifier: item.color,
}));
menuItems = menuItems.concat(messageItems);
}
const [items, deleteItem] = menuItems.reduce((result, value) => { result[value.id === 'delete-message' ? 1 : 0].push(value); return result; }, [[], []]);
const groups = [{ items }];
if (deleteItem.length) {
groups.push({ items: deleteItem });
}
const config = {
columns: [
{
groups,
},
],
instance: i,
currentTarget: e.currentTarget,
data: outerContext,
activeElement: $(e.currentTarget).parents('.message')[0],
onRendered: () => new Clipboard('.rc-popover__item'),
};
popover.open(config);
};
export const getCommonRoomEvents = () => ({
...(() => {
let touchMoved = false;
let lastTouchX = null;
let lastTouchY = null;
let touchtime = null;
return {
...EmojiEvents,
'click .message img'(e) {
clearTimeout(touchtime);
if (touchMoved === true) {
e.preventDefault();
e.stopPropagation();
}
},
'touchstart .message'(e) {
const { touches } = e.originalEvent;
if (touches && touches.length) {
lastTouchX = touches[0].pageX;
lastTouchY = touches[0].pagey;
}
touchMoved = false;
if (e.originalEvent.touches.length !== 1) {
return;
}
if ($(e.currentTarget).hasClass('system')) {
return;
}
if (e.target && (e.target.nodeName === 'AUDIO')) {
return;
}
if (e.target && (e.target.nodeName === 'A') && isURL(e.target.getAttribute('href'))) {
e.preventDefault();
e.stopPropagation();
}
const doLongTouch = () => {
mountPopover(e, t, this);
};
clearTimeout(touchtime);
touchtime = setTimeout(doLongTouch, 500);
},
'touchend .message'(e) {
clearTimeout(touchtime);
if (e.target && (e.target.nodeName === 'A') && isURL(e.target.getAttribute('href'))) {
if (touchMoved === true) {
e.preventDefault();
e.stopPropagation();
return;
}
window.open(e.target.href);
}
},
'touchmove .message'(e) {
const { touches } = e.originalEvent;
if (touches && touches.length) {
const deltaX = Math.abs(lastTouchX - touches[0].pageX);
const deltaY = Math.abs(lastTouchY - touches[0].pageY);
if (deltaX > 5 || deltaY > 5) {
touchMoved = true;
}
}
clearTimeout(touchtime);
},
'touchcancel .message'() {
clearTimeout(touchtime);
},
};
})(),
'click [data-message-action]'(event, template) {
const button = MessageAction.getButtonById(event.currentTarget.dataset.messageAction);
if ((button != null ? button.action : undefined) != null) {
button.action.call(this, event, { tabBar: template.tabBar, rid: template.data.rid });
}
},
'click .js-follow-thread'(e) {
e.preventDefault();
e.stopPropagation();
const { msg } = messageArgs(this);
call('followMessage', { mid: msg._id });
},
'click .js-unfollow-thread'(e) {
e.preventDefault();
e.stopPropagation();
const { msg } = messageArgs(this);
call('unfollowMessage', { mid: msg._id });
},
'click .js-open-thread'(event) {
event.preventDefault();
event.stopPropagation();
const { msg: { rid, _id, tmid } } = messageArgs(this);
const room = Rooms.findOne({ _id: rid });
FlowRouter.go(FlowRouter.getRouteName(), {
rid,
name: room.name,
tab: 'thread',
context: tmid || _id,
}, {
jump: tmid && tmid !== _id && _id && _id,
});
},
'click .js-reply-broadcast'() {
const msg = messageArgs(this);
roomTypes.openRouteLink('d', { name: msg.u.username }, { ...FlowRouter.current().queryParams, reply: msg._id });
},
'click .image-to-download'(event) {
const { msg } = messageArgs(this);
ChatMessage.update({ _id: msg._id, 'urls.url': $(event.currentTarget).data('url') }, { $set: { 'urls.$.downloadImages': true } });
ChatMessage.update({ _id: msg._id, 'attachments.image_url': $(event.currentTarget).data('url') }, { $set: { 'attachments.$.downloadImages': true } });
},
'click .user-card-message'(e, instance) {
const { msg } = messageArgs(this);
if (!Meteor.userId()) {
return;
}
const { username } = msg.u;
if (username) {
openUserCard({
username,
rid: instance.data.rid,
target: e.currentTarget,
open: (e) => {
e.preventDefault();
instance.data.openProfileTab(username);
},
});
}
},
'click .js-actionButton-respondWithMessage'(event, instance) {
const { rid } = instance.data;
const msg = event.currentTarget.value;
if (!msg) {
return;
}
const { input } = chatMessages[rid];
input.value = msg;
input.focus();
},
async 'click .js-actionButton-sendMessage'(event, instance) {
const { rid } = instance.data;
const msg = event.currentTarget.value;
let msgObject = { _id: Random.id(), rid, msg };
if (!msg) {
return;
}
msgObject = await promises.run('onClientBeforeSendMessage', msgObject);
const _chatMessages = chatMessages[rid];
if (_chatMessages && await _chatMessages.processSlashCommand(msgObject)) {
return;
}
await call('sendMessage', msgObject);
},
'click .message-actions__menu'(e, template) {
const messageContext = messageArgs(this);
const { msg: message, context: ctx } = messageContext;
const context = ctx || message.context || message.actionContext || 'message';
const allItems = MessageAction.getButtons(messageContext, context, 'menu').map((item) => ({
icon: item.icon,
name: t(item.label),
type: 'message-action',
id: item.id,
modifier: item.color,
}));
const itemsBelowDivider = [
'delete-message',
'report-message',
];
const [items, alertsItem] = allItems.reduce((result, value) => { result[itemsBelowDivider.includes(value.id) ? 1 : 0].push(value); return result; }, [[], []]);
const groups = [{ items }];
if (alertsItem.length) {
groups.push({ items: alertsItem });
}
const config = {
columns: [
{
groups,
},
],
instance: template,
rid: template.data.rid,
data: this,
currentTarget: e.currentTarget,
activeElement: $(e.currentTarget).parents('.message')[0],
onRendered: () => new Clipboard('.rc-popover__item'),
};
popover.open(config);
},
'click .mention-link'(e, instance) {
e.stopPropagation();
e.preventDefault();
if (!Meteor.userId()) {
return;
}
const { currentTarget: { dataset: { channel, group, username } } } = e;
if (channel) {
if (Layout.isEmbedded()) {
fireGlobalEvent('click-mention-link', { path: FlowRouter.path('channel', { name: channel }), channel });
}
FlowRouter.goToRoomById(channel);
return;
}
if (group) {
return;
}
if (username) {
openUserCard({
username,
rid: instance.data.rid,
target: e.currentTarget,
open: (e) => {
e.preventDefault();
instance.data.openProfileTab(username);
},
});
}
},
});

@ -1,15 +1,6 @@
<template name="roomOld">
<div class="main-content-flex">
<section class="messages-container flex-tab-main-content {{adminClass}}" id="{{windowId}}" aria-label="{{_ "Channel"}}">
{{# if showTopNavbar}}
{{# headerRoom}}
{{#with flexData}}
<div class="rc-header__block rc-header__block-action">
{{> RoomsActionTab}}
</div>
{{/with}}
{{/headerRoom}}
{{/if}}
<div class="messages-container-wrapper">
<div class="messages-container-main dropzone {{dragAndDrop}}">
<div class="dropzone-overlay {{isDropzoneDisabled}} background-transparent-darkest color-content-background-color">{{_ dragAndDropLabel}}</div>
@ -145,14 +136,6 @@
{{> messageBox messageboxData}}
</footer>
</div>
{{# with openedThread}}
{{> ThreadComponent}}
{{/with}}
{{# unless shouldCloseFlexTab}}
{{#with flexData}}
{{> contextualBar}}
{{/with}}
{{/unless}}
</div>
</section>
</div>

File diff suppressed because it is too large Load Diff

@ -1,90 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { Session } from 'meteor/session';
import { settings } from '../../settings';
import { TabBar } from '../../ui-utils';
import { Rooms } from '../../models';
Meteor.startup(function() {
Tracker.autorun(function() {
if (!settings.get('bigbluebutton_Enabled')) {
return TabBar.removeButton('bbb_video');
}
const live = Rooms.findOne({ _id: Session.get('openedRoom'), 'streamingOptions.type': 'call' }, { fields: { streamingOptions: 1 } });
const groups = [];
if (settings.get('bigbluebutton_enable_d')) {
groups.push('direct');
}
if (settings.get('bigbluebutton_enable_p')) {
groups.push('group');
}
if (settings.get('bigbluebutton_enable_c')) {
groups.push('channel');
}
TabBar.addButton({
groups,
id: 'bbb_video',
i18nTitle: 'BBB Video Call',
icon: 'phone',
iconColor: 'red',
template: 'videoFlexTabBbb',
width: 600,
order: live ? -1 : 0,
class: () => live && 'live',
});
});
Tracker.autorun(function() {
if (settings.get('Jitsi_Enabled')) {
TabBar.addButton({
groups: ['direct', 'group'],
id: 'video',
i18nTitle: 'Call',
icon: 'phone',
iconColor: 'red',
template: 'videoFlexTab',
width: 600,
order: 0,
});
} else {
TabBar.removeButton('video');
}
});
Tracker.autorun(function() {
if (settings.get('Jitsi_Enabled') && settings.get('Jitsi_Enable_Channels')) {
TabBar.addGroup('video', ['channel']);
} else {
TabBar.removeGroup('video', ['channel']);
}
});
Tracker.autorun(function() {
if (settings.get('Jitsi_Enabled')) {
// Load from the jitsi meet instance.
if (typeof JitsiMeetExternalAPI === 'undefined') {
const prefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || '';
$.getScript(`${ prefix }/packages/rocketchat_videobridge/client/public/external_api.js`);
}
// Compare current time to call started timeout. If its past then call is probably over.
if (Session.get('openedRoom')) {
const rid = Session.get('openedRoom');
const room = Rooms.findOne({ _id: rid });
const currentTime = new Date().getTime();
const jitsiTimeout = new Date((room && room.jitsiTimeout) || currentTime).getTime();
if (jitsiTimeout > currentTime) {
TabBar.updateButton('video', { class: 'attention' });
} else {
TabBar.updateButton('video', { class: '' });
}
}
}
});
});

@ -0,0 +1,71 @@
import React, { useMemo } from 'react';
import { useStableArray } from '@rocket.chat/fuselage-hooks';
import { Option, Badge } from '@rocket.chat/fuselage';
import { useSetting } from '../../../client/contexts/SettingsContext';
import { addAction, ToolboxActionConfig } from '../../../client/views/room/lib/Toolbox';
import { useTranslation } from '../../../client/contexts/TranslationContext';
import Header from '../../../client/components/Header';
addAction('bbb_video', ({ room }) => {
const enabled = useSetting('bigbluebutton_Enabled');
const t = useTranslation();
const live = room && room.streamingOptions && room.streamingOptions.type === 'call';
const enabledDirect = useSetting('bigbluebutton_enable_d');
const enabledGroup = useSetting('bigbluebutton_enable_p');
const enabledChannel = useSetting('bigbluebutton_enable_c');
const groups = useStableArray([
enabledDirect && 'direct',
enabledGroup && 'group',
enabledChannel && 'channel',
].filter(Boolean) as ToolboxActionConfig['groups']);
return useMemo(() => (enabled ? {
groups,
id: 'bbb_video',
title: 'BBB Video Call',
icon: 'phone',
template: 'videoFlexTabBbb',
order: live ? -1 : 0,
renderAction: (props): React.ReactNode => <Header.ToolBoxAction {...props}>
{live ? <Header.Badge title={t('Started_a_video_call')} variant='primary'>!</Header.Badge> : null}
</Header.ToolBoxAction>,
renderOption: ({ label: { title, icon }, ...props }: any): React.ReactNode => <Option label={title} title={title} icon={icon} {...props}><Badge title={t('Started_a_video_call')} variant='primary'>!</Badge></Option>,
} : null), [enabled, groups, live, t]);
});
addAction('video', ({ room }) => {
const enabled = useSetting('Jitsi_Enabled');
const t = useTranslation();
const enabledChannel = useSetting('Jitsi_Enable_Channels');
const groups = useStableArray([
'direct',
'group',
'live',
enabledChannel && 'channel',
].filter(Boolean) as ToolboxActionConfig['groups']);
const currentTime = new Date().getTime();
const jitsiTimeout = new Date((room && room.jitsiTimeout) || currentTime).getTime();
const live = jitsiTimeout > currentTime || null;
return useMemo(() => (enabled ? {
groups,
id: 'video',
title: 'Call',
icon: 'phone',
template: 'videoFlexTab',
order: live ? -1 : 0,
renderAction: (props): React.ReactNode => <Header.ToolBoxAction {...props}>
{live && <Header.Badge title={t('Started_a_video_call')} variant='primary'>!</Header.Badge>}
</Header.ToolBoxAction>,
renderOption: ({ label: { title, icon }, ...props }: any): React.ReactNode => <Option label={title} title={title} icon={icon} {...props}>
{ live && <Badge title={t('Started_a_video_call')} variant='primary'>!</Badge> }
</Option>,
} : null), [enabled, groups, live, t]);
});

@ -5,7 +5,7 @@ import { Template } from 'meteor/templating';
import { TimeSync } from 'meteor/mizzao:timesync';
import { settings } from '../../../settings';
import { modal, TabBar, call } from '../../../ui-utils/client';
import { modal, call } from '../../../ui-utils/client';
import { t } from '../../../utils/client';
import { Users, Rooms } from '../../../models';
import * as CONSTANTS from '../../constants';
@ -45,7 +45,7 @@ Template.videoFlexTab.onRendered(function() {
this.tabBar.close();
TabBar.updateButton('video', { class: '' });
// TabBar.updateButton('video', { class: '' });
};
const stop = () => {
@ -78,7 +78,7 @@ Template.videoFlexTab.onRendered(function() {
}
clearInterval(this.intervalHandler);
this.intervalHandler = setInterval(update, CONSTANTS.HEARTBEAT);
TabBar.updateButton('video', { class: 'red' });
// TabBar.updateButton('video', { class: 'red' });
return jitsiTimeout;
} catch (error) {
console.error(error);
@ -105,8 +105,8 @@ Template.videoFlexTab.onRendered(function() {
return closePanel();
}
if (this.tabBar.getState() !== 'opened') {
TabBar.updateButton('video', { class: '' });
if (!this.tabBar.isOpen()) {
// TabBar.updateButton('video', { class: '' });
return stop();
}

@ -0,0 +1,41 @@
import { Box, Skeleton } from '@rocket.chat/fuselage';
import React, { useMemo } from 'react';
import { useTranslation } from '../../contexts/TranslationContext';
import CounterSet from '../../components/data/CounterSet';
import { usePolledMethodData } from '../../hooks/usePolledMethodData';
import { AsyncStatePhase } from '../../hooks/useAsyncState';
function OverviewSection() {
const t = useTranslation();
const { value: overviewData, phase: overviewStatus } = usePolledMethodData('federation:getOverviewData', useMemo(() => [], []), 10000);
const eventCount = (overviewStatus === AsyncStatePhase.LOADING && <Skeleton variant='text' />)
|| (overviewStatus === AsyncStatePhase.REJECTED && <Box color='danger'>Error</Box>)
|| overviewData?.data[0]?.value;
const userCount = (overviewStatus === AsyncStatePhase.LOADING && <Skeleton variant='text' />)
|| (overviewStatus === AsyncStatePhase.REJECTED && <Box color='danger'>Error</Box>)
|| overviewData?.data[1]?.value;
const serverCount = (overviewStatus === AsyncStatePhase.LOADING && <Skeleton variant='text' />)
|| (overviewStatus === AsyncStatePhase.REJECTED && <Box color='danger'>Error</Box>)
|| overviewData?.data[2]?.value;
return <CounterSet
counters={[
{
count: eventCount,
description: t('Number_of_events'),
},
{
count: userCount,
description: t('Number_of_federated_users'),
},
{
count: serverCount,
description: t('Number_of_federated_servers'),
},
]}
/>;
}
export default OverviewSection;

@ -0,0 +1,153 @@
import React from 'react';
import { SettingsContext } from '../../contexts/SettingsContext';
import Header from './Header';
import RoomAvatar from '../avatar/RoomAvatar';
import ToolBox from '../../views/room/Header/ToolBox';
import { ToolboxProvider } from '../../views/room/providers/ToolboxProvider';
import { addAction } from '../../views/room/lib/Toolbox';
import { useRoomIcon } from '../../hooks/useRoomIcon';
export default {
title: 'Chat Header',
component: Header,
};
const room = {
t: 'c',
name: 'general general general general general general general general general general general general general general general general general general general',
_id: 'GENERAL',
encrypted: true,
autoTranslate: true,
autoTranslateLanguage: 'pt-BR',
};
const settings = {
Favorite_Rooms: true,
AutoTranslate_Enabled: true,
E2E_Enable: true,
};
const settingContextValue = {
hasPrivateAccess: true,
isLoading: false,
querySetting: (setting) => ({
getCurrentValue: () => settings[setting],
subscribe: () => () => undefined,
}),
querySettings: () => ({
getCurrentValue: () => [],
subscribe: () => () => undefined,
}),
dispatch: async () => undefined,
};
export const ChatHeader = () => {
const icon = useRoomIcon(room);
const avatar = <RoomAvatar room={room}/>;
return <SettingsContext.Provider value={settingContextValue}>
<Header>
<Header.Avatar>{avatar}</Header.Avatar>
<Header.Content>
<Header.Content.Row>
{ icon && <Header.Icon icon={icon}/> }
<Header.Title>{room.name}</Header.Title>
<Header.State onClick icon='star'/>
<Header.State icon='key'/>
<Header.State icon='language'/>
</Header.Content.Row>
<Header.Content.Row>
<Header.Subtitle>{room.name}</Header.Subtitle>
</Header.Content.Row>
</Header.Content>
<Header.ToolBox>
<Header.ToolBoxAction icon='magnifier'/>
<Header.ToolBoxAction icon='key'/>
<Header.ToolBoxAction icon='kebab'/>
</Header.ToolBox>
</Header>
</SettingsContext.Provider>;
};
const toolboxRoom = {
...room,
msgs: 2,
u: { username: 'rocket.cat' },
usersCount: 2,
};
// const renderWithBadge = createHeaderActionRenderer(<Header.Badge variant='primary'>1</Header.Badge>);
// const renderWithRedBadge = createHeaderActionRenderer(<Header.Badge variant='danger'>2</Header.Badge>);
// const renderWithOrangeBadge = createHeaderActionRenderer(<Header.Badge variant='warning'>99</Header.Badge>);
const renderWithBadge = (props, index) => <Header.ToolBoxAction index={index} {...props} >
<Header.Badge variant='primary'>1</Header.Badge>
</Header.ToolBoxAction>;
const renderWithRedBadge = (props, index) => <Header.ToolBoxAction index={index} {...props} >
<Header.Badge variant='danger'>2</Header.Badge>
</Header.ToolBoxAction>;
const renderWithOrangeBadge = (props, index) => <Header.ToolBoxAction index={index} {...props} >
<Header.Badge variant='warning'>99</Header.Badge>
</Header.ToolBoxAction>;
addAction('render-action-example-badge', {
groups: ['channel'],
id: 'render-action-example',
title: 'Example',
icon: 'phone',
template: 'b',
order: 0,
renderAction: renderWithBadge,
});
addAction('render-action-example-badge-warning', {
groups: ['channel'],
id: 'render-action-example',
title: 'Example',
icon: 'thread',
template: 'a',
order: 1,
renderAction: renderWithOrangeBadge,
});
addAction('render-action-example-badge-danger', {
groups: ['channel'],
id: 'render-action-example',
title: 'Example',
icon: 'discussion',
template: 'c',
order: 2,
renderAction: renderWithRedBadge,
});
export const WithToolboxContext = () => {
const icon = useRoomIcon(room);
const avatar = <RoomAvatar room={room}/>;
return <SettingsContext.Provider value={settingContextValue}>
<Header>
<Header.Avatar>{avatar}</Header.Avatar>
<Header.Content>
<Header.Content.Row>
{ icon && <Header.Icon icon={icon}/> }
<Header.Title>{room.name}</Header.Title>
<Header.State onClick icon='star'/>
<Header.State icon='key'/>
<Header.State icon='language'/>
</Header.Content.Row>
<Header.Content.Row>
<Header.Subtitle>{room.name}</Header.Subtitle>
</Header.Content.Row>
</Header.Content>
<Header.ToolBox>
<ToolboxProvider room={toolboxRoom}>
<ToolBox />
</ToolboxProvider>
</Header.ToolBox>
</Header>
</SettingsContext.Provider>;
};

@ -0,0 +1,69 @@
import React, { FC } from 'react';
import { Box, Icon, Divider, ButtonGroup, ActionButton, Badge, BadgeProps } from '@rocket.chat/fuselage';
import { css } from '@rocket.chat/css-in-js';
const Title: FC = (props: any) => <Box color='default' mi='x4' fontScale='s2' withTruncatedText {...props}/>;
const Subtitle: FC = (props: any) => <Box color='hint' fontScale='p1' withTruncatedText {...props}/>;
const Row: FC = (props: any) => <Box alignItems='center' flexShrink={1} flexGrow={1} display='flex' {...props}/>;
const HeaderIcon: FC<{ icon: JSX.Element | { name: string; color?: string } | null}> = ({ icon }) => icon && <Box display='flex' flexShrink={0} alignItems='center' size={18} overflow='hidden' justifyContent='center'>{React.isValidElement(icon) ? icon : <Icon color='info' size='x18' { ...{ name: (icon as any).name }} />}</Box>;
const ToolBox: FC = (props: any) => <ButtonGroup small {...props}/>;
const ToolBoxAction: FC = ({ id, icon, title, action, className, tabId, index, ...props }: any) => <ActionButton
className={className}
primary={tabId === id}
onClick={action}
title={title}
data-toolbox={index}
key={id}
icon={icon}
position='relative'
ghost
tiny
overflow='visible'
{...props}
/>;
const ToolBoxActionBadge: FC<BadgeProps> = (props) => <Box position='absolute' className={css`top: 0; right: 0; transform: translate(30%, -30%);`}><Badge {...props}/></Box>;
const State: FC = (props: any) => (props.onClick ? <ActionButton ghost mini {...props}/> : <Icon size={16} name={props.icon} {...props}/>);
const Content: FC = (props: any) => <Box flexGrow={1} width={1} flexShrink={1} mi='x4' display='flex' justifyContent='center' flexDirection='column' {...props}/>;
const Button: FC = (props: any) => <Box mi='x4' display='flex' alignItems='center' {...props}/>;
const Avatar: FC = (props: any) => <Button width='x36' {...props}/>;
const HeaderDivider: FC = () => <Divider { ...{ mbs: 'neg-x2', mbe: 0 } as any} />;
const Header: FC & { ToolBoxAction: FC; Badge: FC<BadgeProps> } = (props: any) => <Box rcx-room-header is='header' height='x64' display='flex' justifyContent='center' flexDirection='column' overflow='hidden' flexShrink={0}>
<Box height='x64' pi='x24' display='flex' flexGrow={1} justifyContent='center' alignItems='center' overflow='hidden' flexDirection='row' {...props}/>
<HeaderDivider/>
</Box>;
Header.ToolBoxAction = ToolBoxAction;
Header.Badge = ToolBoxActionBadge;
export default Header;
Object.assign(Content, {
Row,
});
Object.assign(ToolBoxAction, {
Badge: ToolBoxActionBadge,
});
Object.assign(Header, {
Button,
State,
Avatar,
Content,
Title,
Subtitle,
ToolBox,
ToolBoxAction,
Divider: HeaderDivider,
Icon: HeaderIcon,
});

@ -0,0 +1,3 @@
import Header from './Header';
export default Header;

@ -1,19 +1,22 @@
import React, { FC } from 'react';
import React, { CSSProperties, FC } from 'react';
import SimpleBar from 'simplebar-react';
import 'simplebar/src/simplebar.css';
import { useDir } from '../hooks/useDir';
const style = {
const styleDefault = {
maxHeight: '100%', flexGrow: 1,
};
type CustomScrollbarsProps = {
onScroll?: Function;
ref: React.Ref<unknown>;
style?: CSSProperties;
}
const ScrollableContentWrapper: FC<CustomScrollbarsProps> = React.memo(React.forwardRef(({ onScroll, children }, ref) => {
const ScrollableContentWrapper: FC<CustomScrollbarsProps> = React.memo(React.forwardRef(({ onScroll, children, style = styleDefault }, ref) => {
const dir = useDir();
return <SimpleBar data-simplebar-direction={dir} direction={dir} style={style} scrollableNodeProps={{ ref, onScroll }} children={children}/>;
}));

@ -1,51 +1,79 @@
import { Box, Button, Icon, Margins, Skeleton } from '@rocket.chat/fuselage';
import { useDebouncedValue, useMediaQuery } from '@rocket.chat/fuselage-hooks';
import { Box, Button, ActionButton, ButtonGroup, Icon, Margins, Skeleton } from '@rocket.chat/fuselage';
import React from 'react';
import { useLayoutContextualBarPosition, useLayoutSizes } from '../providers/LayoutProvider';
import { useTranslation } from '../contexts/TranslationContext';
import Page from './Page';
function VerticalBar({ children, ...props }) {
const mobile = useDebouncedValue(useMediaQuery('(max-width: 500px)'), 50);
const small = useDebouncedValue(useMediaQuery('(max-width: 780px)'), 50);
function VerticalBar({ children, ...props }) {
const sizes = useLayoutSizes();
const position = useLayoutContextualBarPosition();
return <Box
rcx-vertical-bar
backgroundColor='surface'
display='flex'
flexDirection='column'
flexShrink={0}
width={mobile ? 'full' : 'x380'}
width={sizes.contextualBar}
borderInlineStartWidth='2px'
borderInlineStartColor='neutral-300'
borderInlineStartStyle='solid'
height='full'
position={small ? 'absolute' : undefined}
insetInlineEnd={small ? 'none' : undefined}
position={position}
zIndex={5}
insetInlineEnd={'none'}
insetBlockStart={'none'}
{...props}
>
{children}
</Box>;
}
const style = {
left: '0',
right: '0',
};
function VerticalBarInnerContent(props) {
return <Box
rcx-vertical-bar--inner-content
position='absolute'
height='full'
display='flex'
style={style}
{...props}
/>;
}
function VerticalBarHeader({ children, ...props }) {
return <Box
is='h3'
pb='x24'
pi='x24'
height='64px'
display='flex'
alignItems='center'
justifyContent='space-between'
borderBlockColor='neutral-200'
minHeight='56px'
maxHeight='56px'
is='h3'
pi='x24'
borderBlockEndWidth='x2'
fontScale='s2'
color='neutral-800'
borderBlockColor='neutral-200'
{...props}
>
<Margins inline='x4'>{children}</Margins>
<Box
marginInline='neg-x4'
display='flex'
alignItems='center'
justifyContent='space-between'
fontScale='s2'
flexGrow={1}
overflow='hidden'
color='neutral-800'>
<Margins inline='x4'>{children}</Margins>
</Box>
</Box>;
}
function VerticalBarIcon(props) {
return <Icon {...props} size='x24'/>;
return <Icon {...props} pi='x2' size='x24'/>;
}
function VerticalBarClose(props) {
@ -54,7 +82,7 @@ function VerticalBarClose(props) {
}
const VerticalBarContent = React.forwardRef(function VerticalBarContent(props, ref) {
return <Page.Content display='flex' {...props} ref={ref}/>;
return <Page.Content rcx-vertical-bar__content display='flex' {...props} ref={ref}/>;
});
const VerticalBarScrollableContent = React.forwardRef(function VerticalBarScrollableContent({ children, ...props }, ref) {
@ -74,15 +102,19 @@ function VerticalBarButton(props) {
}
function VerticalBarAction({ name, ...props }) {
return <VerticalBarButton small square flexShrink={0} ghost {...props}><VerticalBarIcon name={name}/></VerticalBarButton>;
return <ActionButton flexShrink={0} icon={name} ghost {...props} tiny />;
}
function VerticalBarActions(props) {
return <ButtonGroup small {...props} />;
}
function VerticalBarActionBack(props) {
return <VerticalBarAction {...props} tiny name='arrow-back' />;
return <VerticalBarAction {...props} name='arrow-back' />;
}
function VerticalBarSkeleton(props) {
return <VerticalBar { ...props }>
return <VerticalBar { ...props } width='100%'>
<VerticalBarHeader><Skeleton width='100%'/></VerticalBarHeader>
<Box p='x24'>
<Skeleton width='32px' height='32px' variant='rect'/> <Skeleton />
@ -95,10 +127,12 @@ function VerticalBarText(props) {
return <Box flexShrink={1} flexGrow={1} withTruncatedText {...props}/>;
}
VerticalBar.InnerContent = React.memo(VerticalBarInnerContent);
VerticalBar.Icon = React.memo(VerticalBarIcon);
VerticalBar.Footer = React.memo(VerticalBarFooter);
VerticalBar.Text = React.memo(VerticalBarText);
VerticalBar.Action = React.memo(VerticalBarAction);
VerticalBar.Actions = React.memo(VerticalBarActions);
VerticalBar.Header = React.memo(VerticalBarHeader);
VerticalBar.Close = React.memo(VerticalBarClose);
VerticalBar.Content = React.memo(VerticalBarContent);

@ -1,6 +1,6 @@
import { createContext, useContext } from 'react';
const dummy = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=';
const dummy = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2Oora39DwAFaQJ3y3rKeAAAAABJRU5ErkJggg==';
type AvatarContextValue = {
getUserPathAvatar: (uid: string, etag?: string) => string;

@ -0,0 +1,32 @@
import { createContext, useContext } from 'react';
export type SizeLayout = {
sidebar: string;
contextualBar: string;
}
export type LayoutContextValue = {
isEmbedded: boolean;
showTopNavbarEmbeddedLayout: boolean;
isMobile: boolean;
sidebar: any;
size: SizeLayout;
contextualBarExpanded: boolean;
contextualBarPosition: 'absolute' | 'relative' | 'fixed';
}
export const LayoutContext = createContext<LayoutContextValue>({
isEmbedded: false,
showTopNavbarEmbeddedLayout: false,
isMobile: false,
sidebar: {},
size: {
sidebar: '380px',
contextualBar: '380px',
},
contextualBarPosition: 'relative',
contextualBarExpanded: false,
});
export const useLayout = (): LayoutContextValue =>
useContext(LayoutContext);

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save