diff --git a/packages/rocketchat-analytics/client/trackEvents.js b/packages/rocketchat-analytics/client/trackEvents.js index c672e8bb73d5..12056bedfaba 100644 --- a/packages/rocketchat-analytics/client/trackEvents.js +++ b/packages/rocketchat-analytics/client/trackEvents.js @@ -54,6 +54,12 @@ if (!window._paq || window.ga) { } }, RocketChat.callbacks.priority.MEDIUM, 'analytics-room-topic-changed'); + RocketChat.callbacks.add('roomAnnouncementChanged', (room) => { + if (RocketChat.settings.get('Analytics_features_rooms')) { + trackEvent('Room', 'Changed Announcement', room.name + ' (' + room._id + ')'); + } + }, RocketChat.callbacks.priority.MEDIUM, 'analytics-room-announcement-changed'); + RocketChat.callbacks.add('roomTypeChanged', (room) => { if (RocketChat.settings.get('Analytics_features_rooms')) { trackEvent('Room', 'Changed Room Type', room.name + ' (' + room._id + ')'); diff --git a/packages/rocketchat-channel-settings/client/startup/messageTypes.coffee b/packages/rocketchat-channel-settings/client/startup/messageTypes.coffee index 6d34cd8c9397..7911c68604bc 100644 --- a/packages/rocketchat-channel-settings/client/startup/messageTypes.coffee +++ b/packages/rocketchat-channel-settings/client/startup/messageTypes.coffee @@ -19,6 +19,16 @@ Meteor.startup -> room_topic: message.msg } + RocketChat.MessageTypes.registerType + id: 'room_changed_announcement' + system: true + message: 'room_changed_announcement' + data: (message) -> + return { + user_by: message.u?.username + room_announcement: message.msg + } + RocketChat.MessageTypes.registerType id: 'room_changed_description' system: true diff --git a/packages/rocketchat-channel-settings/client/views/channelSettings.coffee b/packages/rocketchat-channel-settings/client/views/channelSettings.coffee index 5d37e615521b..8939671627c6 100644 --- a/packages/rocketchat-channel-settings/client/views/channelSettings.coffee +++ b/packages/rocketchat-channel-settings/client/views/channelSettings.coffee @@ -145,6 +145,17 @@ Template.channelSettings.onCreated -> toastr.success TAPi18n.__ 'Room_topic_changed_successfully' RocketChat.callbacks.run 'roomTopicChanged', room + announcement: + type: 'markdown' + label: 'Announcement' + canView: (room) => true + canEdit: (room) => RocketChat.authz.hasAllPermission('edit-room', room._id) + save: (value, room) -> + Meteor.call 'saveRoomSettings', room._id, 'roomAnnouncement', value, (err, result) -> + return handleError err if err + toastr.success TAPi18n.__ 'Room_announcement_changed_successfully' + RocketChat.callbacks.run 'roomAnnouncementChanged', room + description: type: 'text' label: 'Description' diff --git a/packages/rocketchat-channel-settings/package.js b/packages/rocketchat-channel-settings/package.js index 3f42e734af3f..6bfe2c0b87b9 100644 --- a/packages/rocketchat-channel-settings/package.js +++ b/packages/rocketchat-channel-settings/package.js @@ -30,6 +30,7 @@ Package.onUse(function(api) { 'server/functions/saveReactWhenReadOnly.js', 'server/functions/saveRoomType.coffee', 'server/functions/saveRoomTopic.coffee', + 'server/functions/saveRoomAnnouncement.js', 'server/functions/saveRoomName.coffee', 'server/functions/saveRoomReadOnly.coffee', 'server/functions/saveRoomDescription.coffee', diff --git a/packages/rocketchat-channel-settings/server/functions/saveRoomAnnouncement.js b/packages/rocketchat-channel-settings/server/functions/saveRoomAnnouncement.js new file mode 100644 index 000000000000..e78663e9b6b7 --- /dev/null +++ b/packages/rocketchat-channel-settings/server/functions/saveRoomAnnouncement.js @@ -0,0 +1,13 @@ +RocketChat.saveRoomAnnouncement = function(rid, roomAnnouncement, user, sendMessage=true) { + if (!Match.test(rid, String)) { + throw new Meteor.Error('invalid-room', 'Invalid room', { function: 'RocketChat.saveRoomAnnouncement' }); + } + + roomAnnouncement = s.escapeHTML(roomAnnouncement); + const updated = RocketChat.models.Rooms.setAnnouncementById(rid, roomAnnouncement); + if (updated && sendMessage) { + RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser('room_changed_announcement', rid, roomAnnouncement, user); + } + + return updated; +}; diff --git a/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.coffee b/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.coffee index f51a06949452..ee85e7950767 100644 --- a/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.coffee +++ b/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.coffee @@ -6,7 +6,7 @@ Meteor.methods unless Match.test rid, String throw new Meteor.Error 'error-invalid-room', 'Invalid room', { method: 'saveRoomSettings' } - if setting not in ['roomName', 'roomTopic', 'roomDescription', 'roomType', 'readOnly', 'reactWhenReadOnly', 'systemMessages', 'default', 'joinCode'] + if setting not in ['roomName', 'roomTopic', 'roomAnnouncement', 'roomDescription', 'roomType', 'readOnly', 'reactWhenReadOnly', 'systemMessages', 'default', 'joinCode'] throw new Meteor.Error 'error-invalid-settings', 'Invalid settings provided', { method: 'saveRoomSettings' } unless RocketChat.authz.hasPermission(Meteor.userId(), 'edit-room', rid) @@ -29,6 +29,9 @@ Meteor.methods when 'roomTopic' if value isnt room.topic RocketChat.saveRoomTopic(rid, value, Meteor.user()) + when 'roomAnnouncement' + if value isnt room.announcement + RocketChat.saveRoomAnnouncement(rid, value, Meteor.user()) when 'roomDescription' if value isnt room.description RocketChat.saveRoomDescription rid, value, Meteor.user() diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 733b6dfa4ae8..1be49c0ce93b 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -161,6 +161,7 @@ "and": "and", "And_more": "And __length__ more", "Animals_and_Nature": "Animals & Nature", + "Announcement": "Announcement", "API": "API", "API_Allow_Infinite_Count": "Allow Getting Everything", "API_Allow_Infinite_Count_Description": "Should calls to the REST API be allowed to return everything in one call?", @@ -1224,10 +1225,12 @@ "Role_Editing": "Role Editing", "Role_removed": "Role removed", "Room": "Room", + "Room_announcement_changed_successfully": "Room announcement changed successfully", "Room_archivation_state": "State", "Room_archivation_state_false": "Active", "Room_archivation_state_true": "Archived", "Room_archived": "Room archived", + "room_changed_announcement": "Room announcement changed to: __room_announcement__ by __user_by__", "room_changed_description": "Room description changed to: __room_description__ by __user_by__", "room_changed_privacy": "Room type changed to: __room_type__ by __user_by__", "room_changed_topic": "Room topic changed to: __room_topic__ by __user_by__", diff --git a/packages/rocketchat-i18n/i18n/zh.i18n.json b/packages/rocketchat-i18n/i18n/zh.i18n.json index 145471928d18..b368a9701b4a 100644 --- a/packages/rocketchat-i18n/i18n/zh.i18n.json +++ b/packages/rocketchat-i18n/i18n/zh.i18n.json @@ -136,6 +136,7 @@ "and": "和", "And_more": "以及其它 __length__ 条", "Animals_and_Nature": "动物与自然", + "Announcement": "公告", "API": "API", "API_Analytics": "分析", "API_Embed": "嵌入", @@ -911,10 +912,12 @@ "Role_Editing": "编辑角色", "Role_removed": "角色已删除", "Room": "聊天室", + "Room_announcement_changed_successfully": "公告已成功修改", "Room_archivation_state": "状态", "Room_archivation_state_false": "活跃", "Room_archivation_state_true": "已归档", "Room_archived": "房间已归档", + "room_changed_announcement": "__user_by__ 将公告修改为:__room_announcement__", "room_changed_description": "房间描述由 __user_by__ 修改为:__room_description__", "room_changed_privacy": "__user_by__ 将房间类型修改为:__room_type__", "room_changed_topic": "__user_by__ 将房间主题修改为:__room_topic__", diff --git a/packages/rocketchat-lib/server/models/Rooms.coffee b/packages/rocketchat-lib/server/models/Rooms.coffee index 0830b53f773d..928dde42caa8 100644 --- a/packages/rocketchat-lib/server/models/Rooms.coffee +++ b/packages/rocketchat-lib/server/models/Rooms.coffee @@ -499,6 +499,16 @@ class ModelRooms extends RocketChat.models._Base return @update query, update + setAnnouncementById: (_id, announcement) -> + query = + _id: _id + + update = + $set: + announcement: announcement + + return @update query, update + muteUsernameByRoomId: (_id, username) -> query = _id: _id diff --git a/packages/rocketchat-theme/client/imports/base.less b/packages/rocketchat-theme/client/imports/base.less index 43c592daf016..97985515451f 100644 --- a/packages/rocketchat-theme/client/imports/base.less +++ b/packages/rocketchat-theme/client/imports/base.less @@ -1776,6 +1776,26 @@ label.required::after { } } +.announcement { + margin-top: 61px; + height: 40px; + line-height: 40px; + overflow: hidden; + font-size: 1.2em; + background-color: #04436a; + color: white; + text-align: center; + display: block; + + ~ .container-bars { + top: 95px; + } + + ~ .messages-box { + margin-top: 100px; + } +} + .cms-page { max-width: 800px; margin: 40px auto; diff --git a/packages/rocketchat-ui-admin/client/rooms/adminRoomInfo.coffee b/packages/rocketchat-ui-admin/client/rooms/adminRoomInfo.coffee index 62cda0ec7042..de3cea04e2db 100644 --- a/packages/rocketchat-ui-admin/client/rooms/adminRoomInfo.coffee +++ b/packages/rocketchat-ui-admin/client/rooms/adminRoomInfo.coffee @@ -137,6 +137,13 @@ Template.adminRoomInfo.onCreated -> return handleError(err) toastr.success TAPi18n.__ 'Room_topic_changed_successfully' RocketChat.callbacks.run 'roomTopicChanged', AdminChatRoom.findOne(rid) + when 'roomAnnouncement' + if @validateRoomTopic(rid) + Meteor.call 'saveRoomSettings', rid, 'roomAnnouncement', @$('input[name=roomAnnouncement]').val(), (err, result) -> + if err + return handleError(err) + toastr.success TAPi18n.__ 'Room_announcement_changed_successfully' + RocketChat.callbacks.run 'roomAnnouncementChanged', AdminChatRoom.findOne(rid) when 'roomType' if @validateRoomType(rid) RocketChat.callbacks.run 'roomTypeChanged', AdminChatRoom.findOne(rid) diff --git a/packages/rocketchat-ui/client/lib/chatMessages.coffee b/packages/rocketchat-ui/client/lib/chatMessages.coffee index 2b22a3b5f379..9af3ae555789 100644 --- a/packages/rocketchat-ui/client/lib/chatMessages.coffee +++ b/packages/rocketchat-ui/client/lib/chatMessages.coffee @@ -15,6 +15,7 @@ class @ChatMessages resize: -> dif = (if RocketChat.Layout.isEmbedded() then 0 else 60) + $(".messages-container").find("footer").outerHeight() + dif += if $(".announcement").length > 0 then 40 else 0 $(".messages-box").css height: "calc(100% - #{dif}px)" diff --git a/packages/rocketchat-ui/client/views/app/room.coffee b/packages/rocketchat-ui/client/views/app/room.coffee index b2fd6090f87e..10a27d5e295a 100644 --- a/packages/rocketchat-ui/client/views/app/room.coffee +++ b/packages/rocketchat-ui/client/views/app/room.coffee @@ -90,6 +90,19 @@ Template.room.helpers return '' unless roomData return roomData.topic + showAnnouncement: -> + roomData = Session.get('roomData' + this._id) + return false unless roomData + Meteor.defer => + if window.chatMessages and window.chatMessages[roomData._id] + window.chatMessages[roomData._id].resize() + return roomData.announcement isnt undefined and roomData.announcement isnt '' + + roomAnnouncement: -> + roomData = Session.get('roomData' + this._id) + return '' unless roomData + return roomData.announcement + roomIcon: -> roomData = Session.get('roomData' + this._id) return '' unless roomData?.t diff --git a/packages/rocketchat-ui/client/views/app/room.html b/packages/rocketchat-ui/client/views/app/room.html index 3c07f859095d..c9bf8d2aa09b 100644 --- a/packages/rocketchat-ui/client/views/app/room.html +++ b/packages/rocketchat-ui/client/views/app/room.html @@ -8,20 +8,25 @@
{{#unless embeddedVersion}} -
- {{> burger}} -

- {{#if showToggleFavorite}} - - {{/if}} - - {{roomName}} - {{#if isTranslated}} - - {{/if}} - {{{RocketChatMarkdown roomTopic}}} -

-
+
+ {{> burger}} +

+ {{#if showToggleFavorite}} + + {{/if}} + + {{roomName}} + {{#if isTranslated}} + + {{/if}} + {{{RocketChatMarkdown roomTopic}}} +

+
+ {{#if showAnnouncement}} +
+ {{{RocketChatMarkdown roomAnnouncement}}} +
+ {{/if}} {{/unless}}
{{#with unreadData}} diff --git a/server/publications/room.js b/server/publications/room.js index c665df94131f..068e70c4f080 100644 --- a/server/publications/room.js +++ b/server/publications/room.js @@ -7,6 +7,7 @@ const options = { u: 1, // usernames: 1, topic: 1, + announcement: 1, muted: 1, archived: 1, jitsiTimeout: 1, diff --git a/server/startup/roomPublishes.js b/server/startup/roomPublishes.js index fa2eccdf776c..0bf9e17c0f38 100644 --- a/server/startup/roomPublishes.js +++ b/server/startup/roomPublishes.js @@ -8,6 +8,7 @@ Meteor.startup(function() { u: 1, usernames: 1, topic: 1, + announcement: 1, muted: 1, archived: 1, ro: 1, @@ -44,6 +45,7 @@ Meteor.startup(function() { u: 1, usernames: 1, topic: 1, + announcement: 1, muted: 1, archived: 1, ro: 1, diff --git a/tests/end-to-end/ui/09-channel.js b/tests/end-to-end/ui/09-channel.js index 23e724f4b7cb..8ad477860f78 100644 --- a/tests/end-to-end/ui/09-channel.js +++ b/tests/end-to-end/ui/09-channel.js @@ -220,6 +220,38 @@ describe('channel', ()=> { }); }); + describe('Channel announcement edit', ()=> { + before(()=> { + flexTab.operateFlexTab('info', true); + }); + + after(()=> { + if (Global.toastAlert.isVisible()) { + Global.dismissToast(); + Global.toastAlert.waitForVisible(5000, true); + } + flexTab.operateFlexTab('info', false); + }); + + it('click the edit announcement', ()=> { + flexTab.editAnnouncementBtn.waitForVisible(5000); + flexTab.editAnnouncementBtn.click(); + }); + + it('edit the announcement input', ()=> { + flexTab.editAnnouncementTextInput.waitForVisible(5000); + flexTab.editAnnouncementTextInput.setValue('ANNOUNCEMENT EDITED'); + }); + + it('save the announcement', ()=> { + flexTab.editNameSave.click(); + }); + + it('should show the new announcement', ()=> { + flexTab.thirdSetting.getText().should.equal('ANNOUNCEMENT EDITED'); + }); + }); + describe('Channel description edit', ()=> { before(()=> { flexTab.operateFlexTab('info', true); @@ -248,7 +280,7 @@ describe('channel', ()=> { }); it('should show the new description', ()=> { - flexTab.thirdSetting.getText().should.equal('DESCRIPTION EDITED'); + flexTab.fourthSetting.getText().should.equal('DESCRIPTION EDITED'); }); }); }); diff --git a/tests/pageobjects/flex-tab.page.js b/tests/pageobjects/flex-tab.page.js index 83c1982652c4..9a4a45dcc9cd 100644 --- a/tests/pageobjects/flex-tab.page.js +++ b/tests/pageobjects/flex-tab.page.js @@ -48,6 +48,7 @@ class FlexTab extends Page { get archiveSave() { return browser.element('.save'); } get editNameBtn() { return browser.element('[data-edit="name"]'); } get editTopicBtn() { return browser.element('[data-edit="topic"]'); } + get editAnnouncementBtn() { return browser.element('[data-edit="announcement"]'); } get editDescriptionBtn() { return browser.element('[data-edit="description"]'); } get editNotificationBtn() { return browser.element('[data-edit="desktopNotifications"]'); } get editMobilePushBtn() { return browser.element('[data-edit="mobilePushNotifications"]'); } @@ -56,10 +57,12 @@ class FlexTab extends Page { get editNameTextInput() { return browser.element('.channel-settings input[name="name"]'); } get editTopicTextInput() { return browser.element('.channel-settings input[name="topic"]'); } + get editAnnouncementTextInput() { return browser.element('.channel-settings input[name="announcement"]'); } get editDescriptionTextInput() { return browser.element('.channel-settings input[name="description"]'); } get firstSetting() { return browser.element('.clearfix li:nth-child(1) .current-setting'); } get secondSetting() { return browser.element('.clearfix li:nth-child(2) .current-setting'); } get thirdSetting() { return browser.element('.clearfix li:nth-child(3) .current-setting'); } + get fourthSetting() { return browser.element('.clearfix li:nth-child(4) .current-setting'); } get editNameTextInput() { return browser.element('.channel-settings input[name="name"]'); } get editNameSave() { return browser.element('.channel-settings .save'); } get memberUserName() { return browser.element('.info h3'); }