Merge remote-tracking branch 'origin/develop' into improvements/2fa-implementation

pull/6476/head
Rodrigo Nascimento 8 years ago
commit 2641efd7f7
  1. 1
      .eslintrc
  2. 6
      packages/rocketchat-analytics/client/trackEvents.js
  3. 2
      packages/rocketchat-api/server/v1/channels.js
  4. 14
      packages/rocketchat-api/server/v1/groups.js
  5. 10
      packages/rocketchat-channel-settings/client/startup/messageTypes.coffee
  6. 11
      packages/rocketchat-channel-settings/client/views/channelSettings.coffee
  7. 1
      packages/rocketchat-channel-settings/package.js
  8. 13
      packages/rocketchat-channel-settings/server/functions/saveRoomAnnouncement.js
  9. 5
      packages/rocketchat-channel-settings/server/methods/saveRoomSettings.coffee
  10. 2
      packages/rocketchat-file-upload/server/config/configFileUploadFileSystem.js
  11. 256
      packages/rocketchat-file/file.server.coffee
  12. 292
      packages/rocketchat-file/file.server.js
  13. 3
      packages/rocketchat-file/package.js
  14. 3
      packages/rocketchat-i18n/i18n/en.i18n.json
  15. 3
      packages/rocketchat-i18n/i18n/zh.i18n.json
  16. 10
      packages/rocketchat-lib/server/models/Rooms.coffee
  17. 26
      packages/rocketchat-mapview/client/mapview.coffee
  18. 27
      packages/rocketchat-mapview/client/mapview.js
  19. 5
      packages/rocketchat-mapview/package.js
  20. 3
      packages/rocketchat-mapview/server/settings.coffee
  21. 4
      packages/rocketchat-mapview/server/settings.js
  22. 55
      packages/rocketchat-mentions/client.coffee
  23. 41
      packages/rocketchat-mentions/client.js
  24. 5
      packages/rocketchat-mentions/package.js
  25. 56
      packages/rocketchat-mentions/server.coffee
  26. 49
      packages/rocketchat-mentions/server.js
  27. 63
      packages/rocketchat-message-attachments/client/messageAttachment.coffee
  28. 74
      packages/rocketchat-message-attachments/client/messageAttachment.js
  29. 2
      packages/rocketchat-message-attachments/package.js
  30. 3
      packages/rocketchat-slashcommands-invite/client.coffee
  31. 4
      packages/rocketchat-slashcommands-invite/client.js
  32. 5
      packages/rocketchat-slashcommands-invite/package.js
  33. 68
      packages/rocketchat-slashcommands-invite/server.coffee
  34. 82
      packages/rocketchat-slashcommands-invite/server.js
  35. 8
      packages/rocketchat-slashcommands-join/client.coffee
  36. 10
      packages/rocketchat-slashcommands-join/client.js
  37. 5
      packages/rocketchat-slashcommands-join/package.js
  38. 33
      packages/rocketchat-slashcommands-join/server.coffee
  39. 37
      packages/rocketchat-slashcommands-join/server.js
  40. 28
      packages/rocketchat-slashcommands-leave/leave.coffee
  41. 31
      packages/rocketchat-slashcommands-leave/leave.js
  42. 3
      packages/rocketchat-slashcommands-leave/package.js
  43. 16
      packages/rocketchat-slashcommands-me/me.coffee
  44. 18
      packages/rocketchat-slashcommands-me/me.js
  45. 3
      packages/rocketchat-slashcommands-me/package.js
  46. 3
      packages/rocketchat-slashcommands-msg/client.coffee
  47. 4
      packages/rocketchat-slashcommands-msg/client.js
  48. 5
      packages/rocketchat-slashcommands-msg/package.js
  49. 43
      packages/rocketchat-slashcommands-msg/server.coffee
  50. 46
      packages/rocketchat-slashcommands-msg/server.js
  51. 3
      packages/rocketchat-slashcommands-mute/client/mute.coffee
  52. 4
      packages/rocketchat-slashcommands-mute/client/mute.js
  53. 3
      packages/rocketchat-slashcommands-mute/client/unmute.coffee
  54. 4
      packages/rocketchat-slashcommands-mute/client/unmute.js
  55. 9
      packages/rocketchat-slashcommands-mute/package.js
  56. 40
      packages/rocketchat-slashcommands-mute/server/mute.coffee
  57. 45
      packages/rocketchat-slashcommands-mute/server/mute.js
  58. 40
      packages/rocketchat-slashcommands-mute/server/unmute.coffee
  59. 44
      packages/rocketchat-slashcommands-mute/server/unmute.js
  60. 20
      packages/rocketchat-theme/client/imports/base.less
  61. 7
      packages/rocketchat-ui-admin/client/rooms/adminRoomInfo.coffee
  62. 253
      packages/rocketchat-ui-master/client/main.coffee
  63. 301
      packages/rocketchat-ui-master/client/main.js
  64. 3
      packages/rocketchat-ui-master/package.js
  65. 47
      packages/rocketchat-ui-message/client/messageBox.coffee
  66. 1
      packages/rocketchat-ui-message/client/messageBox.html
  67. 3
      packages/rocketchat-ui-message/package.js
  68. 47
      packages/rocketchat-ui-vrecord/client/VRecDialog.coffee
  69. 63
      packages/rocketchat-ui-vrecord/client/VRecDialog.js
  70. 3
      packages/rocketchat-ui-vrecord/client/vrecord.js
  71. 3
      packages/rocketchat-ui-vrecord/package.js
  72. 1
      packages/rocketchat-ui/client/lib/chatMessages.coffee
  73. 6
      packages/rocketchat-ui/client/views/404/roomNotFound.coffee
  74. 8
      packages/rocketchat-ui/client/views/404/roomNotFound.js
  75. 5
      packages/rocketchat-ui/client/views/app/burger.coffee
  76. 10
      packages/rocketchat-ui/client/views/app/burger.js
  77. 5
      packages/rocketchat-ui/client/views/app/home.coffee
  78. 8
      packages/rocketchat-ui/client/views/app/home.js
  79. 13
      packages/rocketchat-ui/client/views/app/room.coffee
  80. 33
      packages/rocketchat-ui/client/views/app/room.html
  81. 10
      packages/rocketchat-ui/client/views/app/roomSearch.coffee
  82. 15
      packages/rocketchat-ui/client/views/app/roomSearch.js
  83. 23
      packages/rocketchat-ui/client/views/app/secretURL.coffee
  84. 29
      packages/rocketchat-ui/client/views/app/secretURL.js
  85. 16
      packages/rocketchat-ui/client/views/cmsPage.coffee
  86. 25
      packages/rocketchat-ui/client/views/cmsPage.js
  87. 20
      packages/rocketchat-ui/client/views/fxos.coffee
  88. 19
      packages/rocketchat-ui/client/views/fxos.js
  89. 1
      packages/rocketchat-ui/client/views/modal.coffee
  90. 1
      packages/rocketchat-ui/client/views/modal.js
  91. 19
      packages/rocketchat-ui/getAvatarUrlFromUsername.coffee
  92. 17
      packages/rocketchat-ui/getAvatarUrlFromUsername.js
  93. 18
      packages/rocketchat-ui/package.js
  94. 4
      packages/rocketchat-version/package.js
  95. 62
      packages/rocketchat-version/plugin/compile-version.coffee
  96. 74
      packages/rocketchat-version/plugin/compile-version.js
  97. 20
      packages/rocketchat-wordpress/common.coffee
  98. 30
      packages/rocketchat-wordpress/common.js
  99. 5
      packages/rocketchat-wordpress/package.js
  100. 9
      packages/rocketchat-wordpress/startup.coffee
  101. Some files were not shown because too many files have changed in this diff Show More

@ -48,6 +48,7 @@
"vars": "all",
"args": "after-used"
}],
"no-void": 2,
"no-var": 2,
"one-var": [2, "never"],
"no-lonely-if": 2,

