Merge remote-tracking branch 'origin/master' into models

# Conflicts:
#	packages/rocketchat-lib/package.js
#	server/lib/accounts.coffee
pull/822/head
Rodrigo Nascimento 10 years ago
commit bb65dd5688
  1. 3
      client/lib/tapi18n.coffee
  2. 80
      client/stylesheets/base.less
  3. 4
      client/views/app/message.coffee
  4. 19
      client/views/app/message.html
  5. 43
      client/views/app/room.coffee
  6. 6
      packages/rocketchat-ldap/package.js
  7. 104
      packages/rocketchat-lib/client/MessageAction.coffee
  8. 4
      packages/rocketchat-lib/i18n/en.i18n.json
  9. 4
      packages/rocketchat-lib/i18n/pt.i18n.json
  10. 0
      packages/rocketchat-lib/package-tap.i18n
  11. 19
      packages/rocketchat-lib/package.js
  12. 177
      packages/rocketchat-webrtc/webrtc.js
  13. 21
      server/lib/accounts.coffee

@ -13,3 +13,6 @@
@isRtl = (language) ->
# https://en.wikipedia.org/wiki/Right-to-left#cite_note-2
return language?.split('-').shift().toLowerCase() in ['ar', 'dv', 'fa', 'he', 'ku', 'ps', 'sd', 'ug', 'ur', 'yi']
UI.registerHelper '_t', (key) ->
return TAPi18next.t key

