Skip to content

Commit fb50b3e

Browse files
author
Daniel Schreiber
committed
Allow spaces on room names
Closes RocketChat#892 Closes RocketChat#7488 Fire global event for Load More during scroll
1 parent 485b623 commit fb50b3e

File tree

16 files changed

+138
-74
lines changed

16 files changed

+138
-74
lines changed

packages/rocketchat-channel-settings/client/startup/trackSettingsChange.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ Meteor.startup(function() {
2222
Tracker.nonreactive(() => {
2323
if (msg.t === 'r') {
2424
if (Session.get('openedRoom') === msg.rid) {
25-
const type = FlowRouter.current().route.name === 'channel' ? 'c' : 'p';
26-
RoomManager.close(type + FlowRouter.getParam('name'));
27-
FlowRouter.go(FlowRouter.current().route.name, { name: msg.msg }, FlowRouter.current().queryParams);
25+
const room = ChatRoom.findOne(msg.rid);
26+
if (room.name !== FlowRouter.getParam('name')) {
27+
RoomManager.close(room.t + FlowRouter.getParam('name'));
28+
RocketChat.roomTypes.openRouteLink(room.t, room, FlowRouter.current().queryParams);
29+
}
2830
}
2931
}
3032
});

packages/rocketchat-channel-settings/client/views/channelSettings.js

+22-11
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ Template.channelSettings.helpers({
1616
}
1717
return true;
1818
}
19+
if (this.$value.getValue) {
20+
return this.$value.getValue(obj, key);
21+
}
1922
return obj && obj[key];
2023
},
2124
showSetting(setting, room) {
@@ -152,22 +155,30 @@ Template.channelSettings.onCreated(function() {
152155
canEdit(room) {
153156
return RocketChat.authz.hasAllPermission('edit-room', room._id);
154157
},
158+
getValue(room) {
159+
if (RocketChat.settings.get('UI_Allow_room_names_with_special_chars')) {
160+
return room.fname || room.name;
161+
}
162+
return room.name;
163+
},
155164
save(value, room) {
156165
let nameValidation;
157166
if (!RocketChat.authz.hasAllPermission('edit-room', room._id) || (room.t !== 'c' && room.t !== 'p')) {
158167
return toastr.error(t('error-not-allowed'));
159168
}
160-
try {
161-
nameValidation = new RegExp(`^${ RocketChat.settings.get('UTF8_Names_Validation') }$`);
162-
} catch (error1) {
163-
nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$');
164-
}
165-
if (!nameValidation.test(value)) {
166-
return toastr.error(t('error-invalid-room-name', {
167-
room_name: {
168-
name: value
169-
}
170-
}));
169+
if (!RocketChat.settings.get('UI_Allow_room_names_with_special_chars')) {
170+
try {
171+
nameValidation = new RegExp(`^${ RocketChat.settings.get('UTF8_Names_Validation') }$`);
172+
} catch (error1) {
173+
nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$');
174+
}
175+
if (!nameValidation.test(value)) {
176+
return toastr.error(t('error-invalid-room-name', {
177+
room_name: {
178+
name: value
179+
}
180+
}));
181+
}
171182
}
172183
Meteor.call('saveRoomSettings', room._id, 'roomName', value, function(err) {
173184
if (err) {
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,21 @@
11

2-
RocketChat.saveRoomName = function(rid, name, user, sendMessage = true) {
2+
RocketChat.saveRoomName = function(rid, displayName, user, sendMessage = true) {
33
const room = RocketChat.models.Rooms.findOneById(rid);
44
if (room.t !== 'c' && room.t !== 'p') {
55
throw new Meteor.Error('error-not-allowed', 'Not allowed', {
6-
'function': 'RocketChat.saveRoomName'
6+
'function': 'RocketChat.saveRoomdisplayName'
77
});
88
}
9-
let nameValidation;
10-
try {
11-
nameValidation = new RegExp(`^${ RocketChat.settings.get('UTF8_Names_Validation') }$`);
12-
} catch (error) {
13-
nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$');
14-
}
15-
if (!nameValidation.test(name)) {
16-
throw new Meteor.Error('error-invalid-room-name', `${ name } is not a valid room name. Use only letters, numbers, hyphens and underscores`, {
17-
'function': 'RocketChat.saveRoomName',
18-
room_name: name
19-
});
20-
}
21-
if (name === room.name) {
9+
if (displayName === room.name) {
2210
return;
2311
}
24-
if (RocketChat.models.Rooms.findOneByName(name)) {
25-
throw new Meteor.Error('error-duplicate-channel-name', `A channel with name '${ name }' exists`, {
26-
'function': 'RocketChat.saveRoomName',
27-
channel_name: name
28-
});
29-
}
30-
const update = RocketChat.models.Rooms.setNameById(rid, name) && RocketChat.models.Subscriptions.updateNameAndAlertByRoomId(rid, name);
12+
13+
const slugifiedRoomName = RocketChat.getValidRoomName(displayName, rid);
14+
15+
const update = RocketChat.models.Rooms.setNameById(rid, slugifiedRoomName, displayName) && RocketChat.models.Subscriptions.updateNameAndAlertByRoomId(rid, slugifiedRoomName, displayName);
16+
3117
if (update && sendMessage) {
32-
RocketChat.models.Messages.createRoomRenamedWithRoomIdRoomNameAndUser(rid, name, user);
18+
RocketChat.models.Messages.createRoomRenamedWithRoomIdRoomNameAndUser(rid, displayName, user);
3319
}
34-
return name;
20+
return displayName;
3521
};

packages/rocketchat-i18n/i18n/en.i18n.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@
526526
"error-invalid-redirectUri": "Invalid redirectUri",
527527
"error-invalid-role": "Invalid role",
528528
"error-invalid-room": "Invalid room",
529-
"error-invalid-room-name": "<strong>__room_name__</strong> is not a valid room name,<br/> use only letters, numbers, hyphens and underscores",
529+
"error-invalid-room-name": "<strong>__room_name__</strong> is not a valid room name",
530530
"error-invalid-room-type": "<strong>__type__</strong> is not a valid room type.",
531531
"error-invalid-settings": "Invalid settings provided",
532532
"error-invalid-subscription": "Invalid subscription",
@@ -766,7 +766,7 @@
766766
"Invalid_name": "The name must not be empty",
767767
"Invalid_notification_setting_s": "Invalid notification setting: %s",
768768
"Invalid_pass": "The password must not be empty",
769-
"Invalid_room_name": "<strong>%s</strong> is not a valid room name,<br/> use only letters, numbers, hyphens and underscores",
769+
"Invalid_room_name": "<strong>%s</strong> is not a valid room name",
770770
"Invalid_secret_URL_message": "The URL provided is invalid.",
771771
"Invalid_setting_s": "Invalid setting: %s",
772772
"Invalid_two_factor_code": "Invalid two factor code",
@@ -1557,6 +1557,7 @@
15571557
"Type_your_message": "Type your message",
15581558
"Type_your_name": "Type your name",
15591559
"Type_your_new_password": "Type your new password",
1560+
"UI_Allow_room_names_with_special_chars": "Allow special chars on room names",
15601561
"UI_DisplayRoles": "Display Roles",
15611562
"UI_Merge_Channels_Groups": "Merge private groups with channels",
15621563
"UI_Use_Name_Avatar": "Use full name initials to generate default avatar",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
RocketChat.getValidRoomName = function getValidRoomName(displayName, rid = '') {
2+
let slugifiedName = displayName;
3+
4+
if (RocketChat.settings.get('UI_Allow_room_names_with_special_chars')) {
5+
const room = RocketChat.models.Rooms.findOneByDisplayName(displayName);
6+
if (room && room._id !== rid) {
7+
if (room.archived) {
8+
throw new Meteor.Error('error-archived-duplicate-name', `There's an archived channel with name ${ displayName }`, { function: 'RocketChat.getValidRoomName', channel_name: displayName });
9+
} else {
10+
throw new Meteor.Error('error-duplicate-channel-name', `A channel with name '${ displayName }' exists`, { function: 'RocketChat.getValidRoomName', channel_name: displayName });
11+
}
12+
}
13+
slugifiedName = s.slugify(displayName);
14+
}
15+
16+
let nameValidation;
17+
try {
18+
nameValidation = new RegExp(`^${ RocketChat.settings.get('UTF8_Names_Validation') }$`);
19+
} catch (error) {
20+
nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$');
21+
}
22+
if (!nameValidation.test(slugifiedName)) {
23+
throw new Meteor.Error('error-invalid-room-name', `${ slugifiedName } is not a valid room name.`, {
24+
'function': 'RocketChat.getValidRoomName',
25+
channel_name: slugifiedName
26+
});
27+
}
28+
29+
const room = RocketChat.models.Rooms.findOneByName(slugifiedName);
30+
if (room && room._id !== rid) {
31+
if (RocketChat.settings.get('UI_Allow_room_names_with_special_chars')) {
32+
let tmpName = slugifiedName;
33+
let next = 0;
34+
while (RocketChat.models.Rooms.findOneByNameAndNotId(tmpName, rid)) {
35+
tmpName = `${ slugifiedName }-${ ++next }`;
36+
}
37+
slugifiedName = tmpName;
38+
} else if (room.archived) {
39+
throw new Meteor.Error('error-archived-duplicate-name', `There's an archived channel with name ${ slugifiedName }`, { function: 'RocketChat.getValidRoomName', channel_name: slugifiedName });
40+
} else {
41+
throw new Meteor.Error('error-duplicate-channel-name', `A channel with name '${ slugifiedName }' exists`, { function: 'RocketChat.getValidRoomName', channel_name: slugifiedName });
42+
}
43+
}
44+
45+
return slugifiedName;
46+
};

packages/rocketchat-lib/package.js

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Package.onUse(function(api) {
5454
api.addFiles('lib/settings.js');
5555
api.addFiles('lib/callbacks.js');
5656
api.addFiles('lib/fileUploadRestrictions.js');
57+
api.addFiles('lib/getValidRoomName.js');
5758
api.addFiles('lib/placeholders.js');
5859
api.addFiles('lib/promises.js');
5960
api.addFiles('lib/roomTypesCommon.js');

packages/rocketchat-lib/server/functions/createRoom.js

+6-23
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,18 @@ RocketChat.createRoom = function(type, name, owner, members, readOnly, extraData
1313
throw new Meteor.Error('error-invalid-user', 'Invalid user', { function: 'RocketChat.createRoom' });
1414
}
1515

16-
let nameValidation;
17-
try {
18-
nameValidation = new RegExp(`^${ RocketChat.settings.get('UTF8_Names_Validation') }$`);
19-
} catch (error) {
20-
nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$');
21-
}
22-
23-
if (!nameValidation.test(name)) {
24-
throw new Meteor.Error('error-invalid-name', 'Invalid name', { function: 'RocketChat.createRoom' });
25-
}
16+
const slugifiedRoomName = RocketChat.getValidRoomName(name);
2617

2718
const now = new Date();
2819
if (!_.contains(members, owner.username)) {
2920
members.push(owner.username);
3021
}
3122

32-
// avoid duplicate names
33-
let room = RocketChat.models.Rooms.findOneByName(name);
34-
if (room) {
35-
if (room.archived) {
36-
throw new Meteor.Error('error-archived-duplicate-name', `There's an archived channel with name ${ name }`, { function: 'RocketChat.createRoom', room_name: name });
37-
} else {
38-
throw new Meteor.Error('error-duplicate-channel-name', `A channel with name '${ name }' exists`, { function: 'RocketChat.createRoom', room_name: name });
39-
}
40-
}
41-
4223
if (type === 'c') {
4324
RocketChat.callbacks.run('beforeCreateChannel', owner, {
4425
t: 'c',
45-
name,
26+
name: slugifiedRoomName,
27+
fname: name,
4628
ts: now,
4729
ro: readOnly === true,
4830
sysMes: readOnly !== true,
@@ -60,7 +42,7 @@ RocketChat.createRoom = function(type, name, owner, members, readOnly, extraData
6042
sysMes: readOnly !== true
6143
});
6244

63-
room = RocketChat.models.Rooms.createWithTypeNameUserAndUsernames(type, name, owner, members, extraData);
45+
const room = RocketChat.models.Rooms.createWithTypeNameUserAndUsernames(type, slugifiedRoomName, name, owner, members, extraData);
6446

6547
for (const username of members) {
6648
const member = RocketChat.models.Users.findOneByUsername(username, { fields: { username: 1 }});
@@ -95,6 +77,7 @@ RocketChat.createRoom = function(type, name, owner, members, readOnly, extraData
9577
}
9678

9779
return {
98-
rid: room._id
80+
rid: room._id,
81+
name: slugifiedRoomName
9982
};
10083
};

packages/rocketchat-lib/server/models/Rooms.js

+20-3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,21 @@ class ModelRooms extends RocketChat.models._Base {
3737
return this.findOne(query, options);
3838
}
3939

40+
findOneByNameAndNotId(name, rid) {
41+
const query = {
42+
_id: { $ne: rid },
43+
name
44+
};
45+
46+
return this.findOne(query);
47+
}
48+
49+
findOneByDisplayName(fname, options) {
50+
const query = {fname};
51+
52+
return this.findOne(query, options);
53+
}
54+
4055
findOneByNameAndType(name, type, options) {
4156
const query = {
4257
name,
@@ -490,12 +505,13 @@ class ModelRooms extends RocketChat.models._Base {
490505
return this.update(query, update);
491506
}
492507

493-
setNameById(_id, name) {
508+
setNameById(_id, name, fname) {
494509
const query = {_id};
495510

496511
const update = {
497512
$set: {
498-
name
513+
name,
514+
fname
499515
}
500516
};
501517

@@ -719,9 +735,10 @@ class ModelRooms extends RocketChat.models._Base {
719735
}
720736

721737
// INSERT
722-
createWithTypeNameUserAndUsernames(type, name, user, usernames, extraData) {
738+
createWithTypeNameUserAndUsernames(type, name, fname, user, usernames, extraData) {
723739
const room = {
724740
name,
741+
fname,
725742
t: type,
726743
usernames,
727744
msgs: 0,

packages/rocketchat-lib/server/models/Subscriptions.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,14 @@ class ModelSubscriptions extends RocketChat.models._Base {
281281
return this.update(query, update);
282282
}
283283

284-
updateNameAndAlertByRoomId(roomId, name) {
284+
updateNameAndAlertByRoomId(roomId, name, fname) {
285285
const query =
286286
{rid: roomId};
287287

288288
const update = {
289289
$set: {
290290
name,
291+
fname,
291292
alert: true
292293
}
293294
};
@@ -540,6 +541,7 @@ class ModelSubscriptions extends RocketChat.models._Base {
540541
ts: room.ts,
541542
rid: room._id,
542543
name: room.name,
544+
fname: room.fname,
543545
t: room.t,
544546
u: {
545547
_id: user._id,

packages/rocketchat-lib/server/startup/settings.js

+4
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,10 @@ RocketChat.settings.addGroup('Layout', function() {
10211021
type: 'boolean',
10221022
'public': true
10231023
});
1024+
this.add('UI_Allow_room_names_with_special_chars', false, {
1025+
type: 'boolean',
1026+
public: true
1027+
});
10241028
});
10251029
});
10261030

packages/rocketchat-lib/startup/defaultRoomTypes.js

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ RocketChat.roomTypes.add('c', 10, {
2525
},
2626

2727
roomName(roomData) {
28+
if (RocketChat.settings.get('UI_Allow_room_names_with_special_chars')) {
29+
return roomData.fname || roomData.name;
30+
}
2831
return roomData.name;
2932
},
3033

@@ -114,6 +117,9 @@ RocketChat.roomTypes.add('p', 20, {
114117
},
115118

116119
roomName(roomData) {
120+
if (RocketChat.settings.get('UI_Allow_room_names_with_special_chars')) {
121+
return roomData.fname || roomData.name;
122+
}
117123
return roomData.name;
118124
},
119125

packages/rocketchat-ui-sidenav/client/chatRoomItem.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ Template.chatRoomItem.helpers({
2020
},
2121

2222
name() {
23-
if (RocketChat.settings.get('UI_Use_Real_Name') && this.fname) {
23+
const realNameForDirectMessages = RocketChat.settings.get('UI_Use_Real_Name') && this.t === 'd';
24+
const realNameForChannel = RocketChat.settings.get('UI_Allow_room_names_with_special_chars') && this.t !== 'd';
25+
if ((realNameForDirectMessages || realNameForChannel) && this.fname) {
2426
return this.fname;
2527
}
2628

packages/rocketchat-ui-sidenav/client/createCombinedFlex.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ Template.createCombinedFlex.events({
111111
if (!err) {
112112
return Meteor.call(createRoute, name, instance.selectedUsers.get(), readOnly, function(err, result) {
113113
if (err) {
114-
if (err.error === 'error-invalid-name') {
114+
if (err.error === 'error-invalid-room-name') {
115115
instance.error.set({ invalid: true });
116116
return;
117117
}
@@ -130,10 +130,10 @@ Template.createCombinedFlex.events({
130130
SideNav.closeFlex(() => instance.clearForm());
131131

132132
if (!privateGroup) {
133-
RocketChat.callbacks.run('aftercreateCombined', { _id: result.rid, name });
133+
RocketChat.callbacks.run('aftercreateCombined', { _id: result.rid, name: result.name });
134134
}
135135

136-
return FlowRouter.go(successRoute, { name }, FlowRouter.current().queryParams);
136+
return FlowRouter.go(successRoute, { name: result.name }, FlowRouter.current().queryParams);
137137
});
138138
} else {
139139
return instance.error.set({ fields: err });

0 commit comments

Comments
 (0)