@ -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 })`);

@ -22,7 +22,7 @@ RocketChat.API.v1.addRoute('channels.addAll', { authRequired: true }, {
const findResult = findChannelById({ roomId: this.bodyParams.roomId });
Meteor.runAsUser(this.userId, () => {
Meteor.call('addAllUserToRoom', findResult._id);
Meteor.call('addAllUserToRoom', findResult._id, this.bodyParams.activeUsersOnly);
});
return RocketChat.API.v1.success({

@ -22,6 +22,20 @@ function findPrivateGroupByIdOrName({ roomId, roomName, userId, checkedArchived
return roomSub;
}
RocketChat.API.v1.addRoute('groups.addAll', { authRequired: true }, {
post() {
const findResult = findPrivateGroupByIdOrName({ roomId: this.bodyParams.roomId, userId: this.userId });
Meteor.runAsUser(this.userId, () => {
Meteor.call('addAllUserToRoom', findResult.rid, this.bodyParams.activeUsersOnly);
});
return RocketChat.API.v1.success({
group: RocketChat.models.Rooms.findOneById(findResult.rid, { fields: RocketChat.API.v1.defaultFieldsToExclude })
});
}
});
RocketChat.API.v1.addRoute('groups.addModerator', { authRequired: true }, {
post() {
const findResult = findPrivateGroupByIdOrName({ roomId: this.bodyParams.roomId, userId: this.userId });

@ -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

@ -146,6 +146,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'

@ -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',

@ -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;
};

@ -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()

@ -21,7 +21,7 @@ const createFileSystemStore = _.debounce(function() {
return readStream.pipe(writeStream);
}
let stream = void 0;
let stream = undefined;
const identify = function(err, data) {
if (err != null) {

@ -1,256 +0,0 @@
Grid = Npm.require('gridfs-stream')
stream = Npm.require('stream')
fs = Npm.require('fs')
path = Npm.require('path')
mkdirp = Npm.require('mkdirp')
gm = Npm.require('gm')
exec = Npm.require('child_process').exec
# Fix problem with usernames being converted to object id
Grid.prototype.tryParseObjectId = -> false
RocketChatFile =
gm: gm
enabled: undefined
enable: ->
RocketChatFile.enabled = true
RocketChat.settings.updateOptionsById 'Accounts_AvatarResize', {alert: undefined}
disable: ->
RocketChatFile.enabled = false
RocketChat.settings.updateOptionsById 'Accounts_AvatarResize', {alert: 'The_image_resize_will_not_work_because_we_can_not_detect_ImageMagick_or_GraphicsMagick_installed_in_your_server'}
detectGM = ->
exec 'gm version', Meteor.bindEnvironment (error, stdout, stderr) ->
if not error? and stdout.indexOf('GraphicsMagick') > -1
RocketChatFile.enable()
RocketChat.Info.GraphicsMagick =
enabled: true
version: stdout
else
RocketChat.Info.GraphicsMagick =
enabled: false
exec 'convert -version', Meteor.bindEnvironment (error, stdout, stderr) ->
if not error? and stdout.indexOf('ImageMagick') > -1
if RocketChatFile.enabled isnt true
# Enable GM to work with ImageMagick if no GraphicsMagick
RocketChatFile.gm = RocketChatFile.gm.subClass({imageMagick: true})
RocketChatFile.enable()
RocketChat.Info.ImageMagick =
enabled: true
version: stdout
else
if RocketChatFile.enabled isnt true
RocketChatFile.disable()
RocketChat.Info.ImageMagick =
enabled: false
detectGM()
Meteor.methods
'detectGM': ->
detectGM()
return
RocketChatFile.bufferToStream = (buffer) ->
bufferStream = new stream.PassThrough()
bufferStream.end buffer
return bufferStream
RocketChatFile.dataURIParse = (dataURI) ->
imageData = dataURI.split ';base64,'
return {
image: imageData[1]
contentType: imageData[0].replace('data:', '')
}
RocketChatFile.addPassThrough = (st, fn) ->
pass = new stream.PassThrough()
fn pass, st
return pass
RocketChatFile.GridFS = class
constructor: (config={}) ->
{name, transformWrite} = config
name ?= 'file'
this.name = name
this.transformWrite = transformWrite
mongo = Package.mongo.MongoInternals.NpmModule
db = Package.mongo.MongoInternals.defaultRemoteCollectionDriver().mongo.db
this.store = new Grid(db, mongo)
this.findOneSync = Meteor.wrapAsync this.store.collection(this.name).findOne.bind this.store.collection(this.name)
this.removeSync = Meteor.wrapAsync this.store.remove.bind this.store
this.getFileSync = Meteor.wrapAsync this.getFile.bind this
findOne: (fileName) ->
return this.findOneSync {_id: fileName}
remove: (fileName) ->
return this.removeSync
_id: fileName
root: this.name
createWriteStream: (fileName, contentType) ->
self = this
ws = this.store.createWriteStream
_id: fileName
filename: fileName
mode: 'w'
root: this.name
content_type: contentType
if self.transformWrite?
ws = RocketChatFile.addPassThrough ws, (rs, ws) ->
file =
name: self.name
fileName: fileName
contentType: contentType
self.transformWrite file, rs, ws
ws.on 'close', ->
ws.emit 'end'
return ws
createReadStream: (fileName) ->
return this.store.createReadStream
_id: fileName
root: this.name
return undefined
getFileWithReadStream: (fileName) ->
file = this.findOne fileName
if not file?
return undefined
rs = this.createReadStream fileName
return {
readStream: rs
contentType: file.contentType
length: file.length
uploadDate: file.uploadDate
}
getFile: (fileName, cb) ->
file = this.getFileWithReadStream(fileName)
if not file
return cb()
data = []
file.readStream.on 'data', Meteor.bindEnvironment (chunk) ->
data.push chunk
file.readStream.on 'end', Meteor.bindEnvironment ->
cb null,
buffer: Buffer.concat(data)
contentType: file.contentType
length: file.length
uploadDate: file.uploadDate
deleteFile: (fileName) ->
file = this.findOne fileName
if not file?
return undefined
return this.remove fileName
RocketChatFile.FileSystem = class
constructor: (config={}) ->
{absolutePath, transformWrite} = config
absolutePath ?= '~/uploads'
this.transformWrite = transformWrite
if absolutePath.split(path.sep)[0] is '~'
homepath = process.env.HOME or process.env.HOMEPATH or process.env.USERPROFILE
if homepath?
absolutePath = absolutePath.replace '~', homepath
else
throw new Error('Unable to resolve "~" in path')
this.absolutePath = path.resolve absolutePath
mkdirp.sync this.absolutePath
this.statSync = Meteor.wrapAsync fs.stat.bind fs
this.unlinkSync = Meteor.wrapAsync fs.unlink.bind fs
this.getFileSync = Meteor.wrapAsync this.getFile.bind this
createWriteStream: (fileName, contentType) ->
self = this
ws = fs.createWriteStream path.join this.absolutePath, fileName
if self.transformWrite?
ws = RocketChatFile.addPassThrough ws, (rs, ws) ->
file =
fileName: fileName
contentType: contentType
self.transformWrite file, rs, ws
ws.on 'close', ->
ws.emit 'end'
return ws
createReadStream: (fileName) ->
return fs.createReadStream path.join this.absolutePath, fileName
stat: (fileName) ->
return this.statSync path.join this.absolutePath, fileName
remove: (fileName) ->
return this.unlinkSync path.join this.absolutePath, fileName
getFileWithReadStream: (fileName) ->
try
stat = this.stat fileName
rs = this.createReadStream fileName
return {
readStream: rs
# contentType: file.contentType
length: stat.size
}
catch e
return undefined
getFile: (fileName, cb) ->
file = this.getFileWithReadStream(fileName)
if not file
return cb()
data = []
file.readStream.on 'data', Meteor.bindEnvironment (chunk) ->
data.push chunk
file.readStream.on 'end', Meteor.bindEnvironment ->
buffer: Buffer.concat(data)
contentType: file.contentType
length: file.length
uploadDate: file.uploadDate
deleteFile: (fileName) ->
try
stat = this.stat fileName
return this.remove fileName
catch e
return undefined

@ -0,0 +1,292 @@
import Grid from 'gridfs-stream';
import stream from 'stream';
import fs from 'fs';
import path from 'path';
import mkdirp from 'mkdirp';
import gm from 'gm';
import {exec} from 'child_process';
// Fix problem with usernames being converted to object id
Grid.prototype.tryParseObjectId = function() {
return false;
};
//TODO: REMOVE RocketChatFile from globals
RocketChatFile = {
gm,
enabled: undefined,
enable() {
RocketChatFile.enabled = true;
return RocketChat.settings.updateOptionsById('Accounts_AvatarResize', {
alert: undefined
});
},
disable() {
RocketChatFile.enabled = false;
return RocketChat.settings.updateOptionsById('Accounts_AvatarResize', {
alert: 'The_image_resize_will_not_work_because_we_can_not_detect_ImageMagick_or_GraphicsMagick_installed_in_your_server'
});
}
};
const detectGM = function() {
return exec('gm version', Meteor.bindEnvironment(function(error, stdout) {
if ((error == null) && stdout.indexOf('GraphicsMagick') > -1) {
RocketChatFile.enable();
RocketChat.Info.GraphicsMagick = {
enabled: true,
version: stdout
};
} else {
RocketChat.Info.GraphicsMagick = {
enabled: false
};
}
return exec('convert -version', Meteor.bindEnvironment(function(error, stdout) {
if ((error == null) && stdout.indexOf('ImageMagick') > -1) {
if (RocketChatFile.enabled !== true) {
// Enable GM to work with ImageMagick if no GraphicsMagick
RocketChatFile.gm = RocketChatFile.gm.subClass({
imageMagick: true
});
RocketChatFile.enable();
}
return RocketChat.Info.ImageMagick = {
enabled: true,
version: stdout
};
} else {
if (RocketChatFile.enabled !== true) {
RocketChatFile.disable();
}
return RocketChat.Info.ImageMagick = {
enabled: false
};
}
}));
}));
};
detectGM();
Meteor.methods({
'detectGM'() {
detectGM();
}
});
RocketChatFile.bufferToStream = function(buffer) {
const bufferStream = new stream.PassThrough();
bufferStream.end(buffer);
return bufferStream;
};
RocketChatFile.dataURIParse = function(dataURI) {
const imageData = dataURI.split(';base64,');
return {
image: imageData[1],
contentType: imageData[0].replace('data:', '')
};
};
RocketChatFile.addPassThrough = function(st, fn) {
const pass = new stream.PassThrough();
fn(pass, st);
return pass;
};
RocketChatFile.GridFS = class {
constructor(config = {}) {
const {name = 'file', transformWrite} = config;
this.name = name;
this.transformWrite = transformWrite;
const mongo = Package.mongo.MongoInternals.NpmModule;
const db = Package.mongo.MongoInternals.defaultRemoteCollectionDriver().mongo.db;
this.store = new Grid(db, mongo);
this.findOneSync = Meteor.wrapAsync(this.store.collection(this.name).findOne.bind(this.store.collection(this.name)));
this.removeSync = Meteor.wrapAsync(this.store.remove.bind(this.store));
this.getFileSync = Meteor.wrapAsync(this.getFile.bind(this));
}
findOne(fileName) {
return this.findOneSync({
_id: fileName
});
}
remove(fileName) {
return this.removeSync({
_id: fileName,
root: this.name
});
}
createWriteStream(fileName, contentType) {
const self = this;
let ws = this.store.createWriteStream({
_id: fileName,
filename: fileName,
mode: 'w',
root: this.name,
content_type: contentType
});
if (self.transformWrite != null) {
ws = RocketChatFile.addPassThrough(ws, function(rs, ws) {
const file = {
name: self.name,
fileName,
contentType
};
return self.transformWrite(file, rs, ws);
});
}
ws.on('close', function() {
return ws.emit('end');
});
return ws;
}
createReadStream(fileName) {
return this.store.createReadStream({
_id: fileName,
root: this.name
});
}
getFileWithReadStream(fileName) {
const file = this.findOne(fileName);
if (file == null) {
return null;
}
const rs = this.createReadStream(fileName);
return {
readStream: rs,
contentType: file.contentType,
length: file.length,
uploadDate: file.uploadDate
};
}
getFile(fileName, cb) {
const file = this.getFileWithReadStream(fileName);
if (!file) {
return cb();
}
const data = [];
file.readStream.on('data', Meteor.bindEnvironment(function(chunk) {
return data.push(chunk);
}));
return file.readStream.on('end', Meteor.bindEnvironment(function() {
return cb(null, {
buffer: Buffer.concat(data),
contentType: file.contentType,
length: file.length,
uploadDate: file.uploadDate
});
}));
}
deleteFile(fileName) {
const file = this.findOne(fileName);
if (file == null) {
return undefined;
}
return this.remove(fileName);
}
};
RocketChatFile.FileSystem = class {
constructor(config = {}) {
let {absolutePath = '~/uploads'} = config;
const {transformWrite} = config;
this.transformWrite = transformWrite;
if (absolutePath.split(path.sep)[0] === '~') {
const homepath = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
if (homepath != null) {
absolutePath = absolutePath.replace('~', homepath);
} else {
throw new Error('Unable to resolve "~" in path');
}
}
this.absolutePath = path.resolve(absolutePath);
mkdirp.sync(this.absolutePath);
this.statSync = Meteor.wrapAsync(fs.stat.bind(fs));
this.unlinkSync = Meteor.wrapAsync(fs.unlink.bind(fs));
this.getFileSync = Meteor.wrapAsync(this.getFile.bind(this));
}
createWriteStream(fileName, contentType) {
const self = this;
let ws = fs.createWriteStream(path.join(this.absolutePath, fileName));
if (self.transformWrite != null) {
ws = RocketChatFile.addPassThrough(ws, function(rs, ws) {
const file = {
fileName,
contentType
};
return self.transformWrite(file, rs, ws);
});
}
ws.on('close', function() {
return ws.emit('end');
});
return ws;
}
createReadStream(fileName) {
return fs.createReadStream(path.join(this.absolutePath, fileName));
}
stat(fileName) {
return this.statSync(path.join(this.absolutePath, fileName));
}
remove(fileName) {
return this.unlinkSync(path.join(this.absolutePath, fileName));
}
getFileWithReadStream(fileName) {
try {
const stat = this.stat(fileName);
const rs = this.createReadStream(fileName);
return {
readStream: rs,
// contentType: file.contentType
length: stat.size
};
} catch (error1) {
return null;
}
}
getFile(fileName, cb) {
const file = this.getFileWithReadStream(fileName);
if (!file) {
return cb();
}
const data = [];
file.readStream.on('data', Meteor.bindEnvironment(function(chunk) {
return data.push(chunk);
}));
return file.readStream.on('end', Meteor.bindEnvironment(function() {
return {
buffer: Buffer.concat(data)({
contentType: file.contentType,
length: file.length,
uploadDate: file.uploadDate
})
};
}));
}
deleteFile(fileName) {
try {
return this.remove(fileName);
} catch (error1) {
return null;
}
}
};

@ -8,10 +8,9 @@ Package.describe({
Package.onUse(function(api) {
api.use('rocketchat:lib');
api.use('rocketchat:version');
api.use('coffeescript');
api.use('ecmascript');
api.addFiles('file.server.coffee', 'server');
api.addFiles('file.server.js', 'server');
api.export('RocketChatFile', 'server');
});

@ -162,6 +162,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?",
@ -1235,10 +1236,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: <em>__room_announcement__</em> by <em>__user_by__</em>",
"room_changed_description": "Room description changed to: <em>__room_description__</em> by <em>__user_by__</em>",
"room_changed_privacy": "Room type changed to: <em>__room_type__</em> by <em>__user_by__</em>",
"room_changed_topic": "Room topic changed to: <em>__room_topic__</em> by <em>__user_by__</em>",

@ -161,6 +161,7 @@
"and": "和",
"And_more": "以及其它 __length__ 条",
"Animals_and_Nature": "动物与自然",
"Announcement": "公告",
"API": "API",
"API_Allow_Infinite_Count": "允许获取一切",
"API_Allow_Infinite_Count_Description": "是否允许在一次 REST API 调用中返回所有内容?",
@ -1106,10 +1107,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": "<em>__user_by__</em> 将公告修改为:<em>__room_announcement__</em>",
"room_changed_description": "房间描述由 <em>__user_by__</em> 修改为:<em>__room_description__</em>",
"room_changed_privacy": "<em>__user_by__</em> 将房间类型修改为:<em>__room_type__</em>",
"room_changed_topic": "<em>__user_by__</em> 将房间主题修改为:<em>__room_topic__</em>",

@ -500,6 +500,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

@ -1,26 +0,0 @@
###
# MapView is a named function that will replace geolocation in messages with a Google Static Map
# @param {Object} message - The message object
###
class MapView
constructor: (message) ->
# get MapView settings
mv_googlekey = RocketChat.settings.get 'MapView_GMapsAPIKey'
if message.location
# GeoJSON is reversed - ie. [lng, lat]
longitude = message.location.coordinates[0]
latitude = message.location.coordinates[1]
# confirm we have an api key set, and generate the html required for the mapview
if mv_googlekey?.length
message.html = '<a href="https://maps.google.com/maps?daddr='+latitude+','+longitude+'" target="_blank"><img src="https://maps.googleapis.com/maps/api/staticmap?zoom=14&size=250x250&markers=color:gray%7Clabel:%7C'+latitude+','+longitude+'&key='+mv_googlekey+'" /></a>'
else
message.html = '<a href="https://maps.google.com/maps?daddr='+latitude+','+longitude+'" target="_blank">'+TAPi18n.__('Shared_Location')+'</a>'
return message
RocketChat.callbacks.add 'renderMessage', MapView, RocketChat.callbacks.priority.HIGH

@ -0,0 +1,27 @@
/*
* MapView is a named function that will replace geolocation in messages with a Google Static Map
* @param {Object} message - The message object
*/
function MapView(message) {
// get MapView settings
const mv_googlekey = RocketChat.settings.get('MapView_GMapsAPIKey');
if (message.location) {
// GeoJSON is reversed - ie. [lng, lat]
const [longitude, latitude] = message.location.coordinates;
// confirm we have an api key set, and generate the html required for the mapview
if (mv_googlekey && mv_googlekey.length) {
message.html = `<a href="https://maps.google.com/maps?daddr=${ latitude },${ longitude }" target="_blank"><img src="https://maps.googleapis.com/maps/api/staticmap?zoom=14&size=250x250&markers=color:gray%7Clabel:%7C${ latitude },${ longitude }&key=${ mv_googlekey }" /></a>`;
} else {
message.html = `<a href="https://maps.google.com/maps?daddr=${ latitude },${ longitude }" target="_blank">${ TAPi18n.__('Shared_Location') }</a>`;
}
}
return message;
}
RocketChat.callbacks.add('renderMessage', MapView, RocketChat.callbacks.priority.HIGH);

@ -7,12 +7,11 @@ Package.describe({
Package.onUse(function(api) {
api.use([
'ecmascript',
'coffeescript',
'rocketchat:lib'
]);
api.addFiles('server/settings.coffee', 'server');
api.addFiles('server/settings.js', 'server');
api.addFiles('client/mapview.coffee', 'client');
api.addFiles('client/mapview.js', 'client');
});

@ -1,3 +0,0 @@
Meteor.startup ->
RocketChat.settings.add 'MapView_Enabled', false, {type: 'boolean', group: 'Message', section: 'Google Maps', public: true, i18nLabel: 'MapView_Enabled', i18nDescription: 'MapView_Enabled_Description'}
RocketChat.settings.add 'MapView_GMapsAPIKey', '', {type: 'string', group: 'Message', section: 'Google Maps', public: true, i18nLabel: 'MapView_GMapsAPIKey', i18nDescription: 'MapView_GMapsAPIKey_Description'}

@ -0,0 +1,4 @@
Meteor.startup(function() {
RocketChat.settings.add('MapView_Enabled', false, {type: 'boolean', group: 'Message', section: 'Google Maps', public: true, i18nLabel: 'MapView_Enabled', i18nDescription: 'MapView_Enabled_Description'});
return RocketChat.settings.add('MapView_GMapsAPIKey', '', {type: 'string', group: 'Message', section: 'Google Maps', public: true, i18nLabel: 'MapView_GMapsAPIKey', i18nDescription: 'MapView_GMapsAPIKey_Description'});
});

@ -1,55 +0,0 @@
###
# Mentions is a named function that will process Mentions
# @param {Object} message - The message object
###
class MentionsClient
constructor: (message) ->
if _.trim message.html
msg = message.html
mentions = []
msgMentionRegex = new RegExp '(?:^|\\s|\\n)(?:@)(' + RocketChat.settings.get('UTF8_Names_Validation') + ')', 'g'
message.msg.replace msgMentionRegex, (match, mention) ->
mentions.push mention
me = Meteor.user()?.username
if mentions.length isnt 0
mentions = _.unique mentions
mentions = mentions.join('|')
msg = msg.replace new RegExp("(?:^|\\s|\\n)(@(#{mentions}):?)[:.,\s]?", 'g'), (match, mention, username) ->
if username is 'all' or username is 'here'
return match.replace mention, "<a class=\"mention-link mention-link-me mention-link-all background-attention-color\">#{mention}</a>"
if not message.temp?
if not _.findWhere(message.mentions, {username: username})?
return match
classes = 'mention-link'
if username is me
classes += ' mention-link-me background-primary-action-color'
return match.replace mention, "<a class=\"#{classes}\" data-username=\"#{username}\">#{mention}</a>"
channels = []
msgChannelRegex = new RegExp '(?:^|\\s|\\n)(?:#)(' + RocketChat.settings.get('UTF8_Names_Validation') + ')', 'g'
message.msg.replace msgChannelRegex, (match, mention) ->
channels.push mention
if channels.length isnt 0
channels = _.unique channels
channels = channels.join('|')
msg = msg.replace new RegExp("(?:^|\\s|\\n)(#(#{channels}))[:.,\s]?", 'g'), (match, mention, channel) ->
if not message.temp?
if not _.findWhere(message.channels, {name: channel})?
return match
return match.replace mention, "<a class=\"mention-link\" data-channel=\"#{channel}\">#{mention}</a>"
message.html = msg
return message
RocketChat.callbacks.add 'renderMessage', MentionsClient, RocketChat.callbacks.priority.MEDIUM, 'mentions-message'
RocketChat.callbacks.add 'renderMentions', MentionsClient, RocketChat.callbacks.priority.MEDIUM, 'mentions-mentions'

@ -0,0 +1,41 @@
/*
* Mentions is a named function that will process Mentions
* @param {Object} message - The message object
*/
function MentionsClient(message) {
let msg = (message && message.html) || '';
if (!msg.trim()) {
return message;
}
const msgMentionRegex = new RegExp(`(?:^|\\s|\\n)(@(${ RocketChat.settings.get('UTF8_Names_Validation') }):?)[:.,\s]?`, 'g');
let me = Meteor.user();
me = me ? me.username : null;
msg = msg.replace(msgMentionRegex, function(match, mention, username) {
if (['all', 'here'].includes(username)) {
return match.replace(mention, `<a class="mention-link mention-link-me mention-link-all background-attention-color">${ mention }</a>`);
}
if (message.temp == null && _.findWhere(message.mentions, {username}) == null) {
return match;
}
return match.replace(mention, `<a class="mention-link ${ username === me ? 'mention-link-me background-primary-action-color':'' }" data-username="${ username }">${ mention }</a>`);
});
const msgChannelRegex = new RegExp(`(?:^|\\s|\\n)(#(${ RocketChat.settings.get('UTF8_Names_Validation') }))[:.,\s]?`, 'g');
msg = msg.replace(msgChannelRegex, function(match, mention, name) {
if (message.temp == null && _.findWhere(message.channels, {name}) == null) {
return match;
}
return match.replace(mention, `<a class="mention-link" data-channel="${ name }">${ mention }</a>`);
});
message.html = msg;
return message;
}
RocketChat.callbacks.add('renderMessage', MentionsClient, RocketChat.callbacks.priority.MEDIUM, 'mentions-message');
RocketChat.callbacks.add('renderMentions', MentionsClient, RocketChat.callbacks.priority.MEDIUM, 'mentions-mentions');