@ -2375,15 +2375,87 @@ a.github-fork {
cursor: pointer;
}
&:hover:not(.system) .edit-message {
display: inline-block;
display: block;
}
.delete-message {
display: none;
cursor: pointer;
}
&:hover:not(.system) .delete-message {
display: block;
}
.message-cog-container {
position: relative;
display: inline-block;
.message-cog {
visibility: hidden;
cursor: pointer;
}
}
&:hover:not(.system) .message-cog {
visibility: visible;
}
@keyframes dropdown-in {
0% {
display: none;
opacity: 0;
}
1% {
display: block;
opacity: 0;
transform: scale(0);
}
100% {
opacity: 1;
transform: scale(1);
}
}
.message-dropdown {
position: absolute;
top: -5px;
left: -2px;
z-index: 1000;
display: none;
background-color: #fff;
border: 1px solid #eee;
border-radius: 4px;
overflow: hidden;
box-shadow: 1px 1px 4px #B3B3B3;
transition: transform .15s ease-in-out, opacity .15s ease-in-out;
animation: dropdown-in .15s ease-in-out;
ul {
display: flex;
display: -webkit-flex;
padding: 0px;
font-size: 14px;
li {
display: block;
padding: 0px 8px;
font-weight: 400;
line-height: 26px;
cursor: pointer;
color: #666;
&:first-child {
padding-left: 6px;
background-color: #f8f8f8;
border-right: 1px solid #eee;
}
&:last-child {
padding-right: 13px;
}
&:hover {
background-color: #eee;
}
}
}
}
.user {
display: inline-block;
font-weight: 600;
@ -2437,6 +2509,12 @@ a.github-fork {
float: left;
}
}
// .message-dropdown {
// top: 100%;
// left: 0;
// }
&:hover {
.time {
display: inline-block;

@ -1,5 +1,7 @@
Template.message.helpers
actions: ->
return RocketChat.MessageAction.getButtons(this)
own: ->
return 'own' if this.u?._id is Meteor.userId()

@ -10,14 +10,21 @@
{{#if private}}
<span class="private">{{_ "Only_you_can_see_this_message"}}</span>
{{/if}}
{{#if canEdit}}
<i class="icon-pencil edit-message"></i>
{{/if}}
{{#if canDelete}}
<i class="icon-trash-1 delete-message"></i>
{{#if actions.length}}
<div class="message-cog-container">
<i class="icon-cog message-cog"></i>
<div class="message-dropdown">
<ul>
<li class="message-dropdown-close"><i class=" icon-angle-left"></i></li>
{{#each actions}}
<li class="{{id}} {{classes}}" title="{{_t i18nLabel}}"><i class="{{icon}}"></i></li>
{{/each}}
</ul>
</div>
</div>
{{/if}}
</span>
<div class="body" dir="auto">
{{{body}}}
</div>

@ -405,12 +405,13 @@ Template.room.events
'click .see-all': (e, instance) ->
instance.showUsersOffline.set(!instance.showUsersOffline.get())
"click .edit-message": (e) ->
Template.instance().chatMessages.edit(e.currentTarget.parentNode.parentNode)
input = Template.instance().find('.input-message')
Meteor.setTimeout ->
input.focus()
, 200
'click .message-cog': (e) ->
message_id = $(e.currentTarget).closest('.message').attr('id')
$('.message-dropdown:visible').hide()
$("\##{message_id} .message-dropdown").show()
'click .message-dropdown-close': ->
$('.message-dropdown:visible').hide()
"click .editing-commands-cancel > a": (e) ->
Template.instance().chatMessages.clearEditing()
@ -431,30 +432,6 @@ Template.room.events
'click .image-to-download': (event) ->
ChatMessage.update {_id: this._arguments[1]._id, 'urls.url': $(event.currentTarget).data('url')}, {$set: {'urls.$.downloadImages': true}}
'click .delete-message': (event) ->
message = @_arguments[1]
msg = event.currentTarget.parentNode.parentNode
instance = Template.instance()
return if msg.classList.contains("system")
swal {
title: t('Are_you_sure')
text: t('You_will_not_be_able_to_recover')
type: 'warning'
showCancelButton: true
confirmButtonColor: '#DD6B55'
confirmButtonText: t('Yes_delete_it')
cancelButtonText: t('Cancel')
closeOnConfirm: false
html: false
}, ->
swal
title: t('Deleted')
text: t('Your_entry_has_been_deleted')
type: 'success'
timer: 1000
showConfirmButton: false
instance.chatMessages.deleteMsg(message)
'click .pin-message': (event) ->
message = @_arguments[1]
instance = Template.instance()
@ -569,6 +546,12 @@ Template.room.onCreated ->
@autorun ->
self.subscribe 'fullUserData', Session.get('showUserInfo'), 1
for button in RocketChat.MessageAction.getButtons()
if _.isFunction button.action
evt = {}
evt["click .#{button.id}"] = button.action
Template.room.events evt
Template.room.onDestroyed ->
RocketChat.TabBar.resetButtons()

@ -12,11 +12,11 @@ Npm.depends({
// Loads all i18n.json files into tapi18nFiles
var _ = Npm.require('underscore');
var fs = Npm.require('fs');
tapi18nFiles = fs.readdirSync('packages/rocketchat-ldap/i18n').forEach(function(filename) {
tapi18nFiles = _.compact(_.map(fs.readdirSync('packages/rocketchat-ldap/i18n'), function(filename) {
if (fs.statSync('packages/rocketchat-ldap/i18n/' + filename).size > 16) {
return 'i18n/' + filename;
}
});
}));
Package.onUse(function(api) {
api.versionsFrom('1.0.3.1');
@ -35,7 +35,6 @@ Package.onUse(function(api) {
// Common
// TAP
api.addFiles('package-tap.i18n');
api.addFiles(tapi18nFiles);
// Client
api.addFiles('ldap_client.js', 'client');
@ -43,6 +42,7 @@ Package.onUse(function(api) {
api.addFiles('ldap_server.js', 'server');
api.addFiles('config_server.coffee', 'server');
api.addFiles(tapi18nFiles);
api.export('LDAP', 'server');
api.export('LDAP_DEFAULTS', 'server');

@ -0,0 +1,104 @@
RocketChat.MessageAction = new class
buttons = new ReactiveVar {}
###
config expects the following keys (only id is mandatory):
id (mandatory)
icon: string
i18nLabel: string
action: function(event, instance)
validation: function(message)
order: integer
###
addButton = (config) ->
unless config?.id
throw new Meteor.Error "MessageAction-addButton-error", "Button id was not informed."
Tracker.nonreactive ->
btns = buttons.get()
btns[config.id] = config
buttons.set btns
removeButton = (id) ->
Tracker.nonreactive ->
btns = buttons.get()
delete btns[id]
buttons.set btns
updateButton = (id, config) ->
Tracker.nonreactive ->
btns = buttons.get()
if btns[id]
btns[id] = _.extend btns[id], config
buttons.set btns
getButtons = (message) ->
allButtons = _.toArray buttons.get()
if message
allowedButtons = _.compact _.map allButtons, (button) ->
unless button.validation?
return true
if button.validation(message)
return button
else
allowedButtons = allButtons
return _.sortBy allowedButtons, 'order'
resetButtons = ->
buttons.set {}
addButton: addButton
removeButton: removeButton
updateButton: updateButton
getButtons: getButtons
resetButtons: resetButtons
Meteor.startup ->
RocketChat.MessageAction.addButton
id: 'edit-message'
icon: 'icon-pencil'
i18nLabel: 'rocketchat-lib:Edit'
action: (event, instance) ->
message = $(event.currentTarget).closest('.message')[0]
instance.chatMessages.edit(message)
$("\##{message.id} .message-dropdown").hide()
input = instance.find('.input-message')
Meteor.setTimeout ->
input.focus()
, 200
validation: (message) ->
return RocketChat.authz.hasAtLeastOnePermission('edit-message', message.rid ) or RocketChat.settings.get('Message_AllowEditing') and message.u?._id is Meteor.userId()
order: 1
RocketChat.MessageAction.addButton
id: 'delete-message'
icon: 'icon-trash-1'
i18nLabel: 'rocketchat-lib:Delete'
action: (event, instance) ->
message = @_arguments[1]
msg = $(event.currentTarget).closest('.message')[0]
$("\##{msg.id} .message-dropdown").hide()
return if msg.classList.contains("system")
swal {
title: t('Are_you_sure')
text: t('You_will_not_be_able_to_recover')
type: 'warning'
showCancelButton: true
confirmButtonColor: '#DD6B55'
confirmButtonText: t('Yes_delete_it')
cancelButtonText: t('Cancel')
closeOnConfirm: false
html: false
}, ->
swal
title: t('Deleted')
text: t('Your_entry_has_been_deleted')
type: 'success'
timer: 1000
showConfirmButton: false
instance.chatMessages.deleteMsg(message)
validation: (message) ->
return RocketChat.authz.hasAtLeastOnePermission('delete-message', message.rid ) or RocketChat.settings.get('Message_AllowDeleting') and message.u?._id is Meteor.userId()
order: 2

@ -0,0 +1,4 @@
{
"Edit": "Edit",
"Delete": "Delete"
}

@ -0,0 +1,4 @@
{
"Edit": "Editar",
"Delete": "Excluir"
}

@ -15,6 +15,19 @@ Package.onUse(function(api) {
api.use('underscore');
api.use('underscorestring:underscore.string');
// TAPi18n
api.use('templating', 'client');
var _ = Npm.require('underscore');
var fs = Npm.require('fs');
tapi18nFiles = _.compact(_.map(fs.readdirSync('packages/rocketchat-lib/i18n'), function(filename) {
if (fs.statSync('packages/rocketchat-lib/i18n/' + filename).size > 16) {
return 'i18n/' + filename;
}
}));
api.use(["tap:i18n@1.5.1"], ["client", "server"]);
api.imply('tap:i18n');
api.addFiles("package-tap.i18n", ["client", "server"]);
// COMMON
api.addFiles('lib/core.coffee');
api.addFiles('lib/callbacks.coffee');
@ -23,15 +36,14 @@ Package.onUse(function(api) {
api.addFiles('settings/lib/settings.coffee');
api.addFiles('settings/lib/rocketchat.coffee');
// CLIENT
api.addFiles('client/Notifications.coffee', 'client');
api.addFiles('client/TabBar.coffee', 'client');
api.addFiles('client/MessageAction.coffee', 'client');
api.addFiles('settings/client/startup.coffee', 'client');
api.addFiles('settings/client/rocketchat.coffee', 'client');
// SERVER
api.addFiles('server/functions/checkUsernameAvailability.coffee', 'server');
api.addFiles('server/functions/setUsername.coffee', 'server');
@ -46,6 +58,7 @@ Package.onUse(function(api) {
api.addFiles('server/Notifications.coffee', 'server');
// Settings
api.addFiles('settings/server/methods.coffee', 'server');
api.addFiles('settings/server/publication.coffee', 'server');
api.addFiles('settings/server/startup.coffee', 'server');
@ -59,6 +72,8 @@ Package.onUse(function(api) {
api.addFiles('server/models/Subscriptions.coffee', 'server');
api.addFiles('server/models/Rooms.coffee', 'server');
// TAPi18n -- needs to be added last
api.addFiles(tapi18nFiles, ["client", "server"]);
// EXPORT
api.export('RocketChat');

@ -3,6 +3,10 @@ webrtc = {
pc: undefined,
to: undefined,
room: undefined,
activeMediastream: undefined,
remoteDataSDP: undefined,
mode: undefined,
lastSeenTimestamp: new Date(),
debug: false,
config: {
iceServers: [
@ -14,17 +18,28 @@ webrtc = {
data.to = webrtc.to;
data.room = webrtc.room;
data.from = Meteor.user().username;
data.mod = (webrtc.mode ? webrtc.mode : 0);
RocketChat.Notifications.notifyUser(data.to, 'webrtc', data);
},
stop: function(sendEvent) {
if (webrtc.activeMediastream) {
webrtc.activeMediastream = undefined;
}
if (webrtc.pc) {
if (webrtc.pc.signalingState != 'closed') {
webrtc.pc.close();
}
if (sendEvent != false) {
RocketChat.Notifications.notifyUser(webrtc.to, 'webrtc', {to: webrtc.to, room: webrtc.room, from: Meteor.userId(), close: true});
webrtc.pc = undefined;
webrtc.mode = 0;
}
}
this.onRemoteUrl();
this.onSelfUrl();
if (sendEvent != false) {
RocketChat.Notifications.notifyUser(webrtc.to, 'webrtc', {to: webrtc.to, room: webrtc.room, from: Meteor.userId(), close: true});
}
},
log: function() {
if (webrtc.debug === true) {
@ -39,6 +54,18 @@ function onError() {
console.log(arguments);
}
webrtc.activateLocalStream = function() {
var media ={ "audio": true, "video": {mandatory: {minWidth:1280, minHeight:720}}} ;
// get the local stream, show it in the local video element and send it
navigator.getUserMedia(media, function (stream) {
webrtc.log('getUserMedia got stream');
webrtc.onSelfUrl(URL.createObjectURL(stream));
webrtc.activeMediastream = stream;
}, function(e) { webrtc.log('getUserMedia failed during activateLocalStream ' + e); });
}
// run start(true) to initiate a call
webrtc.start = function (isCaller, fromUsername) {
webrtc.pc = new RTCPeerConnection(webrtc.config);
@ -62,7 +89,7 @@ webrtc.start = function (isCaller, fromUsername) {
// once remote stream arrives, show it in the remote video element
webrtc.pc.onaddstream = function (evt) {
webrtc.log('onaddstream', arguments)
webrtc.log('onaddstream', arguments);
webrtc.onRemoteUrl(URL.createObjectURL(evt.stream));
};
@ -70,23 +97,53 @@ webrtc.start = function (isCaller, fromUsername) {
webrtc.log('oniceconnectionstatechange', arguments)
var srcElement = evt.srcElement || evt.target;
if (srcElement.iceConnectionState == 'disconnected' || srcElement.iceConnectionState == 'closed') {
webrtc.pc.getLocalStreams().forEach(function(stream) {
stream.stop();
webrtc.onSelfUrl();
});
webrtc.pc.getRemoteStreams().forEach(function(stream) {
if (stream.stop) {
if (webrtc.pc) {
webrtc.pc.getLocalStreams().forEach(function(stream) {
stream.stop();
}
webrtc.onRemoteUrl();
});
webrtc.pc = undefined;
webrtc.onSelfUrl();
});
webrtc.pc.getRemoteStreams().forEach(function(stream) {
if (stream.stop) {
stream.stop();
}
webrtc.onRemoteUrl();
});
webrtc.pc = undefined;
webrtc.mode = 0;
}
}
}
var getUserMedia = function() {
var gotDescription = function(desc) {
webrtc.pc.setLocalDescription(desc, function() {}, onError);
webrtc.send({ "sdp": desc.toJSON(), cid: webrtc.cid });
}
var CreateMonitoringOffer = function() {
webrtc.pc.createOffer(gotDescription, onError, { 'mandatory': { 'OfferToReceiveAudio': true, 'OfferToReceiveVideo': true } });
}
var AutoConnectStream = function() {
webrtc.pc.addStream(webrtc.activeMediastream);
webrtc.pc.setRemoteDescription(new RTCSessionDescription(webrtc.remoteDataSDP));
webrtc.pc.createAnswer(gotDescription, onError);
}
var LocalGetUserMedia = function() {
var media ={ "audio": true, "video": {mandatory: {minWidth:1280, minHeight:720}}} ;
// get the local stream, show it in the local video element and send it
navigator.getUserMedia({ "audio": true, "video": {mandatory: {minWidth:1280, minHeight:720}} }, function (stream) {
navigator.getUserMedia(media, function (stream) {
webrtc.log('getUserMedia got stream');
webrtc.onSelfUrl(URL.createObjectURL(stream));
webrtc.pc.addStream(stream);
@ -97,36 +154,49 @@ webrtc.start = function (isCaller, fromUsername) {
webrtc.pc.createAnswer(gotDescription, onError);
}
function gotDescription(desc) {
webrtc.pc.setLocalDescription(desc, function() {}, onError);
webrtc.send({ "sdp": desc.toJSON(), cid: webrtc.cid });
}
}, function() {});
}, function(e) { webrtc.log('getUserMedia failed' + e); });
}
if (isCaller) {
getUserMedia();
} else {
swal({
title: "Video call from "+fromUsername,
text: "Do you want to accept?",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "Yes",
cancelButtonText: "No"
}, function(isConfirm){
if (isConfirm) {
getUserMedia();
} else {
webrtc.stop();
webrtc.log('isCaller LocalGetUserMedia');
if (webrtc.mode) {
if (webrtc.mode === 2) {
CreateMonitoringOffer();
} else { // node === 1
LocalGetUserMedia();
}
});
} else {
// no mode
LocalGetUserMedia();
}
} else {
if (!webrtc.activeMediastream) {
swal({
title: "Video call from "+fromUsername,
text: "Do you want to accept?",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "Yes",
cancelButtonText: "No"
}, function(isConfirm){
if (isConfirm) {
LocalGetUserMedia();
} else {
webrtc.stop();
}
});
} else {
AutoConnectStream();
}
}
}
RocketChat.Notifications.onUser('webrtc', function(data) {
webrtc.log('stream.on', Meteor.userId(), data)
webrtc.log('processIncomingRtcMessage()', Meteor.userId(), data)
if (!webrtc.to) {
webrtc.to = data.room.replace(Meteor.userId(), '');
}
@ -135,15 +205,38 @@ RocketChat.Notifications.onUser('webrtc', function(data) {
webrtc.room = data.room;
}
if (data.close == true) {
// do not stop local video if in monitoring mode
if (data.close == true) {
if (webrtc.activeMediastream) {
if (webrtc.pc) {
webrtc.pc.getRemoteStreams().forEach(function(stream) {
if (!stream.stop) {
stream.stop();
}
});
webrtc.pc = undefined;
webrtc.mode = 0;
}
} else {
webrtc.stop(false);
return
}
return
}
if (!webrtc.pc)
if (!webrtc.pc) {
if ((webrtc.activeMediastream) && (data.sdp != undefined)){
webrtc.remoteDataSDP = data.sdp;
}
webrtc.start(false, data.from);
}
if (data.sdp) {
if (data.sdp != undefined) {
webrtc.pc.setRemoteDescription(new RTCSessionDescription(data.sdp));
} else {
if( ["closed", "failed", "disconnected", "completed"].indexOf(webrtc.pc.iceConnectionState) === -1) {

@ -24,9 +24,6 @@ Accounts.onCreateUser (options, user) ->
user.status = 'offline'
user.active = not RocketChat.settings.get 'Accounts_ManuallyApproveNewUsers'
# when inserting first user give them admin privileges otherwise make a regular user
roleName = if RocketChat.models.Users.findOne() then 'user' else 'admin'
if not user?.name? or user.name is ''
if options.profile?.name?
user.name = options.profile?.name
@ -45,14 +42,20 @@ Accounts.onCreateUser (options, user) ->
verified: true
]
Meteor.defer ->
# need to defer role assignment because underlying alanning:roles requires user
# to exist in users collection
RocketChat.authz.addUsersToRoles( user._id, roleName)
RocketChat.callbacks.run 'afterCreateUser', options, user
return user
# Wrap insertUserDoc to allow executing code after Accounts.insertUserDoc is run
Accounts.insertUserDoc = _.wrap Accounts.insertUserDoc, (insertUserDoc) ->
options = arguments[1]
user = arguments[2]
_id = insertUserDoc(options, user)
# when inserting first user give them admin privileges otherwise make a regular user
roleName = if RocketChat.models.Users.findOne() then 'user' else 'admin'
RocketChat.authz.addUsersToRoles(_id, roleName)
RocketChat.callbacks.run 'afterCreateUser', options, user
return _id
Accounts.validateLoginAttempt (login) ->
login = RocketChat.callbacks.run 'beforeValidateLogin', login

Loading…
Cancel
Save