diff --git a/packages/rocketchat-channel-settings/client/views/channelSettings.js b/packages/rocketchat-channel-settings/client/views/channelSettings.js
index 0753e1256228..0a20941fe5bc 100644
--- a/packages/rocketchat-channel-settings/client/views/channelSettings.js
+++ b/packages/rocketchat-channel-settings/client/views/channelSettings.js
@@ -225,7 +225,7 @@ Template.channelSettingsEditing.onCreated(function() {
return RocketChat.roomTypes.roomTypes[room.t].allowRoomSettingChange(room, RoomSettingsEnum.READ_ONLY);
},
canEdit() {
- return RocketChat.authz.hasAllPermission('set-readonly', room._id);
+ return !room.broadcast && RocketChat.authz.hasAllPermission('set-readonly', room._id);
},
save(value) {
return call('saveRoomSettings', room._id, RoomSettingsEnum.READ_ONLY, value).then(() => toastr.success(TAPi18n.__('Read_only_changed_successfully')));
@@ -237,10 +237,10 @@ Template.channelSettingsEditing.onCreated(function() {
isToggle: true,
processing: new ReactiveVar(false),
canView() {
- return RocketChat.roomTypes.roomTypes[room.t].allowRoomSettingChange(room, RoomSettingsEnum.REACT_WHEN_READ_ONLY) && room.ro;
+ return RocketChat.roomTypes.roomTypes[room.t].allowRoomSettingChange(room, RoomSettingsEnum.REACT_WHEN_READ_ONLY);
},
canEdit() {
- return RocketChat.authz.hasAllPermission('set-react-when-readonly', room._id);
+ return !room.broadcast && RocketChat.authz.hasAllPermission('set-react-when-readonly', room._id);
},
save(value) {
return call('saveRoomSettings', room._id, 'reactWhenReadOnly', value).then(() => {
@@ -289,6 +289,21 @@ Template.channelSettingsEditing.onCreated(function() {
});
}
},
+ broadcast: {
+ type: 'boolean',
+ label: 'Broadcast_channel',
+ isToggle: true,
+ processing: new ReactiveVar(false),
+ canView() {
+ return RocketChat.roomTypes.roomTypes[room.t].allowRoomSettingChange(room, RoomSettingsEnum.BROADCAST);
+ },
+ canEdit() {
+ return false;
+ },
+ save() {
+ return Promise.resolve();
+ }
+ },
joinCode: {
type: 'text',
label: 'Password',
@@ -452,6 +467,9 @@ Template.channelSettingsInfo.helpers({
description() {
return Template.instance().room.description;
},
+ broadcast() {
+ return Template.instance().room.broadcast;
+ },
announcement() {
return Template.instance().room.announcement ? Template.instance().room.announcement.message : '';
},
diff --git a/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.js b/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.js
index 5ccdced2aaa3..dab07dd47f52 100644
--- a/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.js
+++ b/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.js
@@ -31,8 +31,15 @@ Meteor.methods({
});
}
-
const room = RocketChat.models.Rooms.findOneById(rid);
+
+ if (room.broadcast && (settings.readOnly || settings.reactWhenReadOnly)) {
+ throw new Meteor.Error('error-action-not-allowed', 'Editing readOnly/reactWhenReadOnly are not allowed for broadcast rooms', {
+ method: 'saveRoomSettings',
+ action: 'Editing_room'
+ });
+ }
+
if (!room) {
throw new Meteor.Error('error-invalid-room', 'Invalid room', {
method: 'saveRoomSettings'
diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json
index 6d3a1ff4ea6e..d7d4609d42db 100644
--- a/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -267,6 +267,7 @@
"Application_added": "Application added",
"Application_Name": "Application Name",
"Application_updated": "Application updated",
+ "Apply": "Apply",
"Apply_and_refresh_all_clients": "Apply and refresh all clients",
"Archive": "Archive",
"archive-room": "Archive Room",
@@ -343,6 +344,8 @@
"BotHelpers_userFields": "User Fields",
"BotHelpers_userFields_Description": "CSV of user fields that can be accessed by bots helper methods.",
"Branch": "Branch",
+ "Broadcast_channel": "Broadcast Channel",
+ "Broadcast_channel_Description": "Only authorized users can write new messages, but the other users will be able to reply",
"Broadcast_Connected_Instances": "Broadcast Connected Instances",
"Bugsnag_api_key": "Bugsnag API Key",
"Build_Environment": "Build Environment",
diff --git a/packages/rocketchat-lib/client/defaultTabBars.js b/packages/rocketchat-lib/client/defaultTabBars.js
index a323397a7873..2b184de32f9c 100644
--- a/packages/rocketchat-lib/client/defaultTabBars.js
+++ b/packages/rocketchat-lib/client/defaultTabBars.js
@@ -22,7 +22,19 @@ RocketChat.TabBar.addButton({
i18nTitle: 'Members_List',
icon: 'team',
template: 'membersList',
- order: 2
+ order: 2,
+ condition() {
+ const rid = Session.get('openedRoom');
+ const room = RocketChat.models.Rooms.findOne({
+ _id: rid
+ });
+
+ if (!room || !room.broadcast) {
+ return true;
+ }
+
+ return RocketChat.authz.hasRole(Meteor.userId(), ['admin', 'moderator', 'owner'], rid);
+ }
});
RocketChat.TabBar.addButton({
diff --git a/packages/rocketchat-lib/lib/RoomTypeConfig.js b/packages/rocketchat-lib/lib/RoomTypeConfig.js
index 43027c05dada..20bb932467b4 100644
--- a/packages/rocketchat-lib/lib/RoomTypeConfig.js
+++ b/packages/rocketchat-lib/lib/RoomTypeConfig.js
@@ -6,7 +6,8 @@ export const RoomSettingsEnum = {
READ_ONLY: 'readOnly',
REACT_WHEN_READ_ONLY: 'reactWhenReadOnly',
ARCHIVE_OR_UNARCHIVE: 'archiveOrUnarchive',
- JOIN_CODE: 'joinCode'
+ JOIN_CODE: 'joinCode',
+ BROADCAST: 'broadcast'
};
export const UiTextContext = {
diff --git a/packages/rocketchat-lib/lib/roomTypes/private.js b/packages/rocketchat-lib/lib/roomTypes/private.js
index 5522d95a8110..e887fa392102 100644
--- a/packages/rocketchat-lib/lib/roomTypes/private.js
+++ b/packages/rocketchat-lib/lib/roomTypes/private.js
@@ -60,6 +60,12 @@ export class PrivateRoomType extends RoomTypeConfig {
switch (setting) {
case RoomSettingsEnum.JOIN_CODE:
return false;
+ case RoomSettingsEnum.BROADCAST:
+ return room.broadcast;
+ case RoomSettingsEnum.READ_ONLY:
+ return !room.broadcast;
+ case RoomSettingsEnum.REACT_WHEN_READ_ONLY:
+ return !room.broadcast && room.ro;
default:
return true;
}
diff --git a/packages/rocketchat-lib/lib/roomTypes/public.js b/packages/rocketchat-lib/lib/roomTypes/public.js
index fb9c9e277ad3..8569b4a7c508 100644
--- a/packages/rocketchat-lib/lib/roomTypes/public.js
+++ b/packages/rocketchat-lib/lib/roomTypes/public.js
@@ -1,5 +1,5 @@
/* globals openRoom */
-import {RoomTypeConfig, RoomTypeRouteConfig, UiTextContext} from '../RoomTypeConfig';
+import { RoomTypeConfig, RoomTypeRouteConfig, RoomSettingsEnum, UiTextContext } from '../RoomTypeConfig';
export class PublicRoomRoute extends RoomTypeRouteConfig {
constructor() {
@@ -63,12 +63,21 @@ export class PublicRoomType extends RoomTypeConfig {
return RocketChat.authz.hasAtLeastOnePermission(['add-user-to-any-c-room', 'add-user-to-joined-room'], room._id);
}
- allowRoomSettingChange() {
+ enableMembersListProfile() {
return true;
}
- enableMembersListProfile() {
- return true;
+ allowRoomSettingChange(room, setting) {
+ switch (setting) {
+ case RoomSettingsEnum.BROADCAST:
+ return room.broadcast;
+ case RoomSettingsEnum.READ_ONLY:
+ return !room.broadcast;
+ case RoomSettingsEnum.REACT_WHEN_READ_ONLY:
+ return !room.broadcast && room.ro;
+ default:
+ return true;
+ }
}
getUiText(context) {
diff --git a/packages/rocketchat-lib/server/functions/createRoom.js b/packages/rocketchat-lib/server/functions/createRoom.js
index 5a3052429dfd..3c969410c930 100644
--- a/packages/rocketchat-lib/server/functions/createRoom.js
+++ b/packages/rocketchat-lib/server/functions/createRoom.js
@@ -20,6 +20,11 @@ RocketChat.createRoom = function(type, name, owner, members, readOnly, extraData
members.push(owner.username);
}
+ if (extraData.broadcast) {
+ readOnly = true;
+ delete extraData.reactWhenReadOnly;
+ }
+
const now = new Date();
let room = Object.assign({
name,
diff --git a/packages/rocketchat-lib/server/methods/createChannel.js b/packages/rocketchat-lib/server/methods/createChannel.js
index 1515ecd3f228..e40b06d4f727 100644
--- a/packages/rocketchat-lib/server/methods/createChannel.js
+++ b/packages/rocketchat-lib/server/methods/createChannel.js
@@ -1,5 +1,5 @@
Meteor.methods({
- createChannel(name, members, readOnly = false, customFields = {}) {
+ createChannel(name, members, readOnly = false, customFields = {}, extraData = {}) {
check(name, String);
check(members, Match.Optional([String]));
@@ -10,7 +10,6 @@ Meteor.methods({
if (!RocketChat.authz.hasPermission(Meteor.userId(), 'create-c')) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'createChannel' });
}
-
- return RocketChat.createRoom('c', name, Meteor.user() && Meteor.user().username, members, readOnly, {customFields});
+ return RocketChat.createRoom('c', name, Meteor.user() && Meteor.user().username, members, readOnly, {customFields, ...extraData});
}
});
diff --git a/packages/rocketchat-theme/client/imports/components/header.css b/packages/rocketchat-theme/client/imports/components/header.css
index b5d7e6cf847b..b05a45e848cd 100644
--- a/packages/rocketchat-theme/client/imports/components/header.css
+++ b/packages/rocketchat-theme/client/imports/components/header.css
@@ -9,6 +9,10 @@
margin: 0 -0.5rem;
+
+ display: flex;
+ flex: 0 0 auto;
+
padding: var(--header-padding);
white-space: nowrap;
diff --git a/packages/rocketchat-theme/client/imports/forms/button.css b/packages/rocketchat-theme/client/imports/forms/button.css
index b23df6a93aab..f87d596d3830 100644
--- a/packages/rocketchat-theme/client/imports/forms/button.css
+++ b/packages/rocketchat-theme/client/imports/forms/button.css
@@ -140,6 +140,15 @@
border-radius: 50%;
}
}
+
+ &-broadcast {
+ margin: 10px 0;
+ padding: 0 1rem;
+
+ &__icon {
+ margin: 0 5px;
+ }
+ }
}
@media (width < 780px) {
diff --git a/packages/rocketchat-theme/client/imports/forms/input.css b/packages/rocketchat-theme/client/imports/forms/input.css
index 30e6a5c1dd56..ded7b0869ba8 100644
--- a/packages/rocketchat-theme/client/imports/forms/input.css
+++ b/packages/rocketchat-theme/client/imports/forms/input.css
@@ -112,6 +112,9 @@
}
&--error {
+ .rc-tags {
+ border-color: var(--input-error-color);
+ }
& .rc-input {
&__element {
border-color: var(--input-error-color);
diff --git a/packages/rocketchat-theme/client/imports/forms/popup-list.css b/packages/rocketchat-theme/client/imports/forms/popup-list.css
index 0d0efef10fa3..41ec0e51bdb2 100644
--- a/packages/rocketchat-theme/client/imports/forms/popup-list.css
+++ b/packages/rocketchat-theme/client/imports/forms/popup-list.css
@@ -1,6 +1,8 @@
.rc-popup-list {
position: absolute;
+ z-index: 1;
+
width: 100%;
padding: 0 4px;
diff --git a/packages/rocketchat-theme/client/imports/forms/switch.css b/packages/rocketchat-theme/client/imports/forms/switch.css
index 503f7cceb0c7..aa614e9f0a96 100644
--- a/packages/rocketchat-theme/client/imports/forms/switch.css
+++ b/packages/rocketchat-theme/client/imports/forms/switch.css
@@ -28,6 +28,17 @@
&__input {
display: none;
+ &:checked {
+ & + .rc-switch__button {
+ border-color: #26d198;
+ background-color: var(--rc-color-success);
+
+ & .rc-switch__button-inside {
+ transform: translate3d(13px, 1px, 0);
+ }
+ }
+ }
+
&:disabled {
& + .rc-switch__button {
cursor: default;
@@ -40,17 +51,6 @@
cursor: default;
}
}
-
- &:checked {
- & + .rc-switch__button {
- border-color: #26d198;
- background-color: var(--rc-color-success);
-
- & .rc-switch__button-inside {
- transform: translate3d(13px, 1px, 0);
- }
- }
- }
}
&__button {
diff --git a/packages/rocketchat-ui-master/public/icons.svg b/packages/rocketchat-ui-master/public/icons.svg
index 7b2b722e07e7..db48fee615e3 100644
--- a/packages/rocketchat-ui-master/public/icons.svg
+++ b/packages/rocketchat-ui-master/public/icons.svg
@@ -99,6 +99,8 @@
+
+
diff --git a/packages/rocketchat-ui-message/client/message.html b/packages/rocketchat-ui-message/client/message.html
index bc14740efc01..c5dd433820c2 100644
--- a/packages/rocketchat-ui-message/client/message.html
+++ b/packages/rocketchat-ui-message/client/message.html
@@ -111,6 +111,11 @@
{{/each}}
+ {{# if broadcast}}
+ {{#with u}}
+
+ {{/with}}
+ {{/if}}
{{#each reaction in reactions}}
-
diff --git a/packages/rocketchat-ui-message/client/message.js b/packages/rocketchat-ui-message/client/message.js
index 14e376b2f842..86c1382da62f 100644
--- a/packages/rocketchat-ui-message/client/message.js
+++ b/packages/rocketchat-ui-message/client/message.js
@@ -6,6 +6,10 @@ Template.message.helpers({
encodeURI(text) {
return encodeURI(text);
},
+ broadcast() {
+ const instance = Template.instance();
+ return this.u._id !== Meteor.userId() && instance.room && instance.room.broadcast;
+ },
isIgnored() {
return this.ignored;
},
@@ -48,12 +52,12 @@ Template.message.helpers({
});
},
isGroupable() {
- if (this.groupable === false) {
+ if (Template.instance().room.broadcast || this.groupable === false) {
return 'false';
}
},
isSequential() {
- return this.groupable !== false;
+ return this.groupable !== false && !Template.instance().room.broadcast;
},
sequentialClass() {
if (this.groupable !== false) {
@@ -316,6 +320,14 @@ Template.message.onCreated(function() {
this.wasEdited = (msg.editedAt != null) && !RocketChat.MessageTypes.isSystemMessage(msg);
+ this.room = RocketChat.models.Rooms.findOne({
+ _id: msg.rid
+ }, {
+ fields: {
+ broadcast: 1
+ }
+ });
+
return this.body = (() => {
const isSystemMessage = RocketChat.MessageTypes.isSystemMessage(msg);
const messageType = RocketChat.MessageTypes.getType(msg)||{};
diff --git a/packages/rocketchat-ui-message/client/messageBox.js b/packages/rocketchat-ui-message/client/messageBox.js
index 3f7d1e0806ad..fa0d273c1f95 100644
--- a/packages/rocketchat-ui-message/client/messageBox.js
+++ b/packages/rocketchat-ui-message/client/messageBox.js
@@ -649,8 +649,6 @@ Template.messageBox.onRendered(function() {
}).on('autogrow', () => {
this.data && this.data.onResize && this.data.onResize();
}).focus()[0];
-
- chatMessages[RocketChat.openedRoom].restoreText(RocketChat.openedRoom);
});
Template.messageBox.onCreated(function() {
@@ -685,6 +683,7 @@ Meteor.startup(function() {
setTimeout(()=> {
if (chatMessages[RocketChat.openedRoom].input) {
chatMessages[RocketChat.openedRoom].input.focus();
+ chatMessages[RocketChat.openedRoom].restoreText(RocketChat.openedRoom);
}
}, 200);
});
diff --git a/packages/rocketchat-ui/client/components/popupList.html b/packages/rocketchat-ui/client/components/popupList.html
index ce6e0f77a7e0..6374a40c1500 100644
--- a/packages/rocketchat-ui/client/components/popupList.html
+++ b/packages/rocketchat-ui/client/components/popupList.html
@@ -26,3 +26,12 @@
{{/if}}
+
+
+
+
diff --git a/packages/rocketchat-ui/client/lib/chatMessages.js b/packages/rocketchat-ui/client/lib/chatMessages.js
index a153a2e31c50..98a7eeb4e0bf 100644
--- a/packages/rocketchat-ui/client/lib/chatMessages.js
+++ b/packages/rocketchat-ui/client/lib/chatMessages.js
@@ -2,6 +2,9 @@
import s from 'underscore.string';
import moment from 'moment';
import toastr from 'toastr';
+
+const reply = id => id && `[ ](${ RocketChat.MessageAction.getPermaLink(id) }) `;
+
this.ChatMessages = class ChatMessages {
init(node) {
this.editing = {};
@@ -399,7 +402,7 @@ this.ChatMessages = class ChatMessages {
}
restoreText(rid) {
- const text = localStorage.getItem(`messagebox_${ rid }`);
+ const text = reply(FlowRouter.getQueryParam('reply')) || localStorage.getItem(`messagebox_${ rid }`);
if (typeof text === 'string' && this.input) {
this.input.value = text;
}
diff --git a/packages/rocketchat-ui/client/views/app/createChannel.html b/packages/rocketchat-ui/client/views/app/createChannel.html
index 027bc8393d65..27e9ada94696 100644
--- a/packages/rocketchat-ui/client/views/app/createChannel.html
+++ b/packages/rocketchat-ui/client/views/app/createChannel.html
@@ -26,7 +26,7 @@ {{_ "Create_A_New_Channel"}}