@ -8,10 +8,9 @@ Package.describe({
Package.onUse(function(api) {
api.use([
'ecmascript',
'coffeescript',
'rocketchat:lib'
]);
api.addFiles('server.coffee', 'server');
api.addFiles('client.coffee', 'client');
api.addFiles('server.js', 'server');
api.addFiles('client.js', 'client');
});

@ -1,56 +0,0 @@
###
# Mentions is a named function that will process Mentions
# @param {Object} message - The message object
###
class MentionsServer
constructor: (message) ->
# If message starts with /me, replace it for text formatting
mentions = []
msgMentionRegex = new RegExp '(?:^|\\s|\\n)(?:@)(' + RocketChat.settings.get('UTF8_Names_Validation') + ')', 'g'
message.msg.replace msgMentionRegex, (match, mention) ->
mentions.push mention
if mentions.length isnt 0
mentions = _.unique mentions
verifiedMentions = []
mentions.forEach (mention) ->
if mention is 'all'
messageMaxAll = RocketChat.settings.get('Message_MaxAll')
if messageMaxAll > 0
allChannel = RocketChat.models.Rooms.findOneById message.rid
if allChannel.usernames.length <= messageMaxAll
verifiedMention =
_id: mention
username: mention
else
verifiedMention =
_id: mention
username: mention
else if mention is 'here'
verifiedMention =
_id: mention
username: mention
else
verifiedMention = Meteor.users.findOne({username: mention}, {fields: {_id: 1, username: 1}})
verifiedMentions.push verifiedMention if verifiedMention?
if verifiedMentions.length isnt 0
message.mentions = verifiedMentions
channels = []
msgChannelRegex = new RegExp '(?:^|\\s|\\n)(?:#)(' + RocketChat.settings.get('UTF8_Names_Validation') + ')', 'g'
message.msg.replace msgChannelRegex, (match, mention) ->
channels.push mention
if channels.length isnt 0
channels = _.unique channels
verifiedChannels = []
channels.forEach (mention) ->
verifiedChannel = RocketChat.models.Rooms.findOneByNameAndType(mention, 'c', { fields: {_id: 1, name: 1 } })
verifiedChannels.push verifiedChannel if verifiedChannel?
if verifiedChannels.length isnt 0
message.channels = verifiedChannels
return message
RocketChat.callbacks.add 'beforeSaveMessage', MentionsServer, RocketChat.callbacks.priority.HIGH, 'mentions'

@ -0,0 +1,49 @@
/*
* Mentions is a named function that will process Mentions
* @param {Object} message - The message object
*/
function MentionsServer(message) {
const msgMentionRegex = new RegExp(`(?:^|\\s|\\n)(?:@)(${ RocketChat.settings.get('UTF8_Names_Validation') })`, 'g');
const mentionsAll = [];
const userMentions = [];
let mentions = message.msg.match(msgMentionRegex);
if (mentions) {
mentions.forEach((m) => {
const mention = m.trim().substr(1);
if (mention !== 'all' && mention !== 'here') {
return userMentions.push(mention);
}
if (mention === 'all') {
const messageMaxAll = RocketChat.settings.get('Message_MaxAll');
const allChannel = RocketChat.models.Rooms.findOneById(message.rid);
if (messageMaxAll !== 0 && allChannel.usernames.length >= messageMaxAll) {
return;
}
}
mentionsAll.push({
_id: mention,
username: mention
});
});
mentions = userMentions.length ? Meteor.users.find({ username: {$in: _.unique(userMentions)}}, { fields: {_id: true, username: true }}).fetch() : [];
const verifiedMentions = [...mentionsAll, ...mentions];
if (verifiedMentions.length !== 0) {
message.mentions = verifiedMentions;
}
}
const msgChannelRegex = new RegExp(`(?:^|\\s|\\n)(?:#)(${ RocketChat.settings.get('UTF8_Names_Validation') })`, 'g');
let channels = message.msg.match(msgChannelRegex);
if (channels) {
channels = channels.map(c => c.trim().substr(1));
const verifiedChannels = RocketChat.models.Rooms.find({ name: {$in: _.unique(channels)}, t: 'c' }, { fields: {_id: 1, name: 1 }}).fetch();
if (verifiedChannels.length !== 0) {
message.channels = verifiedChannels;
}
}
return message;
}
RocketChat.callbacks.add('beforeSaveMessage', MentionsServer, RocketChat.callbacks.priority.HIGH, 'mentions');

@ -1,63 +0,0 @@
import moment from 'moment'
fixCordova = (url) ->
if url?.indexOf('data:image') is 0
return url
if Meteor.isCordova and url?[0] is '/'
url = Meteor.absoluteUrl().replace(/\/$/, '') + url
query = "rc_uid=#{Meteor.userId()}&rc_token=#{Meteor._localStorage.getItem('Meteor.loginToken')}"
if url.indexOf('?') is -1
url = url + '?' + query
else
url = url + '&' + query
if Meteor.settings.public.sandstorm or url.match /^(https?:)?\/\//i
return url
else if navigator.userAgent.indexOf('Electron') > -1
return __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + url
else
return Meteor.absoluteUrl().replace(/\/$/, '') + __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + url
Template.messageAttachment.helpers
fixCordova: fixCordova
parsedText: ->
renderMessageBody { msg: this.text }
loadImage: ->
if Meteor.user()?.settings?.preferences?.autoImageLoad is false and this.downloadImages? is not true
return false
if Meteor.Device.isPhone() and Meteor.user()?.settings?.preferences?.saveMobileBandwidth and this.downloadImages? is not true
return false
return true
getImageHeight: (height) ->
return height or 200
color: ->
switch @color
when 'good' then return '#35AC19'
when 'warning' then return '#FCB316'
when 'danger' then return '#D30230'
else return @color
collapsed: ->
if this.collapsed?
return this.collapsed
else
return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true
time: ->
messageDate = new Date(@ts)
today = new Date()
if messageDate.toDateString() is today.toDateString()
return moment(@ts).format(RocketChat.settings.get('Message_TimeFormat'))
else
return moment(@ts).format(RocketChat.settings.get('Message_TimeAndDateFormat'))
injectIndex: (data, previousIndex, index) ->
data.index = previousIndex + '.attachments.' + index
return

@ -0,0 +1,74 @@
import moment from 'moment';
const colors = {
good: '#35AC19',
warning: '#FCB316',
danger: '#D30230'
};
const fixCordova = function(url) {
if (url && url.indexOf('data:image') === 0) {
return url;
}
if (Meteor.isCordova && (url && url[0] === '/')) {
url = Meteor.absoluteUrl().replace(/\/$/, '') + url;
const query = `rc_uid=${ Meteor.userId() }&rc_token=${ Meteor._localStorage.getItem('Meteor.loginToken') }`;
if (url.indexOf('?') === -1) {
url = `${ url }?${ query }`;
} else {
url = `${ url }&${ query }`;
}
}
if (Meteor.settings['public'].sandstorm || url.match(/^(https?:)?\/\//i)) {
return url;
} else if (navigator.userAgent.indexOf('Electron') > -1) {
return __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + url;
} else {
return Meteor.absoluteUrl().replace(/\/$/, '') + __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + url;
}
};
/*globals renderMessageBody*/
Template.messageAttachment.helpers({
fixCordova,
parsedText() {
return renderMessageBody({
msg: this.text
});
},
loadImage() {
const user = Meteor.user();
if (user && user.settings && user.settings.preferences && this.downloadImages !== true) {
if (user.settings.preferences.autoImageLoad === false) {
return false;
}
if (Meteor.Device.isPhone() && user.settings.preferences.saveMobileBandwidth !== true) {
return false;
}
}
return true;
},
getImageHeight(height = 200) {
return height;
},
color() {
return colors[this.color] || this.color;
},
collapsed() {
if (this.collapsed != null) {
return this.collapsed;
} else {
const user = Meteor.user();
return user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault === true;
}
},
time() {
const messageDate = new Date(this.ts);
const today = new Date();
if (messageDate.toDateString() === today.toDateString()) {
return moment(this.ts).format(RocketChat.settings.get('Message_TimeFormat'));
} else {
return moment(this.ts).format(RocketChat.settings.get('Message_TimeAndDateFormat'));
}
},
injectIndex(data, previousIndex, index) {
data.index = `${ previousIndex }.attachments.${ index }`;
}
});

@ -16,7 +16,7 @@ Package.onUse(function(api) {
]);
api.addFiles('client/messageAttachment.html', 'client');
api.addFiles('client/messageAttachment.coffee', 'client');
api.addFiles('client/messageAttachment.js', 'client');
// stylesheets
api.addFiles('client/stylesheets/messageAttachments.less', 'client');

@ -1,3 +0,0 @@
RocketChat.slashCommands.add 'invite', undefined,
description: 'Invite_user_to_join_channel'
params: '@username'

@ -0,0 +1,4 @@
RocketChat.slashCommands.add('invite', undefined, {
description: 'Invite_user_to_join_channel',
params: '@username'
});

@ -9,13 +9,12 @@ Package.onUse(function(api) {
api.use([
'ecmascript',
'coffeescript',
'check',
'rocketchat:lib'
]);
api.use('templating', 'client');
api.addFiles('client.coffee', 'client');
api.addFiles('server.coffee', 'server');
api.addFiles('client.js', 'client');
api.addFiles('server.js', 'server');
});

@ -1,68 +0,0 @@
###
# Invite is a named function that will replace /invite commands
# @param {Object} message - The message object
###
class Invite
constructor: (command, params, item) ->
if command isnt 'invite' or not Match.test params, String
return
usernames = params.replace(/@/g, '').split(/[\s,]/).filter((a) -> '' != a)
if 0 == usernames.length
return
users = Meteor.users.find({ username: { $in: usernames }})
currentUser = Meteor.users.findOne Meteor.userId()
if 0 == users.count()
RocketChat.Notifications.notifyUser Meteor.userId(), 'message', {
_id: Random.id()
rid: item.rid
ts: new Date
msg: TAPi18n.__('User_doesnt_exist', { postProcess: 'sprintf', sprintf: [ usernames.join(' @') ] }, currentUser.language)
}
return
usernames = usernames.filter((username) ->
if not RocketChat.models.Rooms.findOneByIdContainingUsername(item.rid, username)?
return true
# Cancel if user is already in this room
RocketChat.Notifications.notifyUser Meteor.userId(), 'message', {
_id: Random.id()
rid: item.rid
ts: new Date
msg: TAPi18n.__('Username_is_already_in_here', { postProcess: 'sprintf', sprintf: [ username ] }, currentUser.language)
}
return false
)
# Cancel if all users is already in this room
if 0 == usernames.length
return
users.forEach((user) ->
try
Meteor.call 'addUserToRoom',
rid: item.rid
username: user.username
catch e
if e.error is 'cant-invite-for-direct-room'
RocketChat.Notifications.notifyUser Meteor.userId(), 'message', {
_id: Random.id()
rid: item.rid
ts: new Date
msg: TAPi18n.__('Cannot_invite_users_to_direct_rooms', null, currentUser.language)
}
else
RocketChat.Notifications.notifyUser Meteor.userId(), 'message', {
_id: Random.id()
rid: item.rid
ts: new Date
msg: TAPi18n.__(e.error, null, currentUser.language)
}
return
)
RocketChat.slashCommands.add 'invite', Invite

@ -0,0 +1,82 @@
/*
* Invite is a named function that will replace /invite commands
* @param {Object} message - The message object
*/
function Invite(command, params, item) {
if (command !== 'invite' || !Match.test(params, String)) {
return;
}
let usernames = params.replace(/@/g, '').split(/[\s,]/).filter((a) => a !== '');
if (usernames.length === 0) {
return;
}
const users = Meteor.users.find({
username: {
$in: usernames
}
});
const currentUser = Meteor.users.findOne(Meteor.userId());
if (users.count() === 0) {
RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: item.rid,
ts: new Date,
msg: TAPi18n.__('User_doesnt_exist', {
postProcess: 'sprintf',
sprintf: [usernames.join(' @')]
}, currentUser.language)
});
return;
}
usernames = usernames.filter(function(username) {
if (RocketChat.models.Rooms.findOneByIdContainingUsername(item.rid, username) == null) {
return true;
}
RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: item.rid,
ts: new Date,
msg: TAPi18n.__('Username_is_already_in_here', {
postProcess: 'sprintf',
sprintf: [username]
}, currentUser.language)
});
return false;
});
if (usernames.length === 0) {
return;
}
users.forEach(function(user) {
try {
return Meteor.call('addUserToRoom', {
rid: item.rid,
username: user.username
});
} catch ({error}) {
if (error === 'cant-invite-for-direct-room') {
RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: item.rid,
ts: new Date,
msg: TAPi18n.__('Cannot_invite_users_to_direct_rooms', null, currentUser.language)
});
} else {
RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: item.rid,
ts: new Date,
msg: TAPi18n.__(error, null, currentUser.language)
});
}
}
});
}
RocketChat.slashCommands.add('invite', Invite);
export {Invite};

