diff --git a/packages/rocketchat-lib/client/lib/openRoom.coffee b/packages/rocketchat-lib/client/lib/openRoom.coffee deleted file mode 100644 index af932dfc5aa..00000000000 --- a/packages/rocketchat-lib/client/lib/openRoom.coffee +++ /dev/null @@ -1,79 +0,0 @@ -currentTracker = undefined - -@openRoom = (type, name) -> - Session.set 'openedRoom', null - - Meteor.defer -> - currentTracker = Tracker.autorun (c) -> - if RoomManager.open(type + name).ready() isnt true - BlazeLayout.render 'main', { modal: RocketChat.Layout.isEmbedded(), center: 'loading' } - return - - user = Meteor.user() - unless user?.username - return - - currentTracker = undefined - c.stop() - - room = RocketChat.roomTypes.findRoom(type, name, user) - if not room? - if type is 'd' - Meteor.call 'createDirectMessage', name, (err) -> - if !err - RoomManager.close(type + name) - openRoom('d', name) - else - Session.set 'roomNotFound', {type: type, name: name} - BlazeLayout.render 'main', {center: 'roomNotFound'} - return - else - Meteor.call 'getRoomByTypeAndName', type, name, (err, record) -> - if err? - Session.set 'roomNotFound', {type: type, name: name} - BlazeLayout.render 'main', {center: 'roomNotFound'} - else - delete record.$loki - RocketChat.models.Rooms.upsert({ _id: record._id }, _.omit(record, '_id')) - RoomManager.close(type + name) - openRoom(type, name) - - return - - mainNode = document.querySelector('.main-content') - if mainNode? - for child in mainNode.children - mainNode.removeChild child if child? - roomDom = RoomManager.getDomOfRoom(type + name, room._id) - mainNode.appendChild roomDom - if roomDom.classList.contains('room-container') - roomDom.querySelector('.messages-box > .wrapper').scrollTop = roomDom.oldScrollTop - - Session.set 'openedRoom', room._id - - fireGlobalEvent 'room-opened', _.omit room, 'usernames' - - Session.set 'editRoomTitle', false - RoomManager.updateMentionsMarksOfRoom type + name - Meteor.setTimeout -> - readMessage.readNow() - , 2000 - # KonchatNotification.removeRoomNotification(params._id) - - if Meteor.Device.isDesktop() and window.chatMessages?[room._id]? - setTimeout -> - $('.message-form .input-message').focus() - , 100 - - # update user's room subscription - sub = ChatSubscription.findOne({rid: room._id}) - if sub?.open is false - Meteor.call 'openRoom', room._id, (err) -> - if err - return handleError(err) - - if FlowRouter.getQueryParam('msg') - msg = { _id: FlowRouter.getQueryParam('msg'), rid: room._id } - RoomHistoryManager.getSurroundingMessages(msg); - - RocketChat.callbacks.run 'enter-room', sub diff --git a/packages/rocketchat-lib/client/lib/openRoom.js b/packages/rocketchat-lib/client/lib/openRoom.js new file mode 100644 index 00000000000..7224887c27d --- /dev/null +++ b/packages/rocketchat-lib/client/lib/openRoom.js @@ -0,0 +1,102 @@ +/* globals fireGlobalEvent readMessage*/ +const openRoom = function(type, name) { + Session.set('openedRoom', null); + Meteor.defer(function() { + Tracker.autorun(function(c) { + if (RoomManager.open(type + name).ready() !== true) { + return BlazeLayout.render('main', { + modal: RocketChat.Layout.isEmbedded(), + center: 'loading' + }); + } + const user = Meteor.user(); + if (!(user != null && user.username)) { + return; + } + c.stop(); + const room = RocketChat.roomTypes.findRoom(type, name, user); + if (room == null) { + if (type === 'd') { + Meteor.call('createDirectMessage', name, function(err) { + if (!err) { + RoomManager.close(type + name); + return openRoom('d', name); + } else { + Session.set('roomNotFound', { + type, + name + }); + BlazeLayout.render('main', { + center: 'roomNotFound' + }); + } + }); + } else { + Meteor.call('getRoomByTypeAndName', type, name, function(err, record) { + if (err != null) { + Session.set('roomNotFound', { + type, + name + }); + return BlazeLayout.render('main', { + center: 'roomNotFound' + }); + } else { + delete record.$loki; + RocketChat.models.Rooms.upsert({ + _id: record._id + }, _.omit(record, '_id')); + RoomManager.close(type + name); + return openRoom(type, name); + } + }); + } + return; + } + const mainNode = document.querySelector('.main-content'); + if (mainNode != null) { + [...((mainNode && mainNode.children) || [])].forEach(child => { + mainNode.removeChild(child); + }); + const roomDom = RoomManager.getDomOfRoom(type + name, room._id); + mainNode.appendChild(roomDom); + if (roomDom.classList.contains('room-container')) { + roomDom.querySelector('.messages-box > .wrapper').scrollTop = roomDom.oldScrollTop; + } + } + Session.set('openedRoom', room._id); + fireGlobalEvent('room-opened', _.omit(room, 'usernames')); + Session.set('editRoomTitle', false); + RoomManager.updateMentionsMarksOfRoom(type + name); + + Meteor.setTimeout(() => { + readMessage.readNow(); + }, 2000); + + if (Meteor.Device.isDesktop() && window.chatMessages && window.chatMessages[room._id] != null) { + setTimeout(() => { + return $('.message-form .input-message').focus(); + }, 100); + } + const sub = ChatSubscription.findOne({ + rid: room._id + }); + if (sub && sub.open === false) { + Meteor.call('openRoom', room._id, function(err) { + if (err) { + return handleError(err); + } + }); + } + if (FlowRouter.getQueryParam('msg')) { + const msg = { + _id: FlowRouter.getQueryParam('msg'), + rid: room._id + }; + RoomHistoryManager.getSurroundingMessages(msg); + } + return RocketChat.callbacks.run('enter-room', sub); + }); + }); +}; +this.openRoom = openRoom; diff --git a/packages/rocketchat-lib/client/lib/roomTypes.coffee b/packages/rocketchat-lib/client/lib/roomTypes.coffee deleted file mode 100644 index 424636cd38a..00000000000 --- a/packages/rocketchat-lib/client/lib/roomTypes.coffee +++ /dev/null @@ -1,87 +0,0 @@ -RocketChat.roomTypes = new class roomTypesClient extends roomTypesCommon - checkCondition: (roomType) -> - return not roomType.condition? or roomType.condition() - - getTypes: -> - orderedTypes = [] - - _.sortBy(@roomTypesOrder, 'order').forEach (type) => - orderedTypes.push @roomTypes[type.identifier] - - return orderedTypes - - getIcon: (roomType) -> - return @roomTypes[roomType]?.icon - - getRoomName: (roomType, roomData) -> - return @roomTypes[roomType]?.roomName roomData - - getIdentifiers: (except) -> - except = [].concat except - list = _.reject @roomTypesOrder, (t) -> return except.indexOf(t.identifier) isnt -1 - return _.map list, (t) -> return t.identifier - - getUserStatus: (roomType, roomId) -> - return @roomTypes[roomType]?.getUserStatus?(roomId) - - findRoom: (roomType, identifier, user) -> - return @roomTypes[roomType]?.findRoom identifier, user - - canSendMessage: (roomId) -> - return ChatSubscription.find({ rid: roomId }).count() > 0 - - readOnly: (roomId, user) -> - - fields = { ro: 1 } - - # if a user has been specified then we want to see if that user has been muted in the room - if user - fields.muted = 1 - - room = ChatRoom.findOne({ _id: roomId }, fields : fields) - - unless user - return room?.ro; - - userOwner = RoomRoles.findOne({ rid: roomId, "u._id": user._id, roles: 'owner' }, { fields: { _id: 1 } }) - - return room?.ro is true and Array.isArray(room?.muted) and room?.muted.indexOf(user.username) != -1 and !userOwner - - archived: (roomId) -> - fields = { archived: 1 } - - room = ChatRoom.findOne({ _id: roomId }, fields : fields) - - return room?.archived is true - - verifyCanSendMessage: (roomId) -> - room = ChatRoom.findOne({ _id: roomId }, { fields: { t: 1 } }) - return if not room?.t? - - roomType = room.t - - return @roomTypes[roomType]?.canSendMessage roomId if @roomTypes[roomType]?.canSendMessage? - - return @canSendMessage roomId - - verifyShowJoinLink: (roomId) -> - room = ChatRoom.findOne({ _id: roomId }, { fields: { t: 1 } }) - return if not room?.t? - - roomType = room.t - - if not @roomTypes[roomType]?.showJoinLink? - return false - - return @roomTypes[roomType].showJoinLink roomId - - getNotSubscribedTpl: (roomId) -> - room = ChatRoom.findOne({ _id: roomId }, { fields: { t: 1 } }) - return if not room?.t? - - roomType = room.t - - if not @roomTypes[roomType]?.notSubscribedTpl? - return false - - return @roomTypes[roomType].notSubscribedTpl diff --git a/packages/rocketchat-lib/client/lib/roomTypes.js b/packages/rocketchat-lib/client/lib/roomTypes.js new file mode 100644 index 00000000000..dec8c462888 --- /dev/null +++ b/packages/rocketchat-lib/client/lib/roomTypes.js @@ -0,0 +1,124 @@ +import roomTypesCommon from '../../lib/roomTypesCommon'; + +RocketChat.roomTypes = new class extends roomTypesCommon { + checkCondition(roomType) { + return (roomType.condition == null) || roomType.condition(); + } + getTypes() { + return _.sortBy(this.roomTypesOrder, 'order').map((type) => this.roomTypes[type.identifier]); + } + getIcon(roomType) { + return this.roomTypes[roomType] && this.roomTypes[roomType].icon; + } + getRoomName(roomType, roomData) { + return this.roomTypes[roomType] && this.roomTypes[roomType].roomName && this.roomTypes[roomType].roomName(roomData); + } + getIdentifiers(e) { + const except = [].concat(e); + const list = _.reject(this.roomTypesOrder, (t) => except.indexOf(t.identifier) !== -1); + return _.map(list, (t) => t.identifier); + } + getUserStatus(roomType, roomId) { + this.roomTypes[roomType] && typeof this.roomTypes[roomType].getUserStatus === 'function' && this.roomTypes[roomType].getUserStatus(roomId); + } + findRoom(roomType, identifier, user) { + return this.roomTypes[roomType] && this.roomTypes[roomType].findRoom(identifier, user); + } + canSendMessage(roomId) { + return ChatSubscription.find({ + rid: roomId + }).count() > 0; + } + readOnly(roomId, user) { + const fields = { + ro: 1 + }; + if (user) { + fields.muted = 1; + } + const room = ChatRoom.findOne({ + _id: roomId + }, { + fields + }); + if (!user) { + return room && room.ro; + } + /* globals RoomRoles */ + const userOwner = RoomRoles.findOne({ + rid: roomId, + 'u._id': user._id, + roles: 'owner' + }, { + fields: { + _id: 1 + } + }); + return room && (room.ro === true && Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1 && !userOwner); + } + archived(roomId) { + const fields = { + archived: 1 + }; + const room = ChatRoom.findOne({ + _id: roomId + }, { + fields + }); + return room && room.archived === true; + } + verifyCanSendMessage(roomId) { + const room = ChatRoom.findOne({ + _id: roomId + }, { + fields: { + t: 1 + } + }); + + if (room && !room.t) { + return; + } + + const roomType = room.t; + if (this.roomTypes[roomType] && this.roomTypes[roomType].canSendMessage) { + return this.roomTypes[roomType].canSendMessage(roomId); + } + return this.canSendMessage(roomId); + } + verifyShowJoinLink(roomId) { + const room = ChatRoom.findOne({ + _id: roomId + }, { + fields: { + t: 1 + } + }); + if (room && !room.t) { + return; + } + const roomType = room.t; + if (this.roomTypes[roomType] && !this.roomTypes[roomType].showJoinLink) { + return false; + } + return this.roomTypes[roomType].showJoinLink(roomId); + } + getNotSubscribedTpl(roomId) { + const room = ChatRoom.findOne({ + _id: roomId + }, { + fields: { + t: 1 + } + }); + if (room && !room.t) { + return; + } + const roomType = room.t; + if (this.roomTypes[roomType] && !this.roomTypes[roomType].notSubscribedTpl) { + return false; + } + return this.roomTypes[roomType].notSubscribedTpl; + } + +}; diff --git a/packages/rocketchat-lib/lib/callbacks.coffee b/packages/rocketchat-lib/lib/callbacks.coffee deleted file mode 100644 index d0e3feb8ff3..00000000000 --- a/packages/rocketchat-lib/lib/callbacks.coffee +++ /dev/null @@ -1,129 +0,0 @@ -# https://github.com/TelescopeJS/Telescope/blob/master/packages/telescope-lib/lib/callbacks.js - -### -# Callback hooks provide an easy way to add extra steps to common operations. -# @namespace RocketChat.callbacks -### -RocketChat.callbacks = {} - -if Meteor.isServer - RocketChat.callbacks.showTime = true - RocketChat.callbacks.showTotalTime = true -else - RocketChat.callbacks.showTime = false - RocketChat.callbacks.showTotalTime = false - -### -# Callback priorities -### -RocketChat.callbacks.priority = - HIGH: -1000 - MEDIUM: 0 - LOW: 1000 - -### -# Add a callback function to a hook -# @param {String} hook - The name of the hook -# @param {Function} callback - The callback function -### -RocketChat.callbacks.add = (hook, callback, priority, id) -> - # if callback array doesn't exist yet, initialize it - priority ?= RocketChat.callbacks.priority.MEDIUM - unless _.isNumber priority - priority = RocketChat.callbacks.priority.MEDIUM - callback.priority = priority - callback.id = id or Random.id() - RocketChat.callbacks[hook] ?= [] - - if RocketChat.callbacks.showTime is true - err = new Error - callback.stack = err.stack - - # if not id? - # console.log('Callback without id', callback.stack) - - # Avoid adding the same callback twice - for cb in RocketChat.callbacks[hook] - if cb.id is callback.id - return - - RocketChat.callbacks[hook].push callback - return - -### -# Remove a callback from a hook -# @param {string} hook - The name of the hook -# @param {string} id - The callback's id -### - -RocketChat.callbacks.remove = (hookName, id) -> - RocketChat.callbacks[hookName] = _.reject RocketChat.callbacks[hookName], (callback) -> - callback.id is id - return - -### -# Successively run all of a hook's callbacks on an item -# @param {String} hook - The name of the hook -# @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks -# @param {Object} [constant] - An optional constant that will be passed along to each callback -# @returns {Object} Returns the item after it's been through all the callbacks for this hook -### - -RocketChat.callbacks.run = (hook, item, constant) -> - callbacks = RocketChat.callbacks[hook] - if !!callbacks?.length - if RocketChat.callbacks.showTotalTime is true - totalTime = 0 - - # if the hook exists, and contains callbacks to run - result = _.sortBy(callbacks, (callback) -> return callback.priority or RocketChat.callbacks.priority.MEDIUM).reduce (result, callback) -> - # console.log(callback.name); - if RocketChat.callbacks.showTime is true or RocketChat.callbacks.showTotalTime is true - time = Date.now() - - callbackResult = callback result, constant - - if RocketChat.callbacks.showTime is true or RocketChat.callbacks.showTotalTime is true - currentTime = Date.now() - time - totalTime += currentTime - if RocketChat.callbacks.showTime is true - if Meteor.isServer - RocketChat.statsTracker.timing('callbacks.time', currentTime, ["hook:#{hook}", "callback:#{callback.id}"]); - else - console.log String(currentTime), hook, callback.id, callback.stack?.split?('\n')[2]?.match(/\(.+\)/)?[0] - - return if typeof callbackResult == 'undefined' then result else callbackResult - , item - - if RocketChat.callbacks.showTotalTime is true - if Meteor.isServer - RocketChat.statsTracker.timing('callbacks.totalTime', totalTime, ["hook:#{hook}"]); - else - console.log hook+':', totalTime - - return result - else - # else, just return the item unchanged - return item - -### -# Successively run all of a hook's callbacks on an item, in async mode (only works on server) -# @param {String} hook - The name of the hook -# @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks -# @param {Object} [constant] - An optional constant that will be passed along to each callback -### - -RocketChat.callbacks.runAsync = (hook, item, constant) -> - callbacks = RocketChat.callbacks[hook] - if Meteor.isServer and !!callbacks?.length - # use defer to avoid holding up client - Meteor.defer -> - # run all post submit server callbacks on post object successively - _.sortBy(callbacks, (callback) -> return callback.priority or RocketChat.callbacks.priority.MEDIUM).forEach (callback) -> - # console.log(callback.name); - callback item, constant - return - return - else - return item - return diff --git a/packages/rocketchat-lib/lib/callbacks.js b/packages/rocketchat-lib/lib/callbacks.js new file mode 100644 index 00000000000..511523f1e40 --- /dev/null +++ b/packages/rocketchat-lib/lib/callbacks.js @@ -0,0 +1,131 @@ +/* +* Callback hooks provide an easy way to add extra steps to common operations. +* @namespace RocketChat.callbacks +*/ + +RocketChat.callbacks = {}; + +if (Meteor.isServer) { + RocketChat.callbacks.showTime = true; + RocketChat.callbacks.showTotalTime = true; +} else { + RocketChat.callbacks.showTime = false; + RocketChat.callbacks.showTotalTime = false; +} + + +/* +* Callback priorities +*/ + +RocketChat.callbacks.priority = { + HIGH: -1000, + MEDIUM: 0, + LOW: 1000 +}; + + +/* +* Add a callback function to a hook +* @param {String} hook - The name of the hook +* @param {Function} callback - The callback function +*/ + +RocketChat.callbacks.add = function(hook, callback, priority, id) { + if (priority == null) { + priority = RocketChat.callbacks.priority.MEDIUM; + } + if (!_.isNumber(priority)) { + priority = RocketChat.callbacks.priority.MEDIUM; + } + callback.priority = priority; + callback.id = id || Random.id(); + RocketChat.callbacks[hook] = RocketChat.callbacks[hook] || []; + if (RocketChat.callbacks.showTime === true) { + const err = new Error; + callback.stack = err.stack; + } + if (RocketChat.callbacks[hook].find((cb) => cb.id === callback.id)) { + return; + } + RocketChat.callbacks[hook].push(callback); +}; + + +/* +* Remove a callback from a hook +* @param {string} hook - The name of the hook +* @param {string} id - The callback's id +*/ + +RocketChat.callbacks.remove = function(hookName, id) { + RocketChat.callbacks[hookName] = _.reject(RocketChat.callbacks[hookName], (callback) => callback.id === id); +}; + + +/* +* Successively run all of a hook's callbacks on an item +* @param {String} hook - The name of the hook +* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks +* @param {Object} [constant] - An optional constant that will be passed along to each callback +* @returns {Object} Returns the item after it's been through all the callbacks for this hook +*/ + +RocketChat.callbacks.run = function(hook, item, constant) { + const callbacks = RocketChat.callbacks[hook]; + if (!callbacks && callbacks.length) { + let totalTime = 0; + const result = _.sortBy(callbacks, function(callback) { + return callback.priority || RocketChat.callbacks.priority.MEDIUM; + }).reduce(function(result, callback) { + let time = 0; + if (RocketChat.callbacks.showTime === true || RocketChat.callbacks.showTotalTime === true) { + time = Date.now(); + } + const callbackResult = callback(result, constant); + if (RocketChat.callbacks.showTime === true || RocketChat.callbacks.showTotalTime === true) { + const currentTime = Date.now() - time; + totalTime += currentTime; + if (RocketChat.callbacks.showTime === true) { + if (Meteor.isServer) { + RocketChat.statsTracker.timing('callbacks.time', currentTime, [`hook:${ hook }`, `callback:${ callback.id }`]); + } else { + let stack = callback.stack && typeof callback.stack.split === 'function' && callback.stack.split('\n'); + stack = stack && stack[2] && (stack[2].match(/\(.+\)/)||[])[0]; + console.log(String(currentTime), hook, callback.id, stack); + } + } + } + return (typeof callbackResult === 'undefined') ? result : callbackResult; + }, item); + if (RocketChat.callbacks.showTotalTime === true) { + if (Meteor.isServer) { + RocketChat.statsTracker.timing('callbacks.totalTime', totalTime, [`hook:${ hook }`]); + } else { + console.log(`${ hook }:`, totalTime); + } + } + return result; + } else { + return item; + } +}; + + +/* +* Successively run all of a hook's callbacks on an item, in async mode (only works on server) +* @param {String} hook - The name of the hook +* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks +* @param {Object} [constant] - An optional constant that will be passed along to each callback +*/ + +RocketChat.callbacks.runAsync = function(hook, item, constant) { + const callbacks = RocketChat.callbacks[hook]; + if (Meteor.isServer && callbacks && callbacks.length) { + Meteor.defer(function() { + _.sortBy(callbacks, (callback) => callback.priority || RocketChat.callbacks.priority.MEDIUM).forEach((callback) => callback(item, constant)); + }); + } else { + return item; + } +}; diff --git a/packages/rocketchat-lib/lib/promises.coffee b/packages/rocketchat-lib/lib/promises.coffee deleted file mode 100644 index a23ab907b73..00000000000 --- a/packages/rocketchat-lib/lib/promises.coffee +++ /dev/null @@ -1,93 +0,0 @@ -# https://github.com/TelescopeJS/Telescope/blob/master/packages/telescope-lib/lib/callbacks.js - -### -# Callback hooks provide an easy way to add extra steps to common operations. -# @namespace RocketChat.promises -### -RocketChat.promises = {} - -### -# Callback priorities -### -RocketChat.promises.priority = - HIGH: -1000 - MEDIUM: 0 - LOW: 1000 - -### -# Add a callback function to a hook -# @param {String} hook - The name of the hook -# @param {Function} callback - The callback function -### - -RocketChat.promises.add = (hook, callback, priority, id) -> - # if callback array doesn't exist yet, initialize it - priority ?= RocketChat.promises.priority.MEDIUM - unless _.isNumber priority - priority = RocketChat.promises.priority.MEDIUM - callback.priority = priority - callback.id = id or Random.id() - RocketChat.promises[hook] ?= [] - - # Avoid adding the same callback twice - for cb in RocketChat.promises[hook] - if cb.id is callback.id - return - - RocketChat.promises[hook].push callback - return - -### -# Remove a callback from a hook -# @param {string} hook - The name of the hook -# @param {string} id - The callback's id -### - -RocketChat.promises.remove = (hookName, id) -> - RocketChat.promises[hookName] = _.reject RocketChat.promises[hookName], (callback) -> - callback.id is id - return - -### -# Successively run all of a hook's callbacks on an item -# @param {String} hook - The name of the hook -# @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks -# @param {Object} [constant] - An optional constant that will be passed along to each callback -# @returns {Object} Returns the item after it's been through all the callbacks for this hook -### - -RocketChat.promises.run = (hook, item, constant) -> - callbacks = RocketChat.promises[hook] - if !!callbacks?.length - # if the hook exists, and contains callbacks to run - callbacks = _.sortBy(callbacks, (callback) -> return callback.priority or RocketChat.promises.priority.MEDIUM) - return callbacks.reduce (previousPromise, callback) -> - return new Promise (resolve, reject) -> - previousPromise.then (result) -> - callback(result, constant).then(resolve, reject) - , Promise.resolve(item) - else - # else, just return the item unchanged - return Promise.resolve(item) - -### -# Successively run all of a hook's callbacks on an item, in async mode (only works on server) -# @param {String} hook - The name of the hook -# @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks -# @param {Object} [constant] - An optional constant that will be passed along to each callback -### - -RocketChat.promises.runAsync = (hook, item, constant) -> - callbacks = RocketChat.promises[hook] - if Meteor.isServer and !!callbacks?.length - # use defer to avoid holding up client - Meteor.defer -> - # run all post submit server callbacks on post object successively - _.sortBy(callbacks, (callback) -> return callback.priority or RocketChat.promises.priority.MEDIUM).forEach (callback) -> - # console.log(callback.name); - callback item, constant - return - return - else - return item - return diff --git a/packages/rocketchat-lib/lib/promises.js b/packages/rocketchat-lib/lib/promises.js new file mode 100644 index 00000000000..1e040f172bc --- /dev/null +++ b/packages/rocketchat-lib/lib/promises.js @@ -0,0 +1,89 @@ + +/* +* Callback hooks provide an easy way to add extra steps to common operations. +* @namespace RocketChat.promises +*/ + +RocketChat.promises = {}; + + +/* +* Callback priorities +*/ + +RocketChat.promises.priority = { + HIGH: -1000, + MEDIUM: 0, + LOW: 1000 +}; + + +/* +* Add a callback function to a hook +* @param {String} hook - The name of the hook +* @param {Function} callback - The callback function +*/ + +RocketChat.promises.add = function(hook, callback, p = RocketChat.promises.priority.MEDIUM, id) { + const priority = !_.isNumber(p) ? RocketChat.promises.priority.MEDIUM : p; + callback.priority = priority; + callback.id = id || Random.id(); + RocketChat.promises[hook] = RocketChat.promises[hook] || []; + if (RocketChat.promises[hook].find(cb => cb.id === callback.id)) { + return; + } + RocketChat.promises[hook].push(callback); +}; + + +/* +* Remove a callback from a hook +* @param {string} hook - The name of the hook +* @param {string} id - The callback's id +*/ + +RocketChat.promises.remove = function(hookName, id) { + RocketChat.promises[hookName] = _.reject(RocketChat.promises[hookName], (callback) => callback.id === id); +}; + + +/* +* Successively run all of a hook's callbacks on an item +* @param {String} hook - The name of the hook +* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks +* @param {Object} [constant] - An optional constant that will be passed along to each callback +* @returns {Object} Returns the item after it's been through all the callbacks for this hook +*/ + +RocketChat.promises.run = function(hook, item, constant) { + let callbacks = RocketChat.promises[hook]; + if (callbacks == null || callbacks.length === 0) { + return Promise.resolve(item); + } + callbacks = _.sortBy(callbacks, (callback) => callback.priority || RocketChat.promises.priority.MEDIUM); + return callbacks.reduce(function(previousPromise, callback) { + return new Promise(function(resolve, reject) { + return previousPromise.then((result) => callback(result, constant).then(resolve, reject)); + }); + }, Promise.resolve(item)); +}; + + +/* +* Successively run all of a hook's callbacks on an item, in async mode (only works on server) +* @param {String} hook - The name of the hook +* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks +* @param {Object} [constant] - An optional constant that will be passed along to each callback +*/ + +RocketChat.promises.runAsync = function(hook, item, constant) { + const callbacks = RocketChat.promises[hook]; + if (!Meteor.isServer || callbacks == null || callbacks.length === 0) { + return item; + } + Meteor.defer(() => { + _.sortBy(callbacks, (callback) => callback.priority || RocketChat.promises.priority.MEDIUM).forEach(function(callback) { + callback(item, constant); + }); + }); +}; diff --git a/packages/rocketchat-lib/lib/roomTypesCommon.coffee b/packages/rocketchat-lib/lib/roomTypesCommon.coffee deleted file mode 100644 index a351dc25892..00000000000 --- a/packages/rocketchat-lib/lib/roomTypesCommon.coffee +++ /dev/null @@ -1,75 +0,0 @@ -class @roomTypesCommon - roomTypes: {} - roomTypesOrder: [] - mainOrder: 1 - - ### Adds a room type to app - @param identifier An identifier to the room type. If a real room, MUST BE the same of `db.rocketchat_room.t` field, if not, can be null - @param order Order number of the type - @param config - template: template name to render on sideNav - icon: icon class - route: - name: route name - action: route action function - ### - add: (identifier, order, config) -> - unless identifier? - identifier = Random.id() - - if @roomTypes[identifier]? - return false - - if not order? - order = @mainOrder + 10 - @mainOrder += 10 - - # @TODO validate config options - @roomTypesOrder.push - identifier: identifier - order: order - @roomTypes[identifier] = config - - if config.route?.path? and config.route?.name? and config.route?.action? - routeConfig = - name: config.route.name - action: config.route.action - - if Meteor.isClient - routeConfig.triggersExit = [ roomExit ] - - FlowRouter.route config.route.path, routeConfig - - hasCustomLink: (roomType) -> - return @roomTypes[roomType]?.route?.link? - - ### - @param roomType: room type (e.g.: c (for channels), d (for direct channels)) - @param subData: the user's subscription data - ### - getRouteLink: (roomType, subData) -> - unless @roomTypes[roomType]? - return false - - routeData = {} - - if @roomTypes[roomType]?.route?.link? - routeData = @roomTypes[roomType].route.link(subData) - else if subData?.name? - routeData = { name: subData.name } - - return FlowRouter.path @roomTypes[roomType].route.name, routeData - - openRouteLink: (roomType, subData, queryParams) -> - unless @roomTypes[roomType]? - return false - - routeData = {} - - if @roomTypes[roomType]?.route?.link? - routeData = @roomTypes[roomType].route.link(subData) - else if subData?.name? - routeData = { name: subData.name } - - return FlowRouter.go @roomTypes[roomType].route.name, routeData, queryParams - diff --git a/packages/rocketchat-lib/lib/roomTypesCommon.js b/packages/rocketchat-lib/lib/roomTypesCommon.js new file mode 100644 index 00000000000..d7275875d8e --- /dev/null +++ b/packages/rocketchat-lib/lib/roomTypesCommon.js @@ -0,0 +1,83 @@ +/* globals roomExit*/ +this.roomTypesCommon = class { + constructor() { + this.roomTypes = {}; + this.roomTypesOrder = []; + this.mainOrder = 1; + } + + /* Adds a room type to app + @param identifier An identifier to the room type. If a real room, MUST BE the same of `db.rocketchat_room.t` field, if not, can be null + @param order Order number of the type + @param config + template: template name to render on sideNav + icon: icon class + route: + name: route name + action: route action function + */ + + add(identifier = Random.id(), order, config) { + if (this.roomTypes[identifier] != null) { + return false; + } + if (order == null) { + order = this.mainOrder + 10; + this.mainOrder += 10; + } + this.roomTypesOrder.push({ + identifier, + order + }); + this.roomTypes[identifier] = config; + if (config.route && config.route.path && config.route.name && config.route.action) { + const routeConfig = { + name: config.route.name, + action: config.route.action + }; + if (Meteor.isClient) { + routeConfig.triggersExit = [roomExit]; + } + return FlowRouter.route(config.route.path, routeConfig); + } + } + + hasCustomLink(roomType) { + return this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link != null; + } + + /* + @param roomType: room type (e.g.: c (for channels), d (for direct channels)) + @param subData: the user's subscription data + */ + + getRouteLink(roomType, subData) { + if (this.roomTypes[roomType] == null) { + return false; + } + let routeData = {}; + if (this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link) { + routeData = this.roomTypes[roomType].route.link(subData); + } else if (subData && subData.name) { + routeData = { + name: subData.name + }; + } + return FlowRouter.path(this.roomTypes[roomType].route.name, routeData); + } + + openRouteLink(roomType, subData, queryParams) { + if (this.roomTypes[roomType] == null) { + return false; + } + let routeData = {}; + if (this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link) { + routeData = this.roomTypes[roomType].route.link(subData); + } else if (subData && subData.name) { + routeData = { + name: subData.name + }; + } + return FlowRouter.go(this.roomTypes[roomType].route.name, routeData, queryParams); + } +}; diff --git a/packages/rocketchat-lib/lib/settings.coffee b/packages/rocketchat-lib/lib/settings.coffee deleted file mode 100644 index cdc221064b3..00000000000 --- a/packages/rocketchat-lib/lib/settings.coffee +++ /dev/null @@ -1,83 +0,0 @@ -### -# RocketChat.settings holds all packages settings -# @namespace RocketChat.settings -### -RocketChat.settings = - callbacks: {} - regexCallbacks: {} - ts: new Date - - get: (_id, callback) -> - if callback? - RocketChat.settings.onload _id, callback - if _id is '*' and Meteor.settings? - for key, value of Meteor.settings - callback key, value - return - - if _.isRegExp(_id) - for key, value of Meteor.settings when _id.test(key) - callback key, value - return - - if Meteor.settings?[_id]? - callback _id, Meteor.settings?[_id] - else - if _.isRegExp(_id) - items = [] - for key, value of Meteor.settings when _id.test(key) - items.push - key: key - value: value - return items - - return Meteor.settings?[_id] - - set: (_id, value, callback) -> - Meteor.call 'saveSetting', _id, value, callback - - batchSet: (settings, callback) -> - - # async -> sync - # http://daemon.co.za/2012/04/simple-async-with-only-underscore/ - - save = (setting) -> - return (callback) -> - Meteor.call 'saveSetting', setting._id, setting.value, setting.editor, callback - - actions = _.map settings, (setting) -> save(setting) - _(actions).reduceRight(_.wrap, (err, success) -> return callback err, success)() - - load: (key, value, initialLoad) -> - if RocketChat.settings.callbacks[key]? - for callback in RocketChat.settings.callbacks[key] - callback key, value, initialLoad - - if RocketChat.settings.callbacks['*']? - for callback in RocketChat.settings.callbacks['*'] - callback key, value, initialLoad - - for cbKey, cbValue of RocketChat.settings.regexCallbacks - if cbValue.regex.test(key) - callback(key, value, initialLoad) for callback in cbValue.callbacks - - - onload: (key, callback) -> - # if key is '*' - # for key, value in Meteor.settings - # callback key, value, false - # else if Meteor.settings?[_id]? - # callback key, Meteor.settings[_id], false - - keys = [].concat key - - for k in keys - if _.isRegExp k - RocketChat.settings.regexCallbacks[k.source] ?= { - regex: k - callbacks: [] - } - RocketChat.settings.regexCallbacks[k.source].callbacks.push callback - else - RocketChat.settings.callbacks[k] ?= [] - RocketChat.settings.callbacks[k].push callback diff --git a/packages/rocketchat-lib/lib/settings.js b/packages/rocketchat-lib/lib/settings.js new file mode 100644 index 00000000000..2e5bb09eba0 --- /dev/null +++ b/packages/rocketchat-lib/lib/settings.js @@ -0,0 +1,102 @@ + +/* +* RocketChat.settings holds all packages settings +* @namespace RocketChat.settings +*/ +RocketChat.settings = { + callbacks: {}, + regexCallbacks: {}, + ts: new Date, + get(_id, callback) { + if (callback != null) { + RocketChat.settings.onload(_id, callback); + if (!Meteor.settings) { + return; + } + if (_id === '*') { + return Object.keys(Meteor.settings).forEach(key => { + const value = Meteor.settings[key]; + callback(key, value); + }); + } + if (_.isRegExp(_id) && Meteor.settings) { + return Object.keys(Meteor.settings).forEach(key => { + if (!_id.test(key)) { + return; + } + const value = Meteor.settings[key]; + callback(key, value); + }); + } + return Meteor.settings[_id] && callback(_id, Meteor.settings[_id]); + } else { + if (!Meteor.settings) { + return; + } + if (_.isRegExp(_id)) { + return Object.keys(Meteor.settings).reduce((items, key) => { + const value = Meteor.settings[key]; + if (_id.test(key)) { + items.push({ + key, + value + }); + } + return items; + }, []); + } + return Meteor.settings && Meteor.settings[_id]; + } + }, + set(_id, value, callback) { + return Meteor.call('saveSetting', _id, value, callback); + }, + batchSet(settings, callback) { + // async -> sync + // http://daemon.co.za/2012/04/simple-async-with-only-underscore/ + const save = function(setting) { + return function(callback) { + return Meteor.call('saveSetting', setting._id, setting.value, setting.editor, callback); + }; + }; + const actions = _.map(settings, (setting) => save(setting)); + return _(actions).reduceRight(_.wrap, (err, success) => callback(err, success))(); + }, + load(key, value, initialLoad) { + Object.keys({ + '*': 1, + [key]: 1 + }).forEach(key => { + if (RocketChat.settings.callbacks[key]) { + RocketChat.settings.callbacks[key].forEach(callback => callback(key, value, initialLoad)); + } + }); + Object.keys(RocketChat.settings.regexCallbacks).forEach(cbKey => { + const cbValue = RocketChat.settings.regexCallbacks[cbKey]; + if (!cbValue.regex.test(key)) { + return; + } + cbValue.callbacks.forEach(callback => callback(key, value, initialLoad)); + }); + }, + onload(key, callback) { + // if key is '*' + // for key, value in Meteor.settings + // callback key, value, false + // else if Meteor.settings?[_id]? + // callback key, Meteor.settings[_id], false + const keys = [].concat(key); + keys.forEach(k => { + if (_.isRegExp(k)) { + RocketChat.settings.regexCallbacks[name = k.source] = RocketChat.settings.regexCallbacks[name = k.source] || { + regex: k, + callbacks: [] + }; + RocketChat.settings.regexCallbacks[k.source].callbacks.push(callback); + } else { + RocketChat.settings.callbacks[k] = RocketChat.settings.callbacks[k] || []; + RocketChat.settings.callbacks[k].push(callback); + } + }); + } +}; diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index 8aea1663f7b..c7b8e96eab9 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -52,13 +52,13 @@ Package.onUse(function(api) { // COMMON LIB api.addFiles('lib/getURL.js'); - api.addFiles('lib/settings.coffee'); + api.addFiles('lib/settings.js'); api.addFiles('lib/configLogger.js'); - api.addFiles('lib/callbacks.coffee'); + api.addFiles('lib/callbacks.js'); api.addFiles('lib/fileUploadRestrictions.js'); api.addFiles('lib/placeholders.js'); - api.addFiles('lib/promises.coffee'); - api.addFiles('lib/roomTypesCommon.coffee'); + api.addFiles('lib/promises.js'); + api.addFiles('lib/roomTypesCommon.js'); api.addFiles('lib/slashCommand.js'); api.addFiles('lib/Message.js'); api.addFiles('lib/MessageTypes.js'); @@ -178,10 +178,10 @@ Package.onUse(function(api) { api.addFiles('client/lib/TabBar.js', 'client'); api.addFiles('client/lib/RocketChatTabBar.js', 'client'); api.addFiles('client/lib/cachedCollection.js', 'client'); - api.addFiles('client/lib/openRoom.coffee', 'client'); + api.addFiles('client/lib/openRoom.js', 'client'); api.addFiles('client/lib/roomExit.js', 'client'); api.addFiles('client/lib/settings.js', 'client'); - api.addFiles('client/lib/roomTypes.coffee', 'client'); + api.addFiles('client/lib/roomTypes.js', 'client'); api.addFiles('client/lib/userRoles.js', 'client'); api.addFiles('client/lib/Layout.js', 'client');