-
+
+
- {{#if archived}}
-
{{/each}}
-
+
- {{_ "Room_archived"}}
+ {{#if showAvatar}}
+
+ {{/if}}
+ {{#if archived}}
+
- {{/if}}
- {{> avatar username=channelName}}
-
+ {{_ "Room_archived"}}
+
+ {{/if}}
+
+ {{> avatar username=channelName}}
{{> icon block="rc-header__icon" icon=channelIcon}}{{ unscape name}}
{{#if password}}
{{#if canEditRoom}}
diff --git a/packages/rocketchat-channel-settings/client/views/channelSettings.js b/packages/rocketchat-channel-settings/client/views/channelSettings.js
index 7d3a58ca77df..e0835b496646 100644
--- a/packages/rocketchat-channel-settings/client/views/channelSettings.js
+++ b/packages/rocketchat-channel-settings/client/views/channelSettings.js
@@ -29,12 +29,12 @@ const common = {
return roomType && roomTypes.roomTypes[roomType].canBeDeleted(hasPermission, room);
},
canEditRoom() {
- const { _id } = Template.instance().room;
- return hasAllPermission('edit-room', _id);
+ const { _id, prid } = Template.instance().room;
+ return !prid && hasAllPermission('edit-room', _id);
},
isDirectMessage() {
- const { room } = Template.instance();
- return room.t === 'd';
+ const { room: { t } } = Template.instance();
+ return t === 'd';
},
};
@@ -797,8 +797,13 @@ Template.channelSettingsInfo.helpers({
channelSettings() {
return ChannelSettings.getOptions(Template.currentData(), 'room');
},
+ showAvatar() {
+ const { room } = Template.instance();
+ return !room.prid;
+ },
name() {
- return Template.instance().room.name;
+ const { room } = Template.instance();
+ return roomTypes.getRoomName(room.t, room);
},
description() {
return Template.instance().room.description;
@@ -814,19 +819,7 @@ Template.channelSettingsInfo.helpers({
},
channelIcon() {
- const roomType = Template.instance().room.t;
- switch (roomType) {
- case 'd':
- return 'at';
- case 'p':
- return 'lock';
- case 'c':
- return 'hashtag';
- case 'l':
- return 'livechat';
- default:
- return null;
- }
+ return roomTypes.getIcon(Template.instance().room);
},
hasPurge() {
return roomHasPurge(Template.instance().room);
diff --git a/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.js b/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.js
index 462a252cee0a..213722ae31bf 100644
--- a/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.js
+++ b/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.js
@@ -59,6 +59,13 @@ Meteor.methods({
});
}
+ if (room.prid) {
+ throw new Meteor.Error('error-action-not-allowed', 'Editing thread room is not allowed', {
+ method: 'saveRoomSettings',
+ action: 'Editing_room',
+ });
+ }
+
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',
diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json
index 89f7955b6f89..1f76602b6a2a 100644
--- a/packages/rocketchat-i18n/i18n/de.i18n.json
+++ b/packages/rocketchat-i18n/i18n/de.i18n.json
@@ -2012,6 +2012,9 @@
"New_encryption_password": "Neues Verschlüsselungs-Passwort",
"New_role": "Neue Rolle",
"New_Room_Notification": "Neuer-Raum-Benachrichtigung",
+ "New_thread": "Neuer Thread",
+ "New_thread_name": "Ein sinnvoller Name für den Thread-Raum",
+ "New_thread_first_message": "Üblicherweise beginnt ein Thread mit einer Frage, bspw. \"Wie lade ich ein Bild hoch?\"",
"New_Trigger": "Neuer Trigger",
"New_version_available_(s)": "Neue Version verfügbar (%s)",
"New_videocall_request": "Neuer Video-Anruf",
@@ -2031,10 +2034,12 @@
"No_messages_yet": "Bisher keine Nachrichten",
"No_pages_yet_Try_hitting_Reload_Pages_button": "Bisher keine Seite. Versicherung die Seite neu zu laden",
"No_pinned_messages": "Es wurden bisher keine Nachrichten fixiert",
+ "No_replies_yet": "Bisher keine Antworten",
"No_results_found": "Keine Ergebnisse gefunden",
"No_results_found_for": "Keine Ergebnisse gefunden für:",
"No_snippet_messages": "Keine Snippets vorhanden",
"No_starred_messages": "Es wurden bisher keine Nachrichten favorisiert",
+ "No_threads_yet": "Keine Threads vorhanden",
"No_such_command": "Es gibt keinen Befehl '/__command__'",
"No_user_with_username_%s_was_found": "Es wurde kein Benutzer mit dem Namen \"%s\" gefunden!",
"Nobody_available": "Es ist niemand verfügbar",
@@ -2315,6 +2320,7 @@
"Remove_someone_from_room": "Jemanden aus dem Raum entfernen",
"Removed": "Entfernt",
"Removed_User": "Benutzer wurde entfernt",
+ "Replies": "Antworten",
"Reply": "Antwort",
"ReplyTo": "Antwort an",
"Report_Abuse": "Missbrauch melden",
@@ -2740,6 +2746,28 @@
"This_month": "Diesen Monat",
"This_room_has_been_archived_by__username_": "Dieser Raum wurde von __username__ archiviert",
"This_room_has_been_unarchived_by__username_": "Dieser Raum wurde von __username__ aus dem Archiv geholt",
+ "Thread_creation_on_home": "Threads von der Home-Seite aus anlegen",
+ "Thread_default_parent_Channel": "Standard-Kanal für neue Threads",
+ "Thread_from_context_menu": "Threads im Kontext-Menü",
+ "Thread_name": "Thread Name",
+ "Thread_invitations_threshold_description": "Max. Anzahl der Benutzer, die automatisch zu einem öffentlichen Thread hinzugezogen werden",
+ "Thread_invitations_threshold": "Max. Anzahl automatisch einzuladender Benutzer",
+ "Thread_slash_command_description": "Erstelle einen Thread zum aktuellen Kanal",
+ "Thread_slash_command_params": "Deine Nachricht",
+ "Thread_start": "Thread starten",
+ "Thread_target_channel_description": "Wähle einen Kanal oder eine Gruppe aus, die zu Deinem Anliegen passt",
+ "Thread_target_channel_prefix": "Du erstellst einen Thread in",
+ "Thread_target_channel_suffix": "- wähle einen anderen übergeordneten Kanal aus",
+ "Thread_target_channel": "Übergeordneter Kanal oder Gruppe",
+ "thread-created": "Ich habe einen neuen Thread angelegt: \"__message__\"",
+ "thread-welcome": "Danke __username__, dass Du einen neuen Thread angelegt hast! Ich habe für Dich Mitglieder aus __parentChannel__ eingeladen. Tipp: mit \"@all\" kannst Du sie anstupsen, wenn sich länger niemand melden sollte ;)",
+ "thread": "Thread",
+ "Threading_context_menu_button": "Separater Button",
+ "Threading_context_menu_none": "Unsichtbar",
+ "Threading_description": "Erstelle einen Thread, um wichtigen Dingen mehr Raum zu geben. Dort kannst Du mit allen verfügbaren Mitgliedern schreiben, ohne andere zu stören. So sorgst Du für etwas mehr Ordnung in Eurem Chat.",
+ "Threading_first_message_title": "Deine Nachricht",
+ "Threads": "Threads",
+ "Threading_title": "Einen neuen Thread anlegen",
"This_week": "Diese Woche",
"Thursday": "Donnerstag",
"Time_in_seconds": "Zeit in Sekunden",
@@ -3094,6 +3122,7 @@
"your_message_optional": "Ihre optionale Nachricht",
"Your_password_is_wrong": "Falsches Passwort",
"Your_push_was_sent_to_s_devices": "Eine Push-Nachricht wurde an %s Geräte gesendet.",
+ "Your_question": "Deine Frage",
"Your_server_link": "Ihre Serververbindung",
"Your_workspace_is_ready": "Ihr Arbeitsbereich ist einsatzbereit 🎉"
-}
\ No newline at end of file
+}
diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json
index 1ffdf1583180..d120a723badf 100644
--- a/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -2050,6 +2050,9 @@
"New_encryption_password": "New encryption password",
"New_role": "New role",
"New_Room_Notification": "New Room Notification",
+ "New_thread": "New thread",
+ "New_thread_name": "A meaningful name for the thread room",
+ "New_thread_first_message": "Usually, a thread starts with a question, like \"How do I upload a picture?\"",
"New_Trigger": "New Trigger",
"New_version_available_(s)": "New version available (%s)",
"New_videocall_request": "New Video Call Request",
@@ -2069,11 +2072,13 @@
"No_messages_yet": "No messages yet",
"No_pages_yet_Try_hitting_Reload_Pages_button": "No pages yet. Try hitting \"Reload Pages\" button.",
"No_pinned_messages": "No pinned messages",
+ "No_replies_yet": "No replies yet",
"No_results_found": "No results found",
"No_results_found_for": "No results found for:",
"No_snippet_messages": "No snippet",
"No_starred_messages": "No starred messages",
"No_such_command": "No such command: `/__command__`",
+ "No_threads_yet": "No threads yet",
"No_user_with_username_%s_was_found": "No user with username \"%s\" was found!",
"Nobody_available": "Nobody available",
"Node_version": "Node Version",
@@ -2173,6 +2178,7 @@
"Override_URL_to_which_files_are_uploaded_This_url_also_used_for_downloads_unless_a_CDN_is_given": "Override URL to which files are uploaded. This url also used for downloads unless a CDN is given",
"Page_title": "Page title",
"Page_URL": "Page URL",
+ "Parent_channel_doesnt_exist": "Channel does not exist.",
"Password": "Password",
"Password_Change_Disabled": "Your Rocket.Chat administrator has disabled the changing of passwords",
"Password_changed_successfully": "Password changed successfully",
@@ -2354,6 +2360,7 @@
"Remove_someone_from_room": "Remove someone from the room",
"Removed": "Removed",
"Removed_User": "Removed User",
+ "Replies": "Replies",
"Reply": "Reply",
"ReplyTo": "Reply-To",
"Report_Abuse": "Report Abuse",
@@ -2376,6 +2383,7 @@
"Retail": "Retail",
"Retention_setting_changed_successfully": "Retention policy setting changed successfully",
"RetentionPolicy": "Retention Policy",
+ "RetentionPolicy_DoNotExcludeThreads": "Do not exclude thread messages",
"RetentionPolicy_RoomWarning": "Messages older than __time__ are automatically pruned here",
"RetentionPolicy_RoomWarning_Unpinned": "Unpinned messages older than __time__ are automatically pruned here",
"RetentionPolicy_RoomWarning_FilesOnly": "Files older than __time__ are automatically pruned here (messages stay intact)",
@@ -2708,6 +2716,7 @@
"The_emails_are_being_sent": "The emails are being sent.",
"The_field_is_required": "The field %s is required.",
"The_image_resize_will_not_work_because_we_can_not_detect_ImageMagick_or_GraphicsMagick_installed_in_your_server": "The image resize will not work because we can not detect ImageMagick or GraphicsMagick installed on your server.",
+ "The_message_is_a_thread_you_will_not_be_able_to_recover": "The message is a thread you will not be able to recover the messages!",
"The_peer__peer__does_not_exist": "The peer __peer__ does not exist.",
"The_redirectUri_is_required": "The redirectUri is required",
"The_server_will_restart_in_s_seconds": "The server will restart in %s seconds",
@@ -2781,6 +2790,29 @@
"This_month": "This Month",
"This_room_has_been_archived_by__username_": "This room has been archived by __username__",
"This_room_has_been_unarchived_by__username_": "This room has been unarchived by __username__",
+ "Thread_creation_on_home": "Create threads on home screen",
+ "Thread_default_parent_Channel": "Default channel for new Threads",
+ "Thread_from_context_menu": "Threads in context-menu",
+ "Thread_invitations_threshold_description": "Max. count of users who are automatically being invited into a public thread",
+ "Thread_invitations_threshold": "Max. users to be automatically invited",
+ "Thread_name": "Thread name",
+ "Thread_slash_command_description": "Creates a thread for the current channel",
+ "Thread_slash_command_params": "your message",
+ "Thread_start": "Start a thread",
+ "Thread_target_channel_description": "Select a channel which is related to what you want to ask",
+ "Thread_target_channel_prefix": "You are creating a thread in",
+ "Thread_target_channel_suffix": "- select a different parent channel",
+ "Thread_target_channel": "Parent channel or group",
+ "thread-created" : "Started a new thread: \"__message__\"",
+ "thread-welcome" : "Thanks __username__ for creating a thread! I invited some members from __parentChannel__ who shall be able to help you. Hint: You can poke them with \"@all\", in case there's nothing happening for a longer time ;)",
+ "thread": "thread",
+ "Threading_context_menu_button": "Dedicated button",
+ "Threading_context_menu_none": "Invisible",
+ "Threading_description": "Help keeping an overview about what's going on! By creating a thread, a sub-channel of the one you selected is created and both are linked.",
+ "Threading_first_message_title": "Your message",
+ "Threads_in_sidebar": "Show threads category on sidebar",
+ "Threads": "Threads",
+ "Threading_title": "Create a new thread",
"This_week": "This Week",
"Thursday": "Thursday",
"Time_in_seconds": "Time in seconds",
@@ -3135,6 +3167,7 @@
"your_message_optional": "your message (optional)",
"Your_password_is_wrong": "Your password is wrong!",
"Your_push_was_sent_to_s_devices": "Your push was sent to %s devices",
+ "Your_question": "Your question",
"Your_server_link": "Your server link",
"Your_workspace_is_ready": "Your workspace is ready to use 🎉"
-}
\ No newline at end of file
+}
diff --git a/packages/rocketchat-lib/lib/roomTypes/direct.js b/packages/rocketchat-lib/lib/roomTypes/direct.js
index e229addaa1bc..0e8768d7cea2 100644
--- a/packages/rocketchat-lib/lib/roomTypes/direct.js
+++ b/packages/rocketchat-lib/lib/roomTypes/direct.js
@@ -50,9 +50,16 @@ export class DirectMessageRoomType extends RoomTypeConfig {
}
roomName(roomData) {
- const subscription = Subscriptions.findOne({ rid: roomData._id }, { fields: { name: 1, fname: 1 } });
- if (!subscription) {
- return '';
+
+ // this function can receive different types of data
+ // if it doesn't have fname and name properties, should be a Room object
+ // so, need to find the related subscription
+ const subscription = roomData && (roomData.fname || roomData.name) ?
+ roomData :
+ Subscriptions.findOne({ rid: roomData._id });
+
+ if (subscription === undefined) {
+ return console.log('roomData', roomData);
}
if (settings.get('UI_Use_Real_Name') && subscription.fname) {
diff --git a/packages/rocketchat-lib/lib/roomTypes/private.js b/packages/rocketchat-lib/lib/roomTypes/private.js
index b17ef7dc98d0..1dfc0aaf70d5 100644
--- a/packages/rocketchat-lib/lib/roomTypes/private.js
+++ b/packages/rocketchat-lib/lib/roomTypes/private.js
@@ -1,5 +1,5 @@
import { Meteor } from 'meteor/meteor';
-import { ChatRoom } from 'meteor/rocketchat:models';
+import { ChatRoom, ChatSubscription } from 'meteor/rocketchat:models';
import { openRoom } from 'meteor/rocketchat:ui-utils';
import { settings } from 'meteor/rocketchat:settings';
import { hasAtLeastOnePermission, hasPermission } from 'meteor/rocketchat:authorization';
@@ -29,6 +29,13 @@ export class PrivateRoomType extends RoomTypeConfig {
});
}
+ getIcon(roomData) {
+ if (roomData.prid) {
+ return 'thread';
+ }
+ return this.icon;
+ }
+
findRoom(identifier) {
const query = {
t: 'p',
@@ -39,6 +46,9 @@ export class PrivateRoomType extends RoomTypeConfig {
}
roomName(roomData) {
+ if (roomData.prid) {
+ return roomData.fname;
+ }
if (settings.get('UI_Allow_room_names_with_special_chars')) {
return roomData.fname || roomData.name;
}
@@ -59,6 +69,18 @@ export class PrivateRoomType extends RoomTypeConfig {
return hasAtLeastOnePermission(['add-user-to-any-p-room', 'add-user-to-joined-room'], room._id);
}
+ canSendMessage(roomId) {
+ const room = ChatRoom.findOne({ _id: roomId, t: 'p' }, { fields: { prid: 1 } });
+ if (room.prid) {
+ return true;
+ }
+
+ // TODO: remove duplicated code
+ return ChatSubscription.find({
+ rid: roomId,
+ }).count() > 0;
+ }
+
allowRoomSettingChange(room, setting) {
switch (setting) {
case RoomSettingsEnum.JOIN_CODE:
diff --git a/packages/rocketchat-lib/lib/roomTypes/public.js b/packages/rocketchat-lib/lib/roomTypes/public.js
index 4f55139602b6..1c2f35c00200 100644
--- a/packages/rocketchat-lib/lib/roomTypes/public.js
+++ b/packages/rocketchat-lib/lib/roomTypes/public.js
@@ -1,6 +1,6 @@
import { Meteor } from 'meteor/meteor';
import { openRoom } from 'meteor/rocketchat:ui-utils';
-import { ChatRoom } from 'meteor/rocketchat:models';
+import { ChatRoom, ChatSubscription } from 'meteor/rocketchat:models';
import { settings } from 'meteor/rocketchat:settings';
import { hasAtLeastOnePermission } from 'meteor/rocketchat:authorization';
import { getUserPreference, RoomTypeConfig, RoomTypeRouteConfig, RoomSettingsEnum, UiTextContext } from 'meteor/rocketchat:utils';
@@ -29,6 +29,13 @@ export class PublicRoomType extends RoomTypeConfig {
});
}
+ getIcon(roomData) {
+ if (roomData.prid) {
+ return 'thread';
+ }
+ return this.icon;
+ }
+
findRoom(identifier) {
const query = {
t: 'c',
@@ -38,6 +45,9 @@ export class PublicRoomType extends RoomTypeConfig {
}
roomName(roomData) {
+ if (roomData.prid) {
+ return roomData.fname;
+ }
if (settings.get('UI_Allow_room_names_with_special_chars')) {
return roomData.fname || roomData.name;
}
@@ -65,6 +75,18 @@ export class PublicRoomType extends RoomTypeConfig {
return hasAtLeastOnePermission(['add-user-to-any-c-room', 'add-user-to-joined-room'], room._id);
}
+ canSendMessage(roomId) {
+ const room = ChatRoom.findOne({ _id: roomId, t: 'c' }, { fields: { prid: 1 } });
+ if (room.prid) {
+ return true;
+ }
+
+ // TODO: remove duplicated code
+ return ChatSubscription.find({
+ rid: roomId,
+ }).count() > 0;
+ }
+
enableMembersListProfile() {
return true;
}
diff --git a/packages/rocketchat-lib/server/functions/attachMessage.js b/packages/rocketchat-lib/server/functions/attachMessage.js
new file mode 100644
index 000000000000..0806ade75a3e
--- /dev/null
+++ b/packages/rocketchat-lib/server/functions/attachMessage.js
@@ -0,0 +1,14 @@
+
+import { getAvatarUrlFromUsername } from 'meteor/rocketchat:utils';
+import { roomTypes } from 'meteor/rocketchat:utils';
+export const attachMessage = function(message, room) {
+ const { msg, u: { username }, ts, attachments, _id } = message;
+ return {
+ text: msg,
+ author_name: username,
+ author_icon: getAvatarUrlFromUsername(username),
+ message_link: `${ roomTypes.getRouteLink(room.t, room) }?msg=${ _id }`,
+ attachments,
+ ts,
+ };
+};
diff --git a/packages/rocketchat-lib/server/functions/cleanRoomHistory.js b/packages/rocketchat-lib/server/functions/cleanRoomHistory.js
index 7cf5ef828b94..9cedd9a4aa65 100644
--- a/packages/rocketchat-lib/server/functions/cleanRoomHistory.js
+++ b/packages/rocketchat-lib/server/functions/cleanRoomHistory.js
@@ -2,8 +2,9 @@ import { TAPi18n } from 'meteor/tap:i18n';
import { FileUpload } from 'meteor/rocketchat:file-upload';
import { Messages, Rooms } from 'meteor/rocketchat:models';
import { Notifications } from 'meteor/rocketchat:notifications';
+import { deleteRoom } from './deleteRoom';
-export const cleanRoomHistory = function({ rid, latest = new Date(), oldest = new Date('0001-01-01T00:00:00Z'), inclusive = true, limit = 0, excludePinned = true, filesOnly = false, fromUsers = [] }) {
+export const cleanRoomHistory = function({ rid, latest = new Date(), oldest = new Date('0001-01-01T00:00:00Z'), inclusive = true, limit = 0, excludePinned = true, ignoreThreads = true, filesOnly = false, fromUsers = [] }) {
const gt = inclusive ? '$gte' : '$gt';
const lt = inclusive ? '$lte' : '$lt';
@@ -15,6 +16,7 @@ export const cleanRoomHistory = function({ rid, latest = new Date(), oldest = ne
Messages.findFilesByRoomIdPinnedTimestampAndUsers(
rid,
excludePinned,
+ ignoreThreads,
ts,
fromUsers,
{ fields: { 'file._id': 1, pinned: 1 }, limit }
@@ -29,13 +31,18 @@ export const cleanRoomHistory = function({ rid, latest = new Date(), oldest = ne
return fileCount;
}
- const count = limit ? Messages.removeByIdPinnedTimestampLimitAndUsers(rid, excludePinned, ts, limit, fromUsers) : Messages.removeByIdPinnedTimestampAndUsers(rid, excludePinned, ts, fromUsers);
+ if (!ignoreThreads) {
+ Messages.findFilesByRoomIdPinnedTimestampAndUsers(rid, excludePinned, ignoreThreads, ts, fromUsers, { fields: { trid: 1 }, ...(limit && { limit }) }).forEach(({ trid }) => deleteRoom(trid));
+ }
+
+ const count = limit ? Messages.removeByIdPinnedTimestampLimitAndUsers(rid, excludePinned, ignoreThreads, ts, limit, fromUsers) : Messages.removeByIdPinnedTimestampAndUsers(rid, excludePinned, ignoreThreads, ts, fromUsers);
if (count) {
Rooms.resetLastMessageById(rid);
Notifications.notifyRoom(rid, 'deleteMessageBulk', {
rid,
excludePinned,
+ ignoreThreads,
ts,
users: fromUsers,
});
diff --git a/packages/rocketchat-lib/server/functions/createRoom.js b/packages/rocketchat-lib/server/functions/createRoom.js
index 80daa229fcb4..20dcb739b582 100644
--- a/packages/rocketchat-lib/server/functions/createRoom.js
+++ b/packages/rocketchat-lib/server/functions/createRoom.js
@@ -145,6 +145,10 @@ export const createRoom = function(type, name, owner, members, readOnly, extraDa
extra.open = true;
+ if (room.prid) {
+ extra.prid = room.prid;
+ }
+
if (username === owner.username) {
extra.ls = now;
}
@@ -174,7 +178,7 @@ export const createRoom = function(type, name, owner, members, readOnly, extraDa
}
return {
- rid: room._id,
- name: room.name,
+ rid: room._id, // backwards compatible
+ ...room,
};
};
diff --git a/packages/rocketchat-lib/server/functions/deleteMessage.js b/packages/rocketchat-lib/server/functions/deleteMessage.js
index 433cb99791a0..e687abcd5f6f 100644
--- a/packages/rocketchat-lib/server/functions/deleteMessage.js
+++ b/packages/rocketchat-lib/server/functions/deleteMessage.js
@@ -38,13 +38,13 @@ export const deleteMessage = function(message, user) {
}
}
+ const room = Rooms.findOneById(message.rid, { fields: { lastMessage: 1, prid: 1, mid: 1 } });
Meteor.defer(function() {
callbacks.run('afterDeleteMessage', deletedMsg);
});
// update last message
if (settings.get('Store_Last_Message')) {
- const room = Rooms.findOneById(message.rid, { fields: { lastMessage: 1 } });
if (!room.lastMessage || room.lastMessage._id === message._id) {
Rooms.resetLastMessageById(message.rid, message._id);
}
@@ -60,4 +60,3 @@ export const deleteMessage = function(message, user) {
Apps.getBridges().getListenerBridge().messageEvent('IPostMessageDeleted', deletedMsg);
}
};
-
diff --git a/packages/rocketchat-lib/server/functions/deleteRoom.js b/packages/rocketchat-lib/server/functions/deleteRoom.js
new file mode 100644
index 000000000000..e2a567be42ad
--- /dev/null
+++ b/packages/rocketchat-lib/server/functions/deleteRoom.js
@@ -0,0 +1,9 @@
+import { Messages, Subscriptions, Rooms } from 'meteor/rocketchat:models';
+import { callbacks } from 'meteor/rocketchat:callbacks';
+export const deleteRoom = function(rid) {
+ Messages.removeFilesByRoomId(rid);
+ Messages.removeByRoomId(rid);
+ Subscriptions.removeByRoomId(rid);
+ callbacks.run('afterDeleteRoom', rid);
+ return Rooms.removeById(rid);
+};
diff --git a/packages/rocketchat-lib/server/functions/index.js b/packages/rocketchat-lib/server/functions/index.js
index 1524410b3b6f..118ca8f784e0 100644
--- a/packages/rocketchat-lib/server/functions/index.js
+++ b/packages/rocketchat-lib/server/functions/index.js
@@ -1,11 +1,13 @@
export { addUserToDefaultChannels } from './addUserToDefaultChannels';
export { addUserToRoom } from './addUserToRoom';
export { archiveRoom } from './archiveRoom';
+export { attachMessage } from './attachMessage';
export { checkEmailAvailability } from './checkEmailAvailability';
export { checkUsernameAvailability } from './checkUsernameAvailability';
export { cleanRoomHistory } from './cleanRoomHistory';
export { createRoom } from './createRoom';
export { deleteMessage } from './deleteMessage';
+export { deleteRoom } from './deleteRoom';
export { deleteUser } from './deleteUser';
export { getFullUserData } from './getFullUserData';
export { getRoomByNameOrIdWithOptionToJoin } from './getRoomByNameOrIdWithOptionToJoin';
diff --git a/packages/rocketchat-lib/server/functions/sendMessage.js b/packages/rocketchat-lib/server/functions/sendMessage.js
index 35bdfb8c2018..10d751a25552 100644
--- a/packages/rocketchat-lib/server/functions/sendMessage.js
+++ b/packages/rocketchat-lib/server/functions/sendMessage.js
@@ -161,7 +161,7 @@ export const sendMessage = function(user, message, room, upsert = false) {
delete message.tokens;
}
- message = callbacks.run('beforeSaveMessage', message);
+ message = callbacks.run('beforeSaveMessage', message, room);
if (message) {
// Avoid saving sandstormSessionId to the database
let sandstormSessionId = null;
diff --git a/packages/rocketchat-lib/server/methods/cleanRoomHistory.js b/packages/rocketchat-lib/server/methods/cleanRoomHistory.js
index baf50abfc0cc..6e2241b33efd 100644
--- a/packages/rocketchat-lib/server/methods/cleanRoomHistory.js
+++ b/packages/rocketchat-lib/server/methods/cleanRoomHistory.js
@@ -4,7 +4,7 @@ import { hasPermission } from 'meteor/rocketchat:authorization';
import { cleanRoomHistory } from '../functions';
Meteor.methods({
- cleanRoomHistory({ roomId, latest, oldest, inclusive = true, limit, excludePinned = false, filesOnly = false, fromUsers = [] }) {
+ cleanRoomHistory({ roomId, latest, oldest, inclusive = true, limit, excludePinned = false, ignoreThreads = true, filesOnly = false, fromUsers = [] }) {
check(roomId, String);
check(latest, Date);
check(oldest, Date);
@@ -24,6 +24,6 @@ Meteor.methods({
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'cleanRoomHistory' });
}
- return cleanRoomHistory({ rid: roomId, latest, oldest, inclusive, limit, excludePinned, filesOnly, fromUsers });
+ return cleanRoomHistory({ rid: roomId, latest, oldest, inclusive, limit, excludePinned, ignoreThreads, filesOnly, fromUsers });
},
});
diff --git a/packages/rocketchat-lib/server/methods/getRoomMessageMetadata.js b/packages/rocketchat-lib/server/methods/getRoomMessageMetadata.js
new file mode 100644
index 000000000000..1a1b26698001
--- /dev/null
+++ b/packages/rocketchat-lib/server/methods/getRoomMessageMetadata.js
@@ -0,0 +1,25 @@
+import { Meteor } from 'meteor/meteor';
+import { check } from 'meteor/check';
+import { Messages } from 'meteor/rocketchat:models';
+Meteor.methods({
+ /**
+ * Non-reactively retrieves metadata about messages of a room
+ * @param {String} roomId
+ * @returns {visibleMessagesCount, lastMessageTimestamp}
+ */
+ 'getRoomMessageMetadata'(roomId) {
+
+ check(roomId, String);
+ if (!Meteor.userId()) {
+ throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'archiveRoom' });
+ }
+
+ const metadata = {};
+
+ metadata.visibleMessagesCount = Messages.findVisibleByRoomId(roomId).count();
+ const lastMessage = Messages.getLastVisibleMessageSentWithNoTypeByRoomId(roomId);
+ metadata.lastMessageTimestamp = lastMessage && lastMessage.ts;
+
+ return metadata;
+ },
+});
diff --git a/packages/rocketchat-message-attachments/client/index.js b/packages/rocketchat-message-attachments/client/index.js
index 00ca1e755854..7dc6bfe9281b 100644
--- a/packages/rocketchat-message-attachments/client/index.js
+++ b/packages/rocketchat-message-attachments/client/index.js
@@ -1,2 +1,10 @@
import './messageAttachment.html';
import './messageAttachment';
+import './renderField.html';
+import './renderField';
+
+import { registerFieldTemplate } from './renderField';
+
+export {
+ registerFieldTemplate,
+};
diff --git a/packages/rocketchat-message-attachments/client/messageAttachment.html b/packages/rocketchat-message-attachments/client/messageAttachment.html
index c8b2b868588f..88e516fd6a2c 100644
--- a/packages/rocketchat-message-attachments/client/messageAttachment.html
+++ b/packages/rocketchat-message-attachments/client/messageAttachment.html
@@ -145,11 +145,8 @@
{{#if fields}}
{{#unless collapsed}}
- {{#each fields}}
-
{{/unless}}
diff --git a/packages/rocketchat-message-attachments/client/renderField.html b/packages/rocketchat-message-attachments/client/renderField.html
new file mode 100644
index 000000000000..44bca7b06fc7
--- /dev/null
+++ b/packages/rocketchat-message-attachments/client/renderField.html
@@ -0,0 +1,20 @@
+
+ {{#if field.type}}
+
+
-
+ {{#each field in fields}}
+ {{> renderField field=field}}
{{/each}}
{{title}}
- {{{RocketChatMarkdown value}}}
-
+ {{{specializedRendering field=field message=../..}}}
+
+ {{else}}
+ {{#if short}}
+
+
+ {{else}}
+ {{field.title}}
+ {{{RocketChatMarkdown field.value}}}
+
+
+ {{/if}}
+ {{/if}}
+
diff --git a/packages/rocketchat-message-attachments/client/renderField.js b/packages/rocketchat-message-attachments/client/renderField.js
new file mode 100644
index 000000000000..28bc9fd0adab
--- /dev/null
+++ b/packages/rocketchat-message-attachments/client/renderField.js
@@ -0,0 +1,56 @@
+import { Template } from 'meteor/templating';
+import { Blaze } from 'meteor/blaze';
+
+const renderers = {};
+
+/**
+ * The field templates will be rendered non-reactive for all messages by the messages-list (@see rocketchat-nrr)
+ * Thus, we cannot provide helpers or events to the template, but we need to register this interactivity at the parent
+ * template which is the room. The event will be bubbled by the Blaze-framework
+ * @param fieldType
+ * @param templateName
+ * @param helpers
+ * @param events
+ */
+export function registerFieldTemplate(fieldType, templateName, events) {
+ renderers[fieldType] = templateName;
+
+ // propagate helpers and events to the room template, changing the selectors
+ // loop at events. For each event (like 'click .accept'), copy the function to a function of the room events.
+ // While doing that, add the fieldType as class selector to the events function in order to avoid naming clashes
+ if (events != null) {
+ const uniqueEvents = {};
+ // rename the event handlers so they are unique in the "parent" template to which the events bubble
+ for (const property in events) {
+ if (events.hasOwnProperty(property)) {
+ const event = property.substr(0, property.indexOf(' '));
+ const selector = property.substr(property.indexOf(' ') + 1);
+ Object.defineProperty(uniqueEvents,
+ `${ event } .${ fieldType } ${ selector }`,
+ {
+ value: events[property],
+ enumerable: true, // assign as a own property
+ });
+ }
+ }
+ Template.room.events(uniqueEvents);
+ }
+}
+
+// onRendered is not being executed (no idea why). Consequently, we cannot use Blaze.renderWithData(), since we don't
+// have access to the DOM outside onRendered. Therefore, we can only translate the content of the field to HTML and
+// embed it non-reactively.
+// This in turn means that onRendered of the field template will not be processed either.
+// I guess it may have someting to do with rocketchat-nrr
+Template.renderField.helpers({
+ specializedRendering({ hash: { field, message } }) {
+ let html = '';
+ if (field.type && renderers[field.type]) {
+ html = Blaze.toHTMLWithData(Template[renderers[field.type]], { field, message });
+ } else {
+ // consider the value already formatted as html
+ html = field.value;
+ }
+ return `{{field.title}}
+ {{{RocketChatMarkdown field.value}}}
+ ${ html }
`;
+ },
+});
diff --git a/packages/rocketchat-models/server/models/Messages.js b/packages/rocketchat-models/server/models/Messages.js
index c33eb9532c7f..012f7031f6f0 100644
--- a/packages/rocketchat-models/server/models/Messages.js
+++ b/packages/rocketchat-models/server/models/Messages.js
@@ -24,6 +24,10 @@ export class Messages extends Base {
this.tryEnsureIndex({ location: '2dsphere' });
this.tryEnsureIndex({ slackBotId: 1, slackTs: 1 }, { sparse: 1 });
this.tryEnsureIndex({ unread: 1 }, { sparse: true });
+
+ // threads
+ this.tryEnsureIndex({ trid: 1 }, { sparse: true });
+
this.loadSettings();
}
@@ -156,7 +160,7 @@ export class Messages extends Base {
return this.find(query, { fields: { 'file._id': 1 }, ...options });
}
- findFilesByRoomIdPinnedTimestampAndUsers(rid, excludePinned, ts, users = [], options = {}) {
+ findFilesByRoomIdPinnedTimestampAndUsers(rid, excludePinned, ignoreThreads = true, ts, users = [], options = {}) {
const query = {
rid,
ts,
@@ -167,6 +171,10 @@ export class Messages extends Base {
query.pinned = { $ne: true };
}
+ if (!ignoreThreads) {
+ query.trid = { $exists: 0 };
+ }
+
if (users.length) {
query['u.username'] = { $in: users };
}
@@ -836,7 +844,7 @@ export class Messages extends Base {
return this.remove(query);
}
- removeByIdPinnedTimestampAndUsers(rid, pinned, ts, users = []) {
+ removeByIdPinnedTimestampAndUsers(rid, pinned, ignoreThreads = true, ts, users = []) {
const query = {
rid,
ts,
@@ -845,7 +853,9 @@ export class Messages extends Base {
if (pinned) {
query.pinned = { $ne: true };
}
-
+ if (!ignoreThreads) {
+ query.trid = { $exists: 0 };
+ }
if (users.length) {
query['u.username'] = { $in: users };
}
@@ -853,7 +863,7 @@ export class Messages extends Base {
return this.remove(query);
}
- removeByIdPinnedTimestampLimitAndUsers(rid, pinned, ts, limit, users = []) {
+ removeByIdPinnedTimestampLimitAndUsers(rid, pinned, ignoreThreads = true, ts, limit, users = []) {
const query = {
rid,
ts,
@@ -863,6 +873,10 @@ export class Messages extends Base {
query.pinned = { $ne: true };
}
+ if (!ignoreThreads) {
+ query.trid = { $exists: 0 };
+ }
+
if (users.length) {
query['u.username'] = { $in: users };
}
@@ -948,6 +962,44 @@ export class Messages extends Base {
},
});
}
+
+ /**
+ * Copy metadata from the thread to the system message in the parent channel
+ * which links to the thread.
+ * Since we don't pass this metadata into the model's function, it is not a subject
+ * to race conditions: If multiple updates occur, the current state will be updated
+ * only if the new state of the thread room is really newer.
+ */
+ refreshThreadMetadata({ rid }) {
+ if (!rid) {
+ return false;
+ }
+ const { lm, msgs: count } = Rooms.findOneById(rid, {
+ fields: {
+ msgs: 1,
+ lm: 1,
+ },
+ });
+
+ const query = {
+ trid: rid,
+ };
+
+ return this.update(query, {
+ $set: {
+ 'attachments.0.fields': [
+ {
+ type: 'messageCounter',
+ count,
+ },
+ {
+ type: 'lastMessageAge',
+ lm,
+ },
+ ],
+ },
+ }, { multi: 1 });
+ }
}
export default new Messages();
diff --git a/packages/rocketchat-models/server/models/Rooms.js b/packages/rocketchat-models/server/models/Rooms.js
index cf19c3ab7f81..a0f9e326abb7 100644
--- a/packages/rocketchat-models/server/models/Rooms.js
+++ b/packages/rocketchat-models/server/models/Rooms.js
@@ -17,6 +17,9 @@ export class Rooms extends Base {
this.tryEnsureIndex({ 'tokenpass.tokens.token': 1 });
this.tryEnsureIndex({ open: 1 }, { sparse: 1 });
this.tryEnsureIndex({ departmentId: 1 }, { sparse: 1 });
+
+ // threads
+ this.tryEnsureIndex({ prid: 1 });
}
findOneByIdOrName(_idOrName, options) {
@@ -1363,6 +1366,37 @@ export class Rooms extends Base {
return this.remove(query);
}
+
+ // ############################
+ // Threads
+ findThreadParentByNameStarting(name, options) {
+ const nameRegex = new RegExp(`^${ s.trim(s.escapeRegExp(name)) }`, 'i');
+
+ const query = {
+ t: {
+ $in: ['c'],
+ },
+ name: nameRegex,
+ archived: { $ne: true },
+ prid: {
+ $exists: false,
+ },
+ };
+
+ return this.find(query, options);
+ }
+
+ setLinkMessageById(_id, linkMessageId) {
+ const query = { _id };
+
+ const update = {
+ $set: {
+ linkMessageId,
+ },
+ };
+
+ return this.update(query, update);
+ }
}
export default new Rooms('room', true);
diff --git a/packages/rocketchat-models/server/models/Subscriptions.js b/packages/rocketchat-models/server/models/Subscriptions.js
index c7223cbf3267..d8ce5fc5e905 100644
--- a/packages/rocketchat-models/server/models/Subscriptions.js
+++ b/packages/rocketchat-models/server/models/Subscriptions.js
@@ -28,6 +28,7 @@ export class Subscriptions extends Base {
this.tryEnsureIndex({ autoTranslate: 1 }, { sparse: 1 });
this.tryEnsureIndex({ autoTranslateLanguage: 1 }, { sparse: 1 });
this.tryEnsureIndex({ 'userHighlights.0': 1 }, { sparse: 1 });
+ this.tryEnsureIndex({ prid: 1 });
}
findByRoomIds(roomIds) {
@@ -1221,6 +1222,10 @@ export class Subscriptions extends Base {
...extraData,
};
+ if (room.prid) {
+ subscription.prid = room.prid;
+ }
+
const result = this.insert(subscription);
Rooms.incUsersCountById(room._id);
diff --git a/packages/rocketchat-retention-policy/server/cronPruneMessages.js b/packages/rocketchat-retention-policy/server/cronPruneMessages.js
index 5b5d70ddaf56..8858b0e34879 100644
--- a/packages/rocketchat-retention-policy/server/cronPruneMessages.js
+++ b/packages/rocketchat-retention-policy/server/cronPruneMessages.js
@@ -21,6 +21,7 @@ function job() {
const now = new Date();
const filesOnly = settings.get('RetentionPolicy_FilesOnly');
const excludePinned = settings.get('RetentionPolicy_ExcludePinned');
+ const ignoreThreads = settings.get('RetentionPolicy_DoNotExcludeThreads');
// get all rooms with default values
types.forEach((type) => {
@@ -36,7 +37,7 @@ function job() {
],
'retention.overrideGlobal': { $ne: true },
}, { fields : { _id: 1 } }).forEach(({ _id: rid }) => {
- cleanRoomHistory({ rid, latest, oldest, filesOnly, excludePinned });
+ cleanRoomHistory({ rid, latest, oldest, filesOnly, excludePinned, ignoreThreads });
});
});
@@ -48,7 +49,7 @@ function job() {
}).forEach((room) => {
const { maxAge = 30, filesOnly, excludePinned } = room.retention;
const latest = new Date(now.getTime() - maxAge * toDays);
- cleanRoomHistory({ rid: room._id, latest, oldest, filesOnly, excludePinned });
+ cleanRoomHistory({ rid: room._id, latest, oldest, filesOnly, excludePinned, ignoreThreads });
});
lastPrune = new Date(now.getTime() - gracePeriod);
}
diff --git a/packages/rocketchat-ui-account/client/accountPreferences.html b/packages/rocketchat-ui-account/client/accountPreferences.html
index 887506f27f56..e201dab06fbb 100644
--- a/packages/rocketchat-ui-account/client/accountPreferences.html
+++ b/packages/rocketchat-ui-account/client/accountPreferences.html
@@ -270,6 +270,14 @@ {{_ "Sidebar"}}
+
+
+
+
+
+
diff --git a/packages/rocketchat-ui-account/client/accountPreferences.js b/packages/rocketchat-ui-account/client/accountPreferences.js
index 12c6d75691ee..067171f7b2e3 100644
--- a/packages/rocketchat-ui-account/client/accountPreferences.js
+++ b/packages/rocketchat-ui-account/client/accountPreferences.js
@@ -167,6 +167,7 @@ Template.accountPreferences.onCreated(function() {
data.desktopNotifications = $('#desktopNotifications').find('select').val();
data.mobileNotifications = $('#mobileNotifications').find('select').val();
data.unreadAlert = JSON.parse($('#unreadAlert').find('input:checked').val());
+ data.sidebarShowThreads = JSON.parse($('#sidebarShowThreads').find('input:checked').val());
data.notificationsSoundVolume = parseInt($('#notificationsSoundVolume').val());
data.roomCounterSidebar = JSON.parse($('#roomCounterSidebar').find('input:checked').val());
data.highlights = _.compact(_.map($('[name=highlights]').val().split(/,|\n/), function(e) {
diff --git a/packages/rocketchat-ui-flextab/client/tabs/userInfo.html b/packages/rocketchat-ui-flextab/client/tabs/userInfo.html
index bcd53aba47b2..7d3197e1a4fe 100644
--- a/packages/rocketchat-ui-flextab/client/tabs/userInfo.html
+++ b/packages/rocketchat-ui-flextab/client/tabs/userInfo.html
@@ -18,7 +18,7 @@
+
{{_ "User_Info"}}
{{> userEdit (userToEdit)}} {{else}} {{#with user}} -
@@ -114,7 +114,7 @@ {{_ "View_All"}}
{{/if}}
-
+
{{/with}}
{{/if}}
{{/if}}
diff --git a/packages/rocketchat-ui-master/public/icons.svg b/packages/rocketchat-ui-master/public/icons.svg
index 1d5ef6fc8244..254c9aba95b2 100644
--- a/packages/rocketchat-ui-master/public/icons.svg
+++ b/packages/rocketchat-ui-master/public/icons.svg
@@ -317,6 +317,9 @@
-
+ {{#if isThread}}
+ {{_ "View_All"}}
{{/if}}
-
+
{{/with}}
{{/if}}
{{/if}}
diff --git a/packages/rocketchat-ui-master/public/icons.svg b/packages/rocketchat-ui-master/public/icons.svg
index 1d5ef6fc8244..254c9aba95b2 100644
--- a/packages/rocketchat-ui-master/public/icons.svg
+++ b/packages/rocketchat-ui-master/public/icons.svg
@@ -317,6 +317,9 @@
+
+
+
@@ -353,4 +356,4 @@
-
\ No newline at end of file
+
diff --git a/packages/rocketchat-ui-master/public/icons/thread.svg b/packages/rocketchat-ui-master/public/icons/thread.svg
new file mode 100644
index 000000000000..7074dcf5cbad
--- /dev/null
+++ b/packages/rocketchat-ui-master/public/icons/thread.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/rocketchat-ui-message/client/message.js b/packages/rocketchat-ui-message/client/message.js
index 06856251b427..0fdc2e31531e 100644
--- a/packages/rocketchat-ui-message/client/message.js
+++ b/packages/rocketchat-ui-message/client/message.js
@@ -356,7 +356,7 @@ Template.message.helpers({
if (room && room.t === 'd') {
return 'at';
}
- return roomTypes.getIcon(room && room.t);
+ return roomTypes.getIcon(room);
},
fromSearch() {
return this.customClass === 'search';
diff --git a/packages/rocketchat-ui-message/client/popup/messagePopupChannel.js b/packages/rocketchat-ui-message/client/popup/messagePopupChannel.js
index b910867792d6..0f79195e3cb5 100644
--- a/packages/rocketchat-ui-message/client/popup/messagePopupChannel.js
+++ b/packages/rocketchat-ui-message/client/popup/messagePopupChannel.js
@@ -3,6 +3,6 @@ import { roomTypes } from 'meteor/rocketchat:utils';
Template.messagePopupChannel.helpers({
channelIcon() {
- return roomTypes.getIcon(this.t);
+ return roomTypes.getIcon(this);
},
});
diff --git a/packages/rocketchat-ui-sidenav/client/chatRoomItem.js b/packages/rocketchat-ui-sidenav/client/chatRoomItem.js
index c48339fee60b..563e69901c2c 100644
--- a/packages/rocketchat-ui-sidenav/client/chatRoomItem.js
+++ b/packages/rocketchat-ui-sidenav/client/chatRoomItem.js
@@ -9,15 +9,6 @@ import { callbacks } from 'meteor/rocketchat:callbacks';
Template.chatRoomItem.helpers({
roomData() {
- let { name } = this;
- if (this.fname) {
- const realNameForDirectMessages = this.t === 'd' && settings.get('UI_Use_Real_Name');
- const realNameForChannel = this.t !== 'd' && settings.get('UI_Allow_room_names_with_special_chars');
- if (realNameForDirectMessages || realNameForChannel) {
- name = this.fname;
- }
- }
-
const openedRoom = Tracker.nonreactive(() => Session.get('openedRoom'));
const unread = this.unread > 0 ? this.unread : false;
// if (this.unread > 0 && (!hasFocus || openedRoom !== this.rid)) {
@@ -30,16 +21,18 @@ Template.chatRoomItem.helpers({
this.alert = !this.hideUnreadStatus && this.alert; // && (!hasFocus || FlowRouter.getParam('_id') !== this.rid);
- const icon = roomTypes.getIcon(this.t);
+ const icon = roomTypes.getIcon(this);
const avatar = !icon;
+ const name = roomTypes.getRoomName(this.t, this);
+
const roomData = {
...this,
icon,
avatar,
username : this.name,
route: roomTypes.getRouteLink(this.t, this),
- name: name || roomTypes.getRoomName(this.t, this),
+ name,
unread,
active,
archivedClass,
@@ -47,6 +40,11 @@ Template.chatRoomItem.helpers({
};
roomData.username = roomData.username || roomData.name;
+ // hide icon for threads
+ if (this.prid) {
+ roomData.darken = true;
+ }
+
if (!this.lastMessage && settings.get('Store_Last_Message')) {
const room = Rooms.findOne(this.rid || this._id, { fields: { lastMessage: 1 } });
roomData.lastMessage = (room && room.lastMessage) || { msg: t('No_messages_yet') };
diff --git a/packages/rocketchat-ui-sidenav/client/roomList.js b/packages/rocketchat-ui-sidenav/client/roomList.js
index 137a73074302..2184d157c11f 100644
--- a/packages/rocketchat-ui-sidenav/client/roomList.js
+++ b/packages/rocketchat-ui-sidenav/client/roomList.js
@@ -23,6 +23,7 @@ Template.roomList.helpers({
'settings.preferences.sidebarSortby': 1,
'settings.preferences.sidebarShowFavorites': 1,
'settings.preferences.sidebarShowUnread': 1,
+ 'settings.preferences.sidebarShowThreads': 1,
'services.tokenpass': 1,
},
});
@@ -58,6 +59,11 @@ Template.roomList.helpers({
types = ['c', 'p', 'd'];
}
+ if (this.identifier === 'thread') {
+ types = ['c', 'p', 'd'];
+ query.prid = { $exists: true };
+ }
+
if (this.identifier === 'unread' || this.identifier === 'tokens') {
types = ['c', 'p'];
}
@@ -68,6 +74,11 @@ Template.roomList.helpers({
query.tokens = { $exists: true };
}
+ // if we display threads as a separate group, we should hide them from the other lists
+ if (getUserPreference(user, 'sidebarShowThreads')) {
+ query.prid = { $exists: false };
+ }
+
if (getUserPreference(user, 'sidebarShowUnread')) {
query.$or = [
{ alert: { $ne: true } },
diff --git a/packages/rocketchat-ui-sidenav/client/sideNav.js b/packages/rocketchat-ui-sidenav/client/sideNav.js
index af49b4c3ca3f..f81e5e3d22ad 100644
--- a/packages/rocketchat-ui-sidenav/client/sideNav.js
+++ b/packages/rocketchat-ui-sidenav/client/sideNav.js
@@ -64,7 +64,6 @@ Template.sideNav.events({
'dropped .sidebar'(e) {
return e.preventDefault();
},
-
'mouseenter .sidebar-item__link'(e) {
const element = e.currentTarget;
setTimeout(() => {
diff --git a/packages/rocketchat-ui-sidenav/client/sidebarHeader.js b/packages/rocketchat-ui-sidenav/client/sidebarHeader.js
index 609238cbf3fb..0848444c3b0c 100644
--- a/packages/rocketchat-ui-sidenav/client/sidebarHeader.js
+++ b/packages/rocketchat-ui-sidenav/client/sidebarHeader.js
@@ -8,7 +8,7 @@ import { AccountBox, menu, SideNav } from 'meteor/rocketchat:ui-utils';
import { callbacks } from 'meteor/rocketchat:callbacks';
import { settings } from 'meteor/rocketchat:settings';
import { hasAtLeastOnePermission } from 'meteor/rocketchat:authorization';
-
+import { modal } from 'meteor/rocketchat:ui-utils';
const setStatus = (status) => {
AccountBox.setStatus(status);
callbacks.run('userStatusManuallySet', status);
@@ -166,9 +166,60 @@ const toolbarButtons = (user) => [{
name: t('Create_A_New_Channel'),
icon: 'edit-rounded',
condition: () => hasAtLeastOnePermission(['create-c', 'create-p']),
- action: () => {
- menu.close();
- FlowRouter.go('create-channel');
+ action: (e) => {
+ const config = {
+ columns: [
+ {
+ groups: [
+ {
+ items: [
+ {
+ icon: 'hashtag',
+ name: t('Channel'),
+ action: (e) => {
+ e.preventDefault();
+ modal.open({
+ // title: t('Message_info'),
+ content: 'createChannel',
+ data: {
+ onCreate() {
+ modal.close();
+ },
+ },
+ showConfirmButton: false,
+ showCancelButton: false,
+ // confirmButtonText: t('Close'),
+ });
+ },
+ },
+ {
+ icon: 'thread',
+ name: t('Thread'),
+ action: (e) => {
+ e.preventDefault();
+ modal.open({
+ // title: t('Message_info'),
+ content: 'CreateThread',
+ data: {
+ onCreate() {
+ modal.close();
+ },
+ },
+ showConfirmButton: false,
+ showCancelButton: false,
+ // confirmButtonText: t('Close'),
+ });
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ currentTarget: e.currentTarget,
+ offsetVertical: e.currentTarget.clientHeight + 10,
+ };
+ popover.open(config);
},
},
{
diff --git a/packages/rocketchat-ui-utils/client/lib/RoomHistoryManager.js b/packages/rocketchat-ui-utils/client/lib/RoomHistoryManager.js
index 163dd46f1b18..2b259ecbb457 100644
--- a/packages/rocketchat-ui-utils/client/lib/RoomHistoryManager.js
+++ b/packages/rocketchat-ui-utils/client/lib/RoomHistoryManager.js
@@ -6,7 +6,7 @@ import _ from 'underscore';
import { RoomManager } from './RoomManager';
import { readMessage } from './readMessages';
-export const upsertMessage = ({ msg, subscription }) => {
+export const upsertMessage = ({ msg: { _id, ...msg }, subscription }) => {
const userId = msg.u && msg.u._id;
if (subscription && subscription.ignored && subscription.ignored.indexOf(userId) > -1) {
@@ -21,7 +21,7 @@ export const upsertMessage = ({ msg, subscription }) => {
msg.e2e = 'pending';
}
- return ChatMessage.upsert({ _id: msg._id }, msg);
+ return ChatMessage.upsert({ _id }, msg);
};
function upsertMessageBulk({ msgs, subscription }) {
diff --git a/packages/rocketchat-ui-utils/client/lib/modal.html b/packages/rocketchat-ui-utils/client/lib/modal.html
index 5abcd2ac5d81..ea16ca9777bd 100644
--- a/packages/rocketchat-ui-utils/client/lib/modal.html
+++ b/packages/rocketchat-ui-utils/client/lib/modal.html
@@ -1,6 +1,6 @@
-
+
+
diff --git a/packages/rocketchat-ui-utils/client/lib/modal.js b/packages/rocketchat-ui-utils/client/lib/modal.js
index 897316a2e1b9..4bc791d5677c 100644
--- a/packages/rocketchat-ui-utils/client/lib/modal.js
+++ b/packages/rocketchat-ui-utils/client/lib/modal.js
@@ -97,7 +97,7 @@ Template.rc_modal.helpers({
return !!this.action;
},
type() {
- return `rc-modal__content-icon rc-modal__content-icon--modal-${ this.type }`;
+ return this.type && `rc-modal__content-icon rc-modal__content-icon--modal-${ this.type }`;
},
modalIcon() {
diff --git a/packages/rocketchat-ui-utils/client/lib/popover.js b/packages/rocketchat-ui-utils/client/lib/popover.js
index e4ce3c2e4b5a..dbeee431400b 100644
--- a/packages/rocketchat-ui-utils/client/lib/popover.js
+++ b/packages/rocketchat-ui-utils/client/lib/popover.js
@@ -162,7 +162,7 @@ Template.popover.events({
const { id } = event.currentTarget.dataset;
const action = messageBox.actions.getById(id);
if ((action[0] != null ? action[0].action : undefined) != null) {
- action[0].action({ rid: t.data.data.rid, messageBox: document.querySelector('.rc-message-box'), element: event.currentTarget, event });
+ action[0].action({ rid: t.data.data.rid, ...t.data.data, messageBox: document.querySelector('.rc-message-box'), element: event.currentTarget, event });
if (id !== 'audio-message') {
popover.close();
}
diff --git a/packages/rocketchat-ui/client/components/header/headerRoom.html b/packages/rocketchat-ui/client/components/header/headerRoom.html
index 98e9d800b726..733b4e9166b7 100644
--- a/packages/rocketchat-ui/client/components/header/headerRoom.html
+++ b/packages/rocketchat-ui/client/components/header/headerRoom.html
@@ -6,9 +6,18 @@
{{> burger}}
+
+
+ {{> icon block="rc-header__icon rc-header__icon" icon="back"}}
+
+ {{/if}}
+
+ {{#if showToggleFavorite}}
+
+ {{/if}}
+
{{#if tokenAccessChannel}}
@@ -29,7 +38,7 @@
{{#unless secondaryName}}
-
{{> icon block="rc-header__icon" icon=channelIcon}}{{roomName}}
+ {{> icon block="rc-header__icon" icon=roomIcon}}{{roomName}}
{{else}}
{{roomName}}
{{/unless}}
diff --git a/packages/rocketchat-ui/client/components/header/headerRoom.js b/packages/rocketchat-ui/client/components/header/headerRoom.js
index 5ebd17d7143a..677a09209278 100644
--- a/packages/rocketchat-ui/client/components/header/headerRoom.js
+++ b/packages/rocketchat-ui/client/components/header/headerRoom.js
@@ -4,8 +4,9 @@ import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { t, roomTypes, handleError } from 'meteor/rocketchat:utils';
import { TabBar, fireGlobalEvent } from 'meteor/rocketchat:ui-utils';
-import { ChatSubscription, Rooms } from 'meteor/rocketchat:models';
+import { ChatSubscription, Rooms, ChatRoom } from 'meteor/rocketchat:models';
import { settings } from 'meteor/rocketchat:settings';
+import { FlowRouter } from 'meteor/kadira:flow-router';
import { emoji } from 'meteor/rocketchat:emoji';
import { Markdown } from 'meteor/rocketchat:markdown';
@@ -13,6 +14,12 @@ const isSubscribed = (_id) => ChatSubscription.find({ rid: _id }).count() > 0;
const favoritesEnabled = () => settings.get('Favorite_Rooms');
+const isThread = ({ _id }) => {
+ const room = ChatRoom.findOne({ _id });
+ return !!(room && room.prid);
+};
+
+
Template.headerRoom.helpers({
back() {
return Template.instance().data.back;
@@ -26,6 +33,10 @@ Template.headerRoom.helpers({
return TabBar.getButtons();
},
+ isThread() {
+ return isThread(Template.instance().data);
+ },
+
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);
@@ -80,27 +91,11 @@ Template.headerRoom.helpers({
return roomTopic;
},
- channelIcon() {
- const roomType = Rooms.findOne(this._id).t;
- switch (roomType) {
- case 'd':
- return 'at';
- case 'p':
- return 'lock';
- case 'c':
- return 'hashtag';
- case 'l':
- return 'livechat';
- default:
- return roomTypes.getIcon(roomType);
- }
- },
-
roomIcon() {
const roomData = Session.get(`roomData${ this._id }`);
if (!(roomData != null ? roomData.t : undefined)) { return ''; }
- return roomTypes.getIcon(roomData != null ? roomData.t : undefined);
+ return roomTypes.getIcon(roomData);
},
tokenAccessChannel() {
@@ -118,7 +113,7 @@ Template.headerRoom.helpers({
},
showToggleFavorite() {
- if (isSubscribed(this._id) && favoritesEnabled()) { return true; }
+ return !isThread(Template.instance().data) && isSubscribed(this._id) && favoritesEnabled();
},
fixedHeight() {
@@ -166,6 +161,12 @@ Template.headerRoom.events({
.select(),
10);
},
+
+ 'click .js-open-parent-channel'(event, t) {
+ event.preventDefault();
+ const { prid } = t.currentChannel;
+ FlowRouter.goToRoomById(prid);
+ },
});
Template.headerRoom.onCreated(function() {
diff --git a/packages/rocketchat-ui/client/lib/chatMessages.js b/packages/rocketchat-ui/client/lib/chatMessages.js
index 7de20a114303..d823bc06d8bc 100644
--- a/packages/rocketchat-ui/client/lib/chatMessages.js
+++ b/packages/rocketchat-ui/client/lib/chatMessages.js
@@ -359,9 +359,14 @@ export const ChatMessages = class ChatMessages {
confirmDeleteMsg(message, done = function() {}) {
if (MessageTypes.isSystemMessage(message)) { return; }
+
+ const room = message.trid && Rooms.findOne({
+ _id: message.trid,
+ prid: { $exists: true },
+ });
modal.open({
title: t('Are_you_sure'),
- text: t('You_will_not_be_able_to_recover'),
+ text: room ? t('The_message_is_a_thread_you_will_not_be_able_to_recover') : t('You_will_not_be_able_to_recover'),
type: 'warning',
showCancelButton: true,
confirmButtonColor: '#DD6B55',
diff --git a/packages/rocketchat-ui/client/views/app/createChannel.html b/packages/rocketchat-ui/client/views/app/createChannel.html
index 56e6499943f1..fd72cb64d751 100644
--- a/packages/rocketchat-ui/client/views/app/createChannel.html
+++ b/packages/rocketchat-ui/client/views/app/createChannel.html
@@ -10,7 +10,7 @@
@{{secondaryName}}
{{_ "Create_A_New_Channel"}}
{{_ "Channels_are_where_your_team_communicate"}}
-