@ -1,8 +0,0 @@
RocketChat.slashCommands.add 'join', undefined,
description: 'Join_the_given_channel'
params: '#channel'
(err, result, params) ->
if err.error == 'error-user-already-in-room'
params.cmd = 'open'
params.msg.msg = params.msg.msg.replace('join', 'open')
RocketChat.slashCommands.run 'open', params.params, params.msg

@ -0,0 +1,10 @@
RocketChat.slashCommands.add('join', undefined, {
description: 'Join_the_given_channel',
params: '#channel'
}, function(err, result, params) {
if (err.error === 'error-user-already-in-room') {
params.cmd = 'open';
params.msg.msg = params.msg.msg.replace('join', 'open');
return RocketChat.slashCommands.run('open', params.params, params.msg);
}
});

@ -9,13 +9,12 @@ Package.onUse(function(api) {
api.use([
'ecmascript',
'coffeescript',
'check',
'rocketchat:lib'
]);
api.use('templating', 'client');
api.addFiles('client.coffee', 'client');
api.addFiles('server.coffee', 'server');
api.addFiles('client.js', 'client');
api.addFiles('server.js', 'server');
});

@ -1,33 +0,0 @@
###
# Join is a named function that will replace /join commands
# @param {Object} message - The message object
###
class Join
constructor: (command, params, item) ->
if command isnt 'join' or not Match.test params, String
return
channel = params.trim()
if channel is ''
return
channel = channel.replace('#', '')
user = Meteor.users.findOne Meteor.userId()
room = RocketChat.models.Rooms.findOneByNameAndType channel, 'c'
if not room
RocketChat.Notifications.notifyUser Meteor.userId(), 'message',
_id: Random.id()
rid: item.rid
ts: new Date
msg: TAPi18n.__('Channel_doesnt_exist', { postProcess: 'sprintf', sprintf: [ channel ] }, user.language)
throw new Meteor.Error('error-user-already-in-room', 'You are already in the channel', {
method: 'slashCommands'
}) if room.usernames.indexOf(user.username) > -1
Meteor.call 'joinRoom', room._id
RocketChat.slashCommands.add 'join', Join

@ -0,0 +1,37 @@
/*
* Join is a named function that will replace /join commands
* @param {Object} message - The message object
*/
RocketChat.slashCommands.add('join', function Join(command, params, item) {
if (command !== 'join' || !Match.test(params, String)) {
return;
}
let channel = params.trim();
if (channel === '') {
return;
}
channel = channel.replace('#', '');
const user = Meteor.users.findOne(Meteor.userId());
const room = RocketChat.models.Rooms.findOneByNameAndType(channel, 'c');
if (!room) {
RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: item.rid,
ts: new Date,
msg: TAPi18n.__('Channel_doesnt_exist', {
postProcess: 'sprintf',
sprintf: [channel]
}, user.language)
});
}
if (room.usernames.includes(user.username)) {
throw new Meteor.Error('error-user-already-in-room', 'You are already in the channel', {
method: 'slashCommands'
});
}
Meteor.call('joinRoom', room._id);
});

@ -1,28 +0,0 @@
###
# Leave is a named function that will replace /leave commands
# @param {Object} message - The message object
###
if Meteor.isClient
RocketChat.slashCommands.add 'leave', undefined,
description: 'Leave_the_current_channel'
RocketChat.slashCommands.add 'part', undefined,
description: 'Leave_the_current_channel'
else
class Leave
constructor: (command, params, item) ->
if(command == "leave" || command == "part")
try
Meteor.call 'leaveRoom', item.rid
catch err
RocketChat.Notifications.notifyUser Meteor.userId(), 'message', {
_id: Random.id()
rid: item.rid
ts: new Date
msg: TAPi18n.__(err.error, null, Meteor.user().language)
}
RocketChat.slashCommands.add 'leave', Leave
RocketChat.slashCommands.add 'part', Leave

@ -0,0 +1,31 @@
/*
* Leave is a named function that will replace /leave commands
* @param {Object} message - The message object
*/
function Leave(command, params, item) {
if (command !== 'leave' && command !== 'part') {
return;
}
try {
Meteor.call('leaveRoom', item.rid);
} catch ({error}) {
RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: item.rid,
ts: new Date,
msg: TAPi18n.__(error, null, Meteor.user().language)
});
}
}
if (Meteor.isClient) {
RocketChat.slashCommands.add('leave', undefined, {
description: 'Leave_the_current_channel'
});
RocketChat.slashCommands.add('part', undefined, {
description: 'Leave_the_current_channel'
});
} else {
RocketChat.slashCommands.add('leave', Leave);
RocketChat.slashCommands.add('part', Leave);
}

@ -8,8 +8,7 @@ Package.describe({
Package.onUse(function(api) {
api.use([
'ecmascript',
'coffeescript',
'rocketchat:lib'
]);
api.addFiles('leave.coffee');
api.addFiles('leave.js');
});

@ -1,16 +0,0 @@
###
# Me is a named function that will replace /me commands
# @param {Object} message - The message object
###
class Me
constructor: (command, params, item) ->
if(command == "me")
if _.trim params
msg = item
msg.msg = '_' + params + '_'
Meteor.call 'sendMessage', msg
RocketChat.slashCommands.add 'me', Me,
description: 'Displays_action_text'
params: 'your_message'

@ -0,0 +1,18 @@
/*
* Me is a named function that will replace /me commands
* @param {Object} message - The message object
*/
RocketChat.slashCommands.add('me', function Me(command, params, item) {
if (command !== 'me') {
return;
}
if (_.trim(params)) {
const msg = item;
msg.msg = `_${ params }_`;
Meteor.call('sendMessage', msg);
}
}, {
description: 'Displays_action_text',
params: 'your_message'
});

@ -8,9 +8,8 @@ Package.describe({
Package.onUse(function(api) {
api.use([
'ecmascript',
'coffeescript',
'rocketchat:lib'
]);
api.addFiles('me.coffee', ['server', 'client']);
api.addFiles('me.js', ['server', 'client']);
});

@ -1,3 +0,0 @@
RocketChat.slashCommands.add 'msg', null,
description: 'Direct_message_someone'
params: '@username <message>'

@ -0,0 +1,4 @@
RocketChat.slashCommands.add('msg', undefined, {
description: 'Direct_message_someone',
params: '@username <message>'
});

@ -9,13 +9,12 @@ Package.onUse(function(api) {
api.use([
'ecmascript',
'coffeescript',
'check',
'rocketchat:lib'
]);
api.use('templating', 'client');
api.addFiles('client.coffee', 'client');
api.addFiles('server.coffee', 'server');
api.addFiles('client.js', 'client');
api.addFiles('server.js', 'server');
});

@ -1,43 +0,0 @@
###
# Msg is a named function that will replace /msg commands
###
class Msg
constructor: (command, params, item) ->
if command isnt 'msg' or not Match.test params, String
return
trimmedParams = params.trim()
separator = trimmedParams.indexOf(' ')
user = Meteor.users.findOne Meteor.userId()
if separator is -1
RocketChat.Notifications.notifyUser Meteor.userId(), 'message', {
_id: Random.id()
rid: item.rid
ts: new Date
msg: TAPi18n.__('Username_and_message_must_not_be_empty', null, user.language)
}
return
message = trimmedParams.slice(separator + 1)
targetUsernameOrig = trimmedParams.slice(0, separator)
targetUsername = targetUsernameOrig.replace('@', '')
targetUser = RocketChat.models.Users.findOneByUsername targetUsername
if not targetUser?
RocketChat.Notifications.notifyUser Meteor.userId(), 'message', {
_id: Random.id()
rid: item.rid
ts: new Date
msg: TAPi18n.__('Username_doesnt_exist', { postProcess: 'sprintf', sprintf: [ targetUsernameOrig ] }, user.language)
}
return
rid = Meteor.call 'createDirectMessage', targetUsername
msgObject = { _id: Random.id(), rid: rid.rid, msg: message}
Meteor.call 'sendMessage', msgObject
RocketChat.slashCommands.add 'msg', Msg

@ -0,0 +1,46 @@
/*
* Msg is a named function that will replace /msg commands
*/
function Msg(command, params, item) {
if (command !== 'msg' || !Match.test(params, String)) {
return;
}
const trimmedParams = params.trim();
const separator = trimmedParams.indexOf(' ');
const user = Meteor.users.findOne(Meteor.userId());
if (separator === -1) {
return RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: item.rid,
ts: new Date,
msg: TAPi18n.__('Username_and_message_must_not_be_empty', null, user.language)
});
}
const message = trimmedParams.slice(separator + 1);
const targetUsernameOrig = trimmedParams.slice(0, separator);
const targetUsername = targetUsernameOrig.replace('@', '');
const targetUser = RocketChat.models.Users.findOneByUsername(targetUsername);
if (targetUser == null) {
RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: item.rid,
ts: new Date,
msg: TAPi18n.__('Username_doesnt_exist', {
postProcess: 'sprintf',
sprintf: [targetUsernameOrig]
}, user.language)
});
return;
}
const {rid} = Meteor.call('createDirectMessage', targetUsername);
const msgObject = {
_id: Random.id(),
rid,
msg: message
};
Meteor.call('sendMessage', msgObject);
}
RocketChat.slashCommands.add('msg', Msg);

@ -1,3 +0,0 @@
RocketChat.slashCommands.add 'mute', null,
description: 'Mute_someone_in_room'
params: '@username'

@ -0,0 +1,4 @@
RocketChat.slashCommands.add('mute', undefined, {
description: 'Mute_someone_in_room',
params: '@username'
});

@ -1,3 +0,0 @@
RocketChat.slashCommands.add 'unmute', null,
description: 'Unmute_someone_in_room'
params: '@username'

@ -0,0 +1,4 @@
RocketChat.slashCommands.add('unmute', undefined, {
description: 'Unmute_someone_in_room',
params: '@username'
});

@ -9,15 +9,14 @@ Package.onUse(function(api) {
api.use([
'ecmascript',
'coffeescript',
'check',
'rocketchat:lib'
]);
api.use('templating', 'client');
api.addFiles('client/mute.coffee', 'client');
api.addFiles('client/unmute.coffee', 'client');
api.addFiles('server/mute.coffee', 'server');
api.addFiles('server/unmute.coffee', 'server');
api.addFiles('client/mute.js', 'client');
api.addFiles('client/unmute.js', 'client');
api.addFiles('server/mute.js', 'server');
api.addFiles('server/unmute.js', 'server');
});

@ -1,40 +0,0 @@
###
# Mute is a named function that will replace /mute commands
###
class Mute
constructor: (command, params, item) ->
if command isnt 'mute' or not Match.test params, String
return
username = params.trim()
if username is ''
return
username = username.replace('@', '')
user = Meteor.users.findOne Meteor.userId()
mutedUser = RocketChat.models.Users.findOneByUsername username
room = RocketChat.models.Rooms.findOneById item.rid
if not mutedUser?
RocketChat.Notifications.notifyUser Meteor.userId(), 'message', {
_id: Random.id()
rid: item.rid
ts: new Date
msg: TAPi18n.__('Username_doesnt_exist', { postProcess: 'sprintf', sprintf: [ username ] }, user.language);
}
return
if username not in (room.usernames or [])
RocketChat.Notifications.notifyUser Meteor.userId(), 'message', {
_id: Random.id()
rid: item.rid
ts: new Date
msg: TAPi18n.__('Username_is_not_in_this_room', { postProcess: 'sprintf', sprintf: [ username ] }, user.language);
}
return
Meteor.call 'muteUserInRoom', { rid: item.rid, username: username }
RocketChat.slashCommands.add 'mute', Mute

@ -0,0 +1,45 @@
/*
* Mute is a named function that will replace /mute commands
*/
RocketChat.slashCommands.add('mute', function Mute(command, params, item) {
if (command !== 'mute' || !Match.test(params, String)) {
return;
}
const username = params.trim().replace('@', '');
if (username === '') {
return;
}
const user = Meteor.users.findOne(Meteor.userId());
const mutedUser = RocketChat.models.Users.findOneByUsername(username);
const room = RocketChat.models.Rooms.findOneById(item.rid);
if (mutedUser == null) {
RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: item.rid,
ts: new Date,
msg: TAPi18n.__('Username_doesnt_exist', {
postProcess: 'sprintf',
sprintf: [username]
}, user.language)
});
return;
}
if ((room.usernames || []).includes(username)) {
RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: item.rid,
ts: new Date,
msg: TAPi18n.__('Username_is_not_in_this_room', {
postProcess: 'sprintf',
sprintf: [username]
}, user.language)
});
return;
}
Meteor.call('muteUserInRoom', {
rid: item.rid,
username
});
});

@ -1,40 +0,0 @@
###
# Unmute is a named function that will replace /unmute commands
###
class Unmute
constructor: (command, params, item) ->
if command isnt 'unmute' or not Match.test params, String
return
username = params.trim()
if username is ''
return
username = username.replace('@', '')
user = Meteor.users.findOne Meteor.userId()
unmutedUser = RocketChat.models.Users.findOneByUsername username
room = RocketChat.models.Rooms.findOneById item.rid
if not unmutedUser?
RocketChat.Notifications.notifyUser Meteor.userId(), 'message', {
_id: Random.id()
rid: item.rid
ts: new Date
msg: TAPi18n.__('Username_doesnt_exist', { postProcess: 'sprintf', sprintf: [ username ] }, user.language);
}
return
if username not in (room.usernames or [])
RocketChat.Notifications.notifyUser Meteor.userId(), 'message', {
_id: Random.id()
rid: item.rid
ts: new Date
msg: TAPi18n.__('Username_is_not_in_this_room', { postProcess: 'sprintf', sprintf: [ username ] }, user.language);
}
return
Meteor.call 'unmuteUserInRoom', { rid: item.rid, username: username }
RocketChat.slashCommands.add 'unmute', Unmute

@ -0,0 +1,44 @@
/*
* Unmute is a named function that will replace /unmute commands
*/
RocketChat.slashCommands.add('unmute', function Unmute(command, params, item) {
if (command !== 'unmute' || !Match.test(params, String)) {
return;
}
const username = params.trim().replace('@', '');
if (username === '') {
return;
}
const user = Meteor.users.findOne(Meteor.userId());
const unmutedUser = RocketChat.models.Users.findOneByUsername(username);
const room = RocketChat.models.Rooms.findOneById(item.rid);
if (unmutedUser == null) {
return RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: item.rid,
ts: new Date,
msg: TAPi18n.__('Username_doesnt_exist', {
postProcess: 'sprintf',
sprintf: [username]
}, user.language)
});
}
if ((room.usernames || []).includes(username)) {
return RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: item.rid,
ts: new Date,
msg: TAPi18n.__('Username_is_not_in_this_room', {
postProcess: 'sprintf',
sprintf: [username]
}, user.language)
});
}
Meteor.call('unmuteUserInRoom', {
rid: item.rid,
username
});
});

@ -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;

@ -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'
val = @$('input[name=roomType]:checked').val()
if @validateRoomType(rid)

@ -1,253 +0,0 @@
`import Clipboard from 'clipboard';`
Template.body.onRendered ->
clipboard = new Clipboard('.clipboard')
$(document.body).on 'keydown', (e) ->
if e.keyCode in [80, 75] and (e.ctrlKey is true or e.metaKey is true) and e.shiftKey is false
e.preventDefault()
e.stopPropagation()
toolbarSearch.focus(true)
unread = Session.get('unread')
if e.keyCode is 27 and e.shiftKey is true and unread? and unread isnt ''
e.preventDefault()
e.stopPropagation()
swal
title: t('Clear_all_unreads_question')
type: 'warning'
confirmButtonText: t('Yes_clear_all')
showCancelButton: true
cancelButtonText: t('Cancel')
confirmButtonColor: '#DD6B55'
, ->
subscriptions = ChatSubscription.find({open: true}, { fields: { unread: 1, alert: 1, rid: 1, t: 1, name: 1, ls: 1 } })
for subscription in subscriptions.fetch()
if subscription.alert or subscription.unread > 0
Meteor.call 'readMessages', subscription.rid
$(document.body).on 'keydown', (e) ->
target = e.target
if(e.ctrlKey is true or e.metaKey is true)
return
if !(e.keyCode > 45 and e.keyCode < 91 or e.keyCode == 8)
return
if /input|textarea|select/i.test(target.tagName)
return
if target.id is 'pswp'
return
inputMessage = $('textarea.input-message')
if inputMessage.length is 0
return
inputMessage.focus()
$(document.body).on 'click', 'a', (e) ->
link = e.currentTarget
if link.origin is s.rtrim(Meteor.absoluteUrl(), '/') and /msg=([a-zA-Z0-9]+)/.test(link.search)
e.preventDefault()
e.stopPropagation()
if RocketChat.Layout.isEmbedded()
return fireGlobalEvent('click-message-link', { link: link.pathname + link.search })
FlowRouter.go(link.pathname + link.search, null, FlowRouter.current().queryParams)
Tracker.autorun (c) ->
w = window
d = document
s = 'script'
l = 'dataLayer'
i = RocketChat.settings.get 'GoogleTagManager_id'
if Match.test(i, String) and i.trim() isnt ''
c.stop()
do (w,d,s,l,i) ->
w[l] = w[l] || []
w[l].push {'gtm.start': new Date().getTime(), event:'gtm.js'}
f = d.getElementsByTagName(s)[0]
j = d.createElement(s)
dl = if l isnt 'dataLayer' then '&l=' + l else ''
j.async = true
j.src = '//www.googletagmanager.com/gtm.js?id=' + i + dl
f.parentNode.insertBefore j, f
if Meteor.isCordova
$(document.body).addClass 'is-cordova'
Template.main.helpers
siteName: ->
return RocketChat.settings.get 'Site_Name'
logged: ->
if Meteor.userId()?
$('html').addClass("noscroll").removeClass("scroll")
return true
else
$('html').addClass("scroll").removeClass("noscroll")
return false
useIframe: ->
iframeEnabled = (typeof RocketChat.iframeLogin isnt "undefined")
return (iframeEnabled and RocketChat.iframeLogin.reactiveEnabled.get())
iframeUrl: ->
iframeEnabled = (typeof RocketChat.iframeLogin isnt "undefined")
return (iframeEnabled and RocketChat.iframeLogin.reactiveIframeUrl.get())
subsReady: ->
routerReady = FlowRouter.subsReady('userData', 'activeUsers')
subscriptionsReady = CachedChatSubscription.ready.get()
ready = not Meteor.userId()? or (routerReady and subscriptionsReady)
RocketChat.CachedCollectionManager.syncEnabled = ready
return ready
hasUsername: ->
return Meteor.userId()? and Meteor.user().username?
requirePasswordChange: ->
return Meteor.user()?.requirePasswordChange is true
CustomScriptLoggedOut: ->
script = RocketChat.settings.get('Custom_Script_Logged_Out') or ''
if script.trim()
eval(script)
return
CustomScriptLoggedIn: ->
script = RocketChat.settings.get('Custom_Script_Logged_In') or ''
if script.trim()
eval(script)
return
embeddedVersion: ->
return 'embedded-view' if RocketChat.Layout.isEmbedded()
Template.main.events
"click .burger": ->
console.log 'room click .burger' if window.rocketDebug
menu.toggle()
'touchstart': (e, t) ->
if document.body.clientWidth > 780
return
t.touchstartX = undefined
t.touchstartY = undefined
t.movestarted = false
t.blockmove = false
t.isRtl = isRtl localStorage.getItem "userLanguage"
if $(e.currentTarget).closest('.main-content').length > 0
t.touchstartX = e.originalEvent.touches[0].clientX
t.touchstartY = e.originalEvent.touches[0].clientY
t.mainContent = $('.main-content')
t.wrapper = $('.messages-box > .wrapper')
'touchmove': (e, t) ->
if t.touchstartX?
touch = e.originalEvent.touches[0]
diffX = touch.clientX - t.touchstartX
diffY = touch.clientY - t.touchstartY
absX = Math.abs(diffX)
absY = Math.abs(diffY)
if t.movestarted isnt true and t.blockmove isnt true and absY > 5
t.blockmove = true
if t.blockmove isnt true and (t.movestarted is true or absX > 5)
t.movestarted = true
if t.isRtl
if menu.isOpen()
t.diff = -260 + diffX
else
t.diff = diffX
if t.diff < -260
t.diff = -260
if t.diff > 0
t.diff = 0
else
if menu.isOpen()
t.diff = 260 + diffX
else
t.diff = diffX
if t.diff > 260
t.diff = 260
if t.diff < 0
t.diff = 0
t.mainContent.addClass('notransition')
t.mainContent.css('transform', 'translate(' + t.diff + 'px)')
t.wrapper.css('overflow', 'hidden')
'touchend': (e, t) ->
if t.movestarted is true
t.mainContent.removeClass('notransition')
t.wrapper.css('overflow', '')
if t.isRtl
if menu.isOpen()
if t.diff >= -200
menu.close()
else
menu.open()
else
if t.diff <= -60
menu.open()
else
menu.close()
else
if menu.isOpen()
if t.diff >= 200
menu.open()
else
menu.close()
else
if t.diff >= 60
menu.open()
else
menu.close()
Template.main.onRendered ->
# RTL Support - Need config option on the UI
if isRtl localStorage.getItem "userLanguage"
$('html').addClass "rtl"
else
$('html').removeClass "rtl"
$('#initial-page-loading').remove()
window.addEventListener 'focus', ->
Meteor.setTimeout ->
if not $(':focus').is('INPUT,TEXTAREA')
$('.input-message').focus()
, 100
Tracker.autorun ->
swal.setDefaults({cancelButtonText: t('Cancel')})
prefs = Meteor.user()?.settings?.preferences
if prefs?.hideUsernames
$(document.body).on('mouseleave', 'button.thumb', (e) ->
RocketChat.tooltip.hide();
)
$(document.body).on('mouseenter', 'button.thumb', (e) ->
avatarElem = $(e.currentTarget)
username = avatarElem.attr('data-username')
if username
e.stopPropagation()
RocketChat.tooltip.showElement($('<span>').text(username), avatarElem)
)
else
$(document.body).off('mouseenter', 'button.thumb')
$(document.body).off('mouseleave', 'button.thumb')
Meteor.startup ->
fireGlobalEvent 'startup', true

@ -0,0 +1,301 @@
/* globals toolbarSearch, menu, isRtl, fireGlobalEvent, CachedChatSubscription */
import Clipboard from 'clipboard';
Template.body.onRendered(function() {
new Clipboard('.clipboard');
$(document.body).on('keydown', function(e) {
if ((e.keyCode === 80 || e.keyCode === 75) && (e.ctrlKey === true || e.metaKey === true) && e.shiftKey === false) {
e.preventDefault();
e.stopPropagation();
toolbarSearch.focus(true);
}
const unread = Session.get('unread');
if (e.keyCode === 27 && e.shiftKey === true && (unread != null) && unread !== '') {
e.preventDefault();
e.stopPropagation();
return swal({
title: t('Clear_all_unreads_question'),
type: 'warning',
confirmButtonText: t('Yes_clear_all'),
showCancelButton: true,
cancelButtonText: t('Cancel'),
confirmButtonColor: '#DD6B55'
}, function() {
const subscriptions = ChatSubscription.find({
open: true
}, {
fields: {
unread: 1,
alert: 1,
rid: 1,
t: 1,
name: 1,
ls: 1
}
});
subscriptions.forEach((subscription) =>{
if (subscription.alert || subscription.unread > 0) {
Meteor.call('readMessages', subscription.rid);
}
});
});
}
});
$(document.body).on('keydown', function(e) {
const target = e.target;
if (e.ctrlKey === true || e.metaKey === true) {
return;
}
if (!(e.keyCode > 45 && e.keyCode < 91 || e.keyCode === 8)) {
return;
}
if (/input|textarea|select/i.test(target.tagName)) {
return;
}
if (target.id === 'pswp') {
return;
}
const inputMessage = $('textarea.input-message');
if (inputMessage.length === 0) {
return;
}
return inputMessage.focus();
});
$(document.body).on('click', 'a', function(e) {
const link = e.currentTarget;
if (link.origin === s.rtrim(Meteor.absoluteUrl(), '/') && /msg=([a-zA-Z0-9]+)/.test(link.search)) {
e.preventDefault();
e.stopPropagation();
if (RocketChat.Layout.isEmbedded()) {
return fireGlobalEvent('click-message-link', {
link: link.pathname + link.search
});
}
return FlowRouter.go(link.pathname + link.search, null, FlowRouter.current().queryParams);
}
});
Tracker.autorun(function(c) {
const w = window;
const d = document;
const s = 'script';
const l = 'dataLayer';
const i = RocketChat.settings.get('GoogleTagManager_id');
if (Match.test(i, String) && i.trim() !== '') {
c.stop();
return (function(w, d, s, l, i) {
w[l] = w[l] || [];
w[l].push({
'gtm.start': new Date().getTime(),
event: 'gtm.js'
});
const f = d.getElementsByTagName(s)[0];
const j = d.createElement(s);
const dl = l !== 'dataLayer' ? `&l=${ l }` : '';
j.async = true;
j.src = `//www.googletagmanager.com/gtm.js?id=${ i }${ dl }`;
return f.parentNode.insertBefore(j, f);
}(w, d, s, l, i));
}
});
if (Meteor.isCordova) {
return $(document.body).addClass('is-cordova');
}
});
Template.main.helpers({
siteName() {
return RocketChat.settings.get('Site_Name');
},
logged() {
if (Meteor.userId() != null) {
$('html').addClass('noscroll').removeClass('scroll');
return true;
} else {
$('html').addClass('scroll').removeClass('noscroll');
return false;
}
},
useIframe() {
const iframeEnabled = typeof RocketChat.iframeLogin !== 'undefined';
return iframeEnabled && RocketChat.iframeLogin.reactiveEnabled.get();
},
iframeUrl() {
const iframeEnabled = typeof RocketChat.iframeLogin !== 'undefined';
return iframeEnabled && RocketChat.iframeLogin.reactiveIframeUrl.get();
},
subsReady() {
const routerReady = FlowRouter.subsReady('userData', 'activeUsers');
const subscriptionsReady = CachedChatSubscription.ready.get();
const ready = (Meteor.userId() == null) || (routerReady && subscriptionsReady);
RocketChat.CachedCollectionManager.syncEnabled = ready;
return ready;
},
hasUsername() {
return (Meteor.userId() != null) && (Meteor.user().username != null);
},
requirePasswordChange() {
const user = Meteor.user();
return user && user.requirePasswordChange === true;
},
CustomScriptLoggedOut() {
const script = RocketChat.settings.get('Custom_Script_Logged_Out') || '';
if (script.trim()) {
eval(script);//eslint-disable-line
}
},
CustomScriptLoggedIn() {
const script = RocketChat.settings.get('Custom_Script_Logged_In') || '';
if (script.trim()) {
eval(script);//eslint-disable-line
}
},
embeddedVersion() {
if (RocketChat.Layout.isEmbedded()) {
return 'embedded-view';
}
}
});
Template.main.events({
'click .burger'() {
if (window.rocketDebug) {
console.log('room click .burger');
}
return menu.toggle();
},
'touchstart'(e, t) {
if (document.body.clientWidth > 780) {
return;
}
t.touchstartX = undefined;
t.touchstartY = undefined;
t.movestarted = false;
t.blockmove = false;
t.isRtl = isRtl(localStorage.getItem('userLanguage'));
if ($(e.currentTarget).closest('.main-content').length > 0) {
t.touchstartX = e.originalEvent.touches[0].clientX;
t.touchstartY = e.originalEvent.touches[0].clientY;
t.mainContent = $('.main-content');
return t.wrapper = $('.messages-box > .wrapper');
}
},
'touchmove'(e, t) {
if (t.touchstartX != null) {
const [touch] = e.originalEvent.touches;
const diffX = touch.clientX - t.touchstartX;
const diffY = touch.clientY - t.touchstartY;
const absX = Math.abs(diffX);
const absY = Math.abs(diffY);
if (t.movestarted !== true && t.blockmove !== true && absY > 5) {
t.blockmove = true;
}
if (t.blockmove !== true && (t.movestarted === true || absX > 5)) {
t.movestarted = true;
if (t.isRtl) {
if (menu.isOpen()) {
t.diff = -260 + diffX;
} else {
t.diff = diffX;
}
if (t.diff < -260) {
t.diff = -260;
}
if (t.diff > 0) {
t.diff = 0;
}
} else {
if (menu.isOpen()) {
t.diff = 260 + diffX;
} else {
t.diff = diffX;
}
if (t.diff > 260) {
t.diff = 260;
}
if (t.diff < 0) {
t.diff = 0;
}
}
t.mainContent.addClass('notransition');
t.mainContent.css('transform', `translate(${ t.diff }px)`);
return t.wrapper.css('overflow', 'hidden');
}
}
},
'touchend'(e, t) {
if (t.movestarted === true) {
t.mainContent.removeClass('notransition');
t.wrapper.css('overflow', '');
if (t.isRtl) {
if (menu.isOpen()) {
if (t.diff >= -200) {
return menu.close();
} else {
return menu.open();
}
} else if (t.diff <= -60) {
return menu.open();
} else {
return menu.close();
}
} else if (menu.isOpen()) {
if (t.diff >= 200) {
return menu.open();
} else {
return menu.close();
}
} else if (t.diff >= 60) {
return menu.open();
} else {
return menu.close();
}
}
}
});
Template.main.onRendered(function() {
if (isRtl(localStorage.getItem('userLanguage'))) {
$('html').addClass('rtl');
} else {
$('html').removeClass('rtl');
}
$('#initial-page-loading').remove();
window.addEventListener('focus', function() {
return Meteor.setTimeout(function() {
if (!$(':focus').is('INPUT,TEXTAREA')) {
return $('.input-message').focus();
}
}, 100);
});
return Tracker.autorun(function() {
swal.setDefaults({
cancelButtonText: t('Cancel')
});
const user = Meteor.user();
const settings = user && user.settings;
const prefs = settings && settings.preferences;
if (prefs && prefs.hideUsernames != null) {
$(document.body).on('mouseleave', 'button.thumb', function() {
return RocketChat.tooltip.hide();
});
return $(document.body).on('mouseenter', 'button.thumb', function(e) {
const avatarElem = $(e.currentTarget);
const username = avatarElem.attr('data-username');
if (username) {
e.stopPropagation();
return RocketChat.tooltip.showElement($('<span>').text(username), avatarElem);
}
});
} else {
$(document.body).off('mouseenter', 'button.thumb');
return $(document.body).off('mouseleave', 'button.thumb');
}
});
});
Meteor.startup(function() {
return fireGlobalEvent('startup', true);
});

@ -19,7 +19,6 @@ Package.onUse(function(api) {
'mongo',
'ecmascript',
'templating',
'coffeescript',
'underscore',
'rocketchat:lib',
'meteorhacks:inject-initial'
@ -29,7 +28,7 @@ Package.onUse(function(api) {
api.addFiles('client/loading.html', 'client');
api.addFiles('client/error.html', 'client');
api.addFiles('client/logoLayout.html', 'client');
api.addFiles('client/main.coffee', 'client');
api.addFiles('client/main.js', 'client');
api.addFiles('server/inject.js', 'server');
});

@ -1,6 +1,7 @@
import toastr from 'toastr'
import mime from 'mime-type/with-db'
import moment from 'moment'
import {VRecDialog} from 'meteor/rocketchat:ui-vrecord'
katexSyntax = ->
if RocketChat.katex.katex_enabled()
@ -117,6 +118,45 @@ Template.messageBox.helpers
showSandstorm: ->
return Meteor.settings.public.sandstorm && !Meteor.isCordova
firefoxPasteUpload = (fn) ->
user = navigator.userAgent.match(/Firefox\/(\d+)\.\d/)
if !user or user[1] > 49
return fn
return (event, instance) ->
if (event.originalEvent.ctrlKey or event.originalEvent.metaKey) and (event.keyCode == 86)
textarea = instance.find("textarea")
selectionStart = textarea.selectionStart
selectionEnd = textarea.selectionEnd
contentEditableDiv = instance.find('#msg_contenteditable')
contentEditableDiv.focus()
Meteor.setTimeout ->
pastedImg = contentEditableDiv.querySelector 'img'
textareaContent = textarea.value
startContent = textareaContent.substring(0, selectionStart)
endContent = textareaContent.substring(selectionEnd)
restoreSelection = (pastedText) ->
textarea.value = startContent + pastedText + endContent
textarea.selectionStart = selectionStart + pastedText.length
textarea.selectionEnd = textarea.selectionStart
contentEditableDiv.innerHTML = '' if pastedImg
textarea.focus
return if (!pastedImg || contentEditableDiv.innerHTML.length > 0)
[].slice.call(contentEditableDiv.querySelectorAll("br")).forEach (el) ->
contentEditableDiv.replaceChild(new Text("\n") , el)
restoreSelection(contentEditableDiv.innerText)
imageSrc = pastedImg.getAttribute("src")
if imageSrc.match(/^data:image/)
fetch(imageSrc)
.then((img)->
return img.blob())
.then (blob)->
fileUpload [{
file: blob
name: 'Clipboard'
}]
, 150
fn?.apply @, arguments
Template.messageBox.events
'click .join': (event) ->
@ -158,7 +198,6 @@ Template.messageBox.events
if not e.originalEvent.clipboardData?
return
items = e.originalEvent.clipboardData.items
files = []
for item in items
@ -173,8 +212,8 @@ Template.messageBox.events
else
instance.isMessageFieldEmpty.set(false)
'keydown .input-message': (event) ->
chatMessages[@_id].keydown(@_id, event, Template.instance())
'keydown .input-message': firefoxPasteUpload((event, instance) ->
chatMessages[@_id].keydown(@_id, event, Template.instance()))
'input .input-message': (event) ->
chatMessages[@_id].valueChanged(@_id, event, Template.instance())
@ -319,4 +358,4 @@ Meteor.startup ->
navigator.geolocation.watchPosition success, error
else
RocketChat.Geolocation.set false
RocketChat.Geolocation.set false

@ -5,6 +5,7 @@
{{#if allowedToSend}}
<div class="message-input border-component-color">
<div class="input-message-container content-background-color">
<div id="msg_contenteditable" contenteditable="true" style="height: 0; width: 0; overflow: hidden"></div>
<textarea dir="auto" name="msg" maxlength="{{maxMessageLength}}" class="message-form-text input-message autogrow-short" placeholder="{{_ 'Message'}}"></textarea>
<div class="inner-left-toolbar">

@ -19,7 +19,8 @@ Package.onUse(function(api) {
'underscore',
'tracker',
'rocketchat:lib',
'rocketchat:ui-account'
'rocketchat:ui-account',
'rocketchat:ui-vrecord'
]);
api.addFiles('client/message.html', 'client');

@ -1,47 +0,0 @@
@VRecDialog = new class
opened: false
initiated: false
width: 400
height: 280
init: ->
if @initiated
return
@initiated = true
Blaze.render(Template.vrecDialog, document.body)
open: (source) ->
if not @initiated
@init()
@source = source
dialog = $('.vrec-dialog')
@setPosition(dialog, source)
dialog.addClass('show')
@opened = true
@initializeCamera()
close: ->
$('.vrec-dialog').removeClass('show')
@opened = false
if @video?
VideoRecorder.stop()
setPosition: (dialog, source) ->
sourcePos = $(source).offset()
left = sourcePos.left - @width + 100
top = sourcePos.top - @height - 40
left = 10 if left < 0
top = 10 if top < 0
dialog.css({ top: top + 'px', left: left + 'px' })
initializeCamera: ->
@video = $('.vrec-dialog video').get('0')
if not @video
return
VideoRecorder.start @video

@ -0,0 +1,63 @@
export const VRecDialog = new class {
static initClass() {
this.prototype.opened = false;
this.prototype.initiated = false;
this.prototype.width = 400;
this.prototype.height = 280;
}
init() {
if (this.initiated) {
return;
}
this.initiated = true;
return Blaze.render(Template.vrecDialog, document.body);
}
open(source) {
if (!this.initiated) {
this.init();
}
this.source = source;
const dialog = $('.vrec-dialog');
this.setPosition(dialog, source);
dialog.addClass('show');
this.opened = true;
return this.initializeCamera();
}
close() {
$('.vrec-dialog').removeClass('show');
this.opened = false;
if (this.video != null) {
return VideoRecorder.stop();
}
}
setPosition(dialog, source) {
const sourcePos = $(source).offset();
let left = (sourcePos.left - this.width) + 100;
let top = sourcePos.top - this.height - 40;
if (left < 0) {
left = 10;
}
if (top < 0) {
top = 10;
}
return dialog.css({ top: `${ top }px`, left: `${ left }px` });
}
initializeCamera() {
this.video = $('.vrec-dialog video').get('0');
if (!this.video) {
return;
}
return VideoRecorder.start(this.video);
}
};

@ -1,4 +1,5 @@
/* globals VideoRecorder, VRecDialog, fileUpload */
/* globals VideoRecorder, fileUpload */
import {VRecDialog} from './VRecDialog';
Template.vrecDialog.helpers({
recordIcon() {

@ -10,7 +10,6 @@ Package.onUse(function(api) {
'mongo',
'ecmascript',
'templating',
'coffeescript',
'underscore',
'tracker',
'rocketchat:lib',
@ -22,7 +21,7 @@ Package.onUse(function(api) {
api.addFiles('client/vrecord.html', 'client');
api.addFiles('client/vrecord.js', 'client');
api.addFiles('client/VRecDialog.coffee', 'client');
api.addFiles('server/settings.js', 'server');
api.mainModule('client/VRecDialog.js', 'client');
});

@ -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)"

@ -1,6 +0,0 @@
Template.roomNotFound.helpers
data: ->
return Session.get 'roomNotFound'
name: ->
return Blaze._escape(this.name)

@ -0,0 +1,8 @@
Template.roomNotFound.helpers({
data() {
return Session.get('roomNotFound');
},
name() {
return Blaze._escape(this.name);
}
});

@ -1,5 +0,0 @@
Template.burger.helpers
unread: ->
return Session.get 'unread'
isMenuOpen: ->
if Session.equals('isMenuOpen', true) then 'menu-opened'

@ -0,0 +1,10 @@
Template.burger.helpers({
unread() {
return Session.get('unread');
},
isMenuOpen() {
if (Session.equals('isMenuOpen', true)) {
return 'menu-opened';
}
}
});

@ -1,5 +0,0 @@
Template.home.helpers
title: ->
return RocketChat.settings.get 'Layout_Home_Title'
body: ->
return RocketChat.settings.get 'Layout_Home_Body'

@ -0,0 +1,8 @@
Template.home.helpers({
title() {
return RocketChat.settings.get('Layout_Home_Title');
},
body() {
return RocketChat.settings.get('Layout_Home_Body');
}
});

@ -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

@ -8,20 +8,25 @@
<div class="main-content-flex">
<section class="messages-container flex-tab-main-content {{adminClass}}" id="{{windowId}}" aria-label="{{_ "Channel"}}">
{{#unless embeddedVersion}}
<header class="fixed-title content-background-color border-component-color">
{{> burger}}
<h2>
{{#if showToggleFavorite}}
<a href="#favorite" class="toggle-favorite"><i class="{{favorite}}" aria-label="{{_ favoriteLabel}}"></i></a>
{{/if}}
<i class="{{roomIcon}} status-{{userStatus}}"></i>
<span class="room-title">{{roomName}}</span>
{{#if isTranslated}}
<i class="icon-language" aria-label="{{_ "Translated"}}"></i>
{{/if}}
<span class="room-topic">{{{RocketChatMarkdown roomTopic}}}</span>
</h2>
</header>
<header class="fixed-title content-background-color border-component-color">
{{> burger}}
<h2>
{{#if showToggleFavorite}}
<a href="#favorite" class="toggle-favorite"><i class="{{favorite}}" aria-label="{{_ favoriteLabel}}"></i></a>
{{/if}}
<i class="{{roomIcon}} status-{{userStatus}}"></i>
<span class="room-title">{{roomName}}</span>
{{#if isTranslated}}
<i class="icon-language" aria-label="{{_ "Translated"}}"></i>
{{/if}}
<span class="room-topic">{{{RocketChatMarkdown roomTopic}}}</span>
</h2>
</header>
{{#if showAnnouncement}}
<div class="fixed-title announcement">
{{{RocketChatMarkdown roomAnnouncement}}}
</div>
{{/if}}
{{/unless}}
<div class="container-bars {{containerBarsShow unreadData uploading}}">
{{#with unreadData}}

@ -1,10 +0,0 @@
Template.roomSearch.helpers
roomIcon: ->
return 'icon-at' if this.type is 'u'
if this.type is 'r'
return RocketChat.roomTypes.getIcon this.t
userStatus: ->
if this.type is 'u'
return 'status-' + this.status

@ -0,0 +1,15 @@
Template.roomSearch.helpers({
roomIcon() {
if (this.type === 'u') {
return 'icon-at';
}
if (this.type === 'r') {
return RocketChat.roomTypes.getIcon(this.t);
}
},
userStatus() {
if (this.type === 'u') {
return `status-${ this.status }`;
}
}
});

@ -1,23 +0,0 @@
Template.secretURL.helpers
registrationAllowed: ->
return RocketChat.settings.get('Accounts_RegistrationForm') is 'Secret URL' and Template.instance().hashIsValid?.get()
ready: ->
return Template.instance().subscriptionsReady?() and Template.instance().hashReady?.get()
Template.secretURL.onCreated ->
@hashIsValid = new ReactiveVar false
@hashReady = new ReactiveVar false
Meteor.call 'checkRegistrationSecretURL', FlowRouter.getParam('hash'), (err, success) =>
@hashReady.set true
if success
Session.set 'loginDefaultState', 'register'
KonchatNotification.getDesktopPermission()
@hashIsValid.set true
else
@hashIsValid.set false
Template.secretURL.onRendered ->
$('#initial-page-loading').remove()

@ -0,0 +1,29 @@
/* globals KonchatNotification */
Template.secretURL.helpers({
registrationAllowed() {
const {hashIsValid} = Template.instance();
return RocketChat.settings.get('Accounts_RegistrationForm') === 'Secret URL' && hashIsValid && hashIsValid.get();
},
ready() {
const {subscriptionsReady, hashReady} = Template.instance();
return typeof subscriptionsReady === 'function' && subscriptionsReady() && hashReady && hashReady.get();
}
});
Template.secretURL.onCreated(function() {
this.hashIsValid = new ReactiveVar(false);
this.hashReady = new ReactiveVar(false);
Meteor.call('checkRegistrationSecretURL', FlowRouter.getParam('hash'), (err, success) => {
this.hashReady.set(true);
if (success) {
Session.set('loginDefaultState', 'register');
KonchatNotification.getDesktopPermission();
return this.hashIsValid.set(true);
}
return this.hashIsValid.set(false);
});
});
Template.secretURL.onRendered(function() {
return $('#initial-page-loading').remove();
});

@ -1,16 +0,0 @@
Template.cmsPage.onCreated ->
@page = new ReactiveVar ''
Meteor.autorun =>
if Session.get('cmsPage')?
@page.set RocketChat.settings.get Session.get('cmsPage')
Template.cmsPage.helpers
page: ->
return Template.instance().page.get()
Template.cmsPage.events
'click .cms-page-close': ->
FlowRouter.go('/')
Template.cmsPage.onRendered ->
$('#initial-page-loading').remove()

@ -0,0 +1,25 @@
Template.cmsPage.onCreated(function() {
this.page = new ReactiveVar('');
return Meteor.autorun(() => {
const cmsPage = Session.get('cmsPage');
if (cmsPage != null) {
return this.page.set(RocketChat.settings.get(cmsPage));
}
});
});
Template.cmsPage.helpers({
page() {
return Template.instance().page.get();
}
});
Template.cmsPage.events({
'click .cms-page-close'() {
return FlowRouter.go('/');
}
});
Template.cmsPage.onRendered(function() {
return $('#initial-page-loading').remove();
});

@ -1,20 +0,0 @@
Template.fxOsInstallPrompt.onRendered ->
showPrompt = () ->
request = window.navigator.mozApps.install 'http://' + location.host + '/manifest.webapp'
request.onsuccess = () ->
# Save the App object that is returned
appRecord = this.result
BlazeLayout.render 'fxOsInstallDone'
request.onerror = () ->
# Display the error information from the DOMError object
BlazeLayout.render 'fxOsInstallError', {installError: this.error.name}
setTimeout(showPrompt, 2000);
$('#initial-page-loading').remove()
Template.fxOsInstallDone.onRendered ->
$('#initial-page-loading').remove()
Template.fxOsInstallError.onRendered ->
$('#initial-page-loading').remove()

@ -0,0 +1,19 @@
Template.fxOsInstallPrompt.onRendered(function() {
const showPrompt = function() {
const request = window.navigator.mozApps.install(`http://${ location.host }/manifest.webapp`);
request.onsuccess = function() {
BlazeLayout.render('fxOsInstallDone');
};
request.onerror = function() {
BlazeLayout.render('fxOsInstallError', {
installError: this.error.name
});
};
};
setTimeout(showPrompt, 2000);
return $('#initial-page-loading').remove();
});
Template.fxOsInstallDone.onRendered(() => $('#initial-page-loading').remove());
Template.fxOsInstallError.onRendered(() => $('#initial-page-loading').remove());

@ -1 +0,0 @@
Template.modal.rendered = ->

@ -0,0 +1 @@
Template.modal.rendered = function() {};

@ -1,19 +0,0 @@
@getAvatarUrlFromUsername = (username) ->
key = "avatar_random_#{username}"
random = Session?.keys[key] or 0
if not username?
return
cdnPrefix = (RocketChat.settings.get('CDN_PREFIX') or '').trim().replace(/\/$/, '')
pathPrefix = (__meteor_runtime_config__.ROOT_URL_PATH_PREFIX or '').trim().replace(/\/$/, '')
if cdnPrefix
path = cdnPrefix + pathPrefix
else if Meteor.isCordova
# Meteor.absoluteUrl alread has path prefix
path = Meteor.absoluteUrl().replace(/\/$/, '')
else
path = pathPrefix
return "#{path}/avatar/#{encodeURIComponent(username)}?_dc=#{random}"

@ -0,0 +1,17 @@
// TODO: remove global
this.getAvatarUrlFromUsername = function(username) {
const key = `avatar_random_${ username }`;
const random = typeof Session !== 'undefined' ? Session.keys[key] : 0;
if (username == null) {
return;
}
const cdnPrefix = (RocketChat.settings.get('CDN_PREFIX') || '').trim().replace(/\/$/, '');
const pathPrefix = (__meteor_runtime_config__.ROOT_URL_PATH_PREFIX || '').trim().replace(/\/$/, '');
let path = pathPrefix;
if (cdnPrefix) {
path = cdnPrefix + pathPrefix;
} else if (Meteor.isCordova) {
path = Meteor.absoluteUrl().replace(/\/$/, '');
}
return `${ path }/avatar/${ encodeURIComponent(username) }?_dc=${ random }`;
};

@ -29,7 +29,7 @@ Package.onUse(function(api) {
api.use('kadira:flow-router', 'client');
api.addFiles('getAvatarUrlFromUsername.coffee');
api.addFiles('getAvatarUrlFromUsername.js');
// LIB FILES
api.addFiles('client/lib/accountBox.coffee', 'client');
@ -94,17 +94,17 @@ Package.onUse(function(api) {
api.addFiles('client/views/app/videoCall/videoCall.html', 'client');
api.addFiles('client/views/app/photoswipe.html', 'client');
api.addFiles('client/views/cmsPage.coffee', 'client');
api.addFiles('client/views/fxos.coffee', 'client');
api.addFiles('client/views/modal.coffee', 'client');
api.addFiles('client/views/404/roomNotFound.coffee', 'client');
api.addFiles('client/views/app/burger.coffee', 'client');
api.addFiles('client/views/app/home.coffee', 'client');
api.addFiles('client/views/cmsPage.js', 'client');
api.addFiles('client/views/fxos.js', 'client');
api.addFiles('client/views/modal.js', 'client');
api.addFiles('client/views/404/roomNotFound.js', 'client');
api.addFiles('client/views/app/burger.js', 'client');
api.addFiles('client/views/app/home.js', 'client');
api.addFiles('client/views/app/mobileMessageMenu.js', 'client');
api.addFiles('client/views/app/privateHistory.coffee', 'client');
api.addFiles('client/views/app/room.coffee', 'client');
api.addFiles('client/views/app/roomSearch.coffee', 'client');
api.addFiles('client/views/app/secretURL.coffee', 'client');
api.addFiles('client/views/app/roomSearch.js', 'client');
api.addFiles('client/views/app/secretURL.js', 'client');
api.addFiles('client/views/app/videoCall/videoButtons.coffee', 'client');
api.addFiles('client/views/app/videoCall/videoCall.coffee', 'client');
api.addFiles('client/views/app/photoswipe.js', 'client');

@ -6,8 +6,8 @@ Package.describe({
Package.registerBuildPlugin({
name: 'compileVersion',
use: ['coffeescript'],
sources: ['plugin/compile-version.coffee']
use: ['ecmascript'],
sources: ['plugin/compile-version.js']
});
Package.onUse(function(api) {

@ -1,62 +0,0 @@
exec = Npm.require('child_process').exec
os = Npm.require('os')
Future = Npm.require('fibers/future')
async = Npm.require('async')
Plugin.registerCompiler
extensions: ['info']
, -> new VersionCompiler()
class VersionCompiler
processFilesForTarget: (files) ->
future = new Future
processFile = (file, cb) ->
return cb() if not file.getDisplayPath().match /rocketchat\.info$/
output = JSON.parse file.getContentsAsString()
output.build =
date: new Date().toISOString()
nodeVersion: process.version
arch: process.arch
platform: process.platform
osRelease: os.release()
totalMemory: os.totalmem()
freeMemory: os.freemem()
cpus: os.cpus().length
if process.env.TRAVIS_BUILD_NUMBER
output.travis =
buildNumber: process.env.TRAVIS_BUILD_NUMBER
branch: process.env.TRAVIS_BRANCH
tag: process.env.TRAVIS_TAG
exec "git log --pretty=format:'%H%n%ad%n%an%n%s' -n 1", (err, result) ->
if not err?
result = result.split('\n')
output.commit =
hash: result.shift()
date: result.shift()
author: result.shift()
subject: result.join('\n')
exec "git describe --abbrev=0 --tags", (err, result) ->
if not err?
output.commit?.tag = result.replace('\n', '')
exec "git rev-parse --abbrev-ref HEAD", (err, result) ->
if not err?
output.commit?.branch = result.replace('\n', '')
output = """
RocketChat.Info = #{JSON.stringify(output, null, 4)};
"""
file.addJavaScript({ data: output, path: file.getPathInPackage() + '.js' })
cb()
async.each files, processFile, future.resolver()
future.wait()

@ -0,0 +1,74 @@
import {exec} from 'child_process';
import os from 'os';
import Future from 'fibers/future';
import async from 'async';
class VersionCompiler {
processFilesForTarget(files) {
const future = new Future;
const processFile = function(file, cb) {
if (!file.getDisplayPath().match(/rocketchat\.info$/)) {
return cb();
}
let output = JSON.parse(file.getContentsAsString());
output.build = {
date: new Date().toISOString(),
nodeVersion: process.version,
arch: process.arch,
platform: process.platform,
osRelease: os.release(),
totalMemory: os.totalmem(),
freeMemory: os.freemem(),
cpus: os.cpus().length
};
if (process.env.TRAVIS_BUILD_NUMBER) {
output.travis = {
buildNumber: process.env.TRAVIS_BUILD_NUMBER,
branch: process.env.TRAVIS_BRANCH,
tag: process.env.TRAVIS_TAG
};
}
exec('git log --pretty=format:\'%H%n%ad%n%an%n%s\' -n 1', function(err, result) {
if (err == null) {
result = result.split('\n');
output.commit = {
hash: result.shift(),
date: result.shift(),
author: result.shift(),
subject: result.join('\n')
};
}
exec('git describe --abbrev=0 --tags', function(err, result) {
if (err == null && output.commit != null) {
output.commit.tag = result.replace('\n', '');
}
exec('git rev-parse --abbrev-ref HEAD', function(err, result) {
if (err == null && output.commit != null) {
output.commit.branch = result.replace('\n', '');
}
output = `RocketChat.Info = ${ JSON.stringify(output, null, 4) };`;
file.addJavaScript({
data: output,
path: `${ file.getPathInPackage() }.js`
});
cb();
});
});
});
};
async.each(files, processFile, future.resolver());
return future.wait();
}
}
Plugin.registerCompiler({
extensions: ['info']
}, function() {
return new VersionCompiler();
});

@ -1,20 +0,0 @@
config =
serverURL: ''
identityPath: '/oauth/me'
addAutopublishFields:
forLoggedInUser: ['services.wordpress']
forOtherUsers: ['services.wordpress.user_login']
WordPress = new CustomOAuth 'wordpress', config
if Meteor.isServer
Meteor.startup ->
RocketChat.settings.get 'API_Wordpress_URL', (key, value) ->
config.serverURL = value
WordPress.configure config
else
Meteor.startup ->
Tracker.autorun ->
if RocketChat.settings.get 'API_Wordpress_URL'
config.serverURL = RocketChat.settings.get 'API_Wordpress_URL'
WordPress.configure config

@ -0,0 +1,30 @@
/* globals CustomOAuth */
const config = {
serverURL: '',
identityPath: '/oauth/me',
addAutopublishFields: {
forLoggedInUser: ['services.wordpress'],
forOtherUsers: ['services.wordpress.user_login']
}
};
const WordPress = new CustomOAuth('wordpress', config);
if (Meteor.isServer) {
Meteor.startup(function() {
return RocketChat.settings.get('API_Wordpress_URL', function(key, value) {
config.serverURL = value;
return WordPress.configure(config);
});
});
} else {
Meteor.startup(function() {
return Tracker.autorun(function() {
if (RocketChat.settings.get('API_Wordpress_URL')) {
config.serverURL = RocketChat.settings.get('API_Wordpress_URL');
return WordPress.configure(config);
}
});
});
}

@ -6,13 +6,12 @@ Package.describe({
Package.onUse(function(api) {
api.use('ecmascript');
api.use('coffeescript');
api.use('rocketchat:lib');
api.use('rocketchat:custom-oauth');
api.use('templating', 'client');
api.addFiles('common.coffee');
api.addFiles('common.js');
api.addFiles('wordpress-login-button.css', 'client');
api.addFiles('startup.coffee', 'server');
api.addFiles('startup.js', 'server');
});

@ -1,9 +0,0 @@
RocketChat.settings.addGroup 'OAuth', ->
@section 'WordPress', ->
enableQuery = {_id: 'Accounts_OAuth_Wordpress', value: true}
@add 'Accounts_OAuth_Wordpress', false, { type: 'boolean', public: true }
@add 'API_Wordpress_URL', '', { type: 'string', enableQuery: enableQuery, public: true }
@add 'Accounts_OAuth_Wordpress_id', '', { type: 'string', enableQuery: enableQuery }
@add 'Accounts_OAuth_Wordpress_secret', '', { type: 'string', enableQuery: enableQuery }
@add 'Accounts_OAuth_Wordpress_callback_url', '_oauth/wordpress', { type: 'relativeUrl', readonly: true, force: true, enableQuery: enableQuery }

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

Loading…
Cancel
Save