Merge branch 'master' into chatops-package

pull/1002/head
Aaron Ogle 10 years ago
commit 10dec4a261
  1. 4
      .meteor/cordova-plugins
  2. 5
      .meteor/versions
  3. 3
      README.md
  4. 2
      client/lib/chatMessages.coffee
  5. 1
      client/lib/collections.coffee
  6. 4
      client/lib/fileUpload.coffee
  7. 0
      client/lib/recorderjs/audioRecorder.coffee
  8. 1
      client/routes/roomRoute.coffee
  9. 32
      client/stylesheets/base.less
  10. 69
      client/views/account/accountProfile.coffee
  11. 6
      client/views/account/accountProfile.html
  12. 5
      client/views/account/avatar/prompt.coffee
  13. 26
      client/views/account/avatar/prompt.html
  14. 4
      client/views/app/flexTabBar.html
  15. 2
      client/views/app/messagePopup.coffee
  16. 4
      client/views/app/messagePopup.html
  17. 68
      client/views/app/messagePopupConfig.coffee
  18. 14
      client/views/app/messagePopupConfig.html
  19. 6
      client/views/app/room.coffee
  20. 18
      client/views/app/room.html
  21. 33
      client/views/app/tabBar/uploadedFilesList.coffee
  22. 25
      client/views/app/tabBar/uploadedFilesList.html
  23. 10
      i18n/en.i18n.json
  24. 2
      i18n/pt.i18n.json
  25. 4
      mobile-config.js
  26. 23
      packages/rocketchat-autolinker/autolinker.coffee
  27. 2
      packages/rocketchat-colors/client.coffee
  28. 1
      packages/rocketchat-highlight/highlight.coffee
  29. 1
      packages/rocketchat-lib/client/TabBar.coffee
  30. 2
      packages/rocketchat-lib/server/models/Users.coffee
  31. 16
      packages/rocketchat-lib/settings/server/startup.coffee
  32. 1
      packages/rocketchat-markdown/markdown.coffee
  33. 4
      packages/rocketchat-slashcommands-invite/i18n/en.i18n.json
  34. 4
      packages/rocketchat-slashcommands-invite/i18n/pt.i18n.json
  35. 14
      packages/rocketchat-slashcommands-invite/invite.coffee
  36. 13
      packages/rocketchat-slashcommands-invite/package.js
  37. 3
      packages/rocketchat-slashcommands-join/i18n/en.i18n.json
  38. 3
      packages/rocketchat-slashcommands-join/i18n/pt.i18n.json
  39. 8
      packages/rocketchat-slashcommands-join/join.coffee
  40. 22
      packages/rocketchat-slashcommands-join/package.js
  41. 12
      packages/rocketchat-spotify/spotify.coffee
  42. 20
      server/methods/getAvatarSuggestion.coffee
  43. 7
      server/methods/getTotalChannels.coffee
  44. 4
      server/methods/saveUserProfile.coffee
  45. 5
      server/methods/setPassword.coffee
  46. 4
      server/methods/updateMessage.coffee
  47. 28
      server/publications/channelAutocomplete.coffee
  48. 2
      server/publications/fullUserData.coffee
  49. 27
      server/publications/roomFiles.coffee
  50. 27
      server/startup/migrations/v20.coffee

@ -1,2 +1,2 @@
com.ionic.keyboard@https://github.com/driftyco/ionic-plugin-keyboard.git#66926f5e25fcd71b65799adaf12fa0b2df65ce78
com.phonegap.plugins.facebookconnect@0.11.0
ionic-plugin-keyboard@1.0.7
phonegap-facebook-plugin@https://github.com/Sing-Li/phonegap-facebook-plugin.git#667dda292cf70d8fb725f512cedef3fe0dd55ac3

@ -60,7 +60,7 @@ jquery@1.11.4
kadira:blaze-layout@2.1.0
kadira:flow-router@2.6.2
kevohagan:sweetalert@1.0.0
konecty:autolinker@1.0.2
konecty:autolinker@1.0.3
konecty:change-case@2.3.0
konecty:delayed-task@1.0.0
konecty:mongo-counter@0.0.3
@ -139,13 +139,14 @@ rocketchat:oembed@0.0.1
rocketchat:slashcommands-invite@0.0.1
rocketchat:slashcommands-join@0.0.1
rocketchat:slashcommands-leave@0.0.1
rocketchat:spotify@0.0.1
rocketchat:statistics@0.0.1
rocketchat:webrtc@0.0.1
rocketchat:wordpress@0.0.1
routepolicy@1.0.6
service-configuration@1.0.5
session@1.1.1
sha@1.0.4
rocketchat:wordpress@0.0.1
simple:highlight.js@1.0.9
simple:json-routes@1.0.4
spacebars@1.0.7

@ -65,6 +65,9 @@ It is a great solution for communities and companies wanting to privately host t
##### [clasesdeperiodismo.com](http://www.clasesdeperiodismo.com/2015/05/30/un-chat-de-codigo-abierto-que-puedes-anadir-a-la-web/)
> Un chat de código abierto que puedes añadir a la web
##### [snowulf.com](https://snowulf.com/2015/09/25/why-slack-when-you-can-rocket-chat/)
> Why Slack when you can Rocket.chat?
## Features
- BYOS (bring your own server)

@ -207,6 +207,8 @@ class @ChatMessages
this.clearEditing()
return
else if k is 38 or k is 40 # Arrow Up or down
return true if event.shiftKey
if k is 38
return if input.value.slice(0, input.selectionStart).match(/[\n]/) isnt null
this.toPrevMessage()

@ -2,3 +2,4 @@
@ChatRoom = new Meteor.Collection 'rocketchat_room'
@ChatSubscription = new Meteor.Collection 'rocketchat_subscription'
@UserAndRoom = new Meteor.Collection null
@CachedChannelList = new Meteor.Collection null

@ -14,6 +14,7 @@ readAsArrayBuffer = (file, callback) ->
@fileUpload = (files) ->
roomId = Session.get('openedRoom')
files = [].concat files
consume = ->
@ -61,6 +62,7 @@ readAsArrayBuffer = (file, callback) ->
name: file.name or file.file.name
size: file.file.size
type: file.file.type
rid: roomId
upload = new UploadFS.Uploader
store: Meteor.fileStore
@ -79,7 +81,7 @@ readAsArrayBuffer = (file, callback) ->
self = this
Meteor.call 'sendMessage', {
_id: Random.id()
rid: Session.get('openedRoom')
rid: roomId
msg: """
File Uploaded: *#{file.name}*
#{file.url}

@ -56,6 +56,7 @@ openRoom = (type, name) ->
RocketChat.TabBar.addButton({ id: 'members-list', title: t('User_Info'), icon: 'icon-user', template: 'membersList', order: 2 })
else
RocketChat.TabBar.addButton({ id: 'members-list', title: t('Members_List'), icon: 'icon-users', template: 'membersList', order: 2 })
RocketChat.TabBar.addButton({ id: 'uploaded-files-list', title: t('Room_uploaded_file_list'), icon: 'icon-download', template: 'uploadedFilesList', order: 3 })
# update user's room subscription
if ChatSubscription.findOne({rid: room._id})?.open is false

@ -18,6 +18,10 @@
*:before,
*:after {
.box-sizing(border-box);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
:focus {
@ -2348,6 +2352,20 @@ a.github-fork {
position: relative;
line-height: 20px;
min-height: 40px;
.body, .user.user-card-message, .time {
-webkit-user-select: all;
-moz-user-select: all;
-ms-user-select: all;
user-select: all;
* {
-webkit-user-select: all;
-moz-user-select: all;
-ms-user-select: all;
user-select: all;
}
}
&:nth-child(1) {
margin-top: 0;
}
@ -3815,6 +3833,20 @@ a.github-fork {
}
}
.message-popup-results {
&.notready {
.message-popup-items {
background-image: url(/images/logo/loading.gif);
background-repeat: no-repeat;
background-position: 50% 50%;
height: 100px;
}
.popup-item {
display: none;
}
}
}
@media all and(max-height: 480px) {
#login-card {
padding: 10px;

@ -19,36 +19,53 @@ Template.accountProfile.onCreated ->
@clearForm = ->
@find('#language').value = localStorage.getItem('userLanguage')
@find('#oldPassword').value = ''
@find('#password').value = ''
@changePassword = (oldPassword, newPassword, callback) ->
if not oldPassword and not newPassword
return callback()
else if !!oldPassword ^ !!newPassword
toastr.warning t('Old_and_new_password_required')
else if newPassword and oldPassword
Accounts.changePassword oldPassword, newPassword, (error) ->
if error
toastr.error t('Incorrect_Password')
else
return callback()
@save = ->
instance = @
data = {}
reload = false
selectedLanguage = $('#language').val()
if localStorage.getItem('userLanguage') isnt selectedLanguage
localStorage.setItem 'userLanguage', selectedLanguage
data.language = selectedLanguage
reload = true
if _.trim $('#password').val()
data.password = _.trim $('#password').val()
if _.trim $('#username').val()
data.username = _.trim $('#username').val()
Meteor.call 'saveUserProfile', data, (error, results) ->
if results
toastr.success t('Profile_saved_successfully')
instance.clearForm()
if reload
setTimeout ->
Meteor._reload.reload()
, 1000
if error
toastr.error error.reason
oldPassword = _.trim($('#oldPassword').val())
newPassword = _.trim($('#password').val())
instance.changePassword oldPassword, newPassword, ->
data = {}
reload = false
selectedLanguage = $('#language').val()
if localStorage.getItem('userLanguage') isnt selectedLanguage
localStorage.setItem 'userLanguage', selectedLanguage
data.language = selectedLanguage
reload = true
if _.trim $('#username').val()
data.username = _.trim $('#username').val()
Meteor.call 'saveUserProfile', data, (error, results) ->
if results
toastr.success t('Profile_saved_successfully')
instance.clearForm()
if reload
setTimeout ->
Meteor._reload.reload()
, 1000
if error
toastr.error error.reason
Template.accountProfile.onRendered ->
Tracker.afterFlush ->

@ -15,6 +15,12 @@
<input type="text" name="username" id="username" placeholder="{{username}}" />
</div>
</div>
<div class="input-line">
<label for="password">{{_ "Old_Password"}}</label>
<div>
<input type="password" name="oldPassword" id="oldPassword" />
</div>
</div>
<div class="input-line">
<label for="password">{{_ "Password"}}</label>
<div>

@ -21,6 +21,11 @@ Template.avatarPrompt.helpers
suggestions: ->
return Template.instance().suggestions.get()
suggestAvatar: (service) ->
suggestions = Template.instance().suggestions.get()
console.log service, "Accounts_OAuth_#{_.capitalize service}", RocketChat.settings.get("Accounts_OAuth_#{_.capitalize service}"), suggestions
return RocketChat.settings.get("Accounts_OAuth_#{_.capitalize service}") and not suggestions.avatars[service]
upload: ->
return Template.instance().upload.get()

@ -64,22 +64,30 @@
{{> avatarSuggestion suggestions.avatars.facebook}}
{{> avatarSuggestion suggestions.avatars.google}}
{{> avatarSuggestion suggestions.avatars.github}}
{{> avatarSuggestion suggestions.avatars.gitlab}}
{{> avatarSuggestion suggestions.avatars.linkedin}}
{{> avatarSuggestion suggestions.avatars.twitter}}
{{#unless suggestions.avatars.facebook}}
{{#if suggestAvatar 'facebook'}}
{{> avatarSuggestionLogin 'facebook'}}
{{/unless}}
{{#unless suggestions.avatars.google}}
{{/if}}
{{#if suggestAvatar 'google'}}
{{> avatarSuggestionLogin 'google'}}
{{/unless}}
{{#unless suggestions.avatars.github}}
{{/if}}
{{#if suggestAvatar 'github'}}
{{> avatarSuggestionLogin 'github'}}
{{/unless}}
{{#unless suggestions.avatars.linkedin}}
{{/if}}
{{#if suggestAvatar 'gitlab'}}
{{> avatarSuggestionLogin 'gitlab'}}
{{/if}}
{{#if suggestAvatar 'linkedin'}}
{{> avatarSuggestionLogin 'linkedin'}}
{{/unless}}
{{/if}}
{{#if suggestAvatar 'twitter'}}
{{> avatarSuggestionLogin 'twitter'}}
{{/if}}
{{else}}
{{_ "Loading_suggestion"}}
{{/if}}

@ -5,10 +5,10 @@
</div>
{{/each}}
{{!-- <div class="tab-button" data-target="messageSearch" title="Search">
<i class="icon-search"></i>
<i class="icon-search" aria-label="{{_ "Search"}}"></i>
</div>
<div class="tab-button" data-target="membersList" title="Member List">
<i class="icon-users"></i>
<i class="icon-users" aria-label="{{_ "Members_List"}}"></i>
</div> --}}
{{!-- <div class="tab-button" data-target="pinned-messages" title="Pinned Messages">
<i class="icon-pin"></i>

@ -196,7 +196,7 @@ Template.messagePopup.events
Template.messagePopup.helpers
isOpen: ->
Template.instance().open.get() and Template.instance().hasData.get()
Template.instance().open.get() and (Template.instance().hasData.get() or not Template.instance().parentTemplate(1).subscriptionsReady())
data: ->
template = Template.instance()

@ -5,7 +5,7 @@
<div class="message-popup-title">
{{title}}
</div>
<div>
<div class="message-popup-items">
{{#each data}}
<div class="popup-item" data-id="{{_id}}">
{{> Template.dynamic template=../template}}
@ -15,4 +15,4 @@
</div>
</div>
{{/if}}
</template>
</template>

@ -1,31 +1,35 @@
@filteredUsers = new Mongo.Collection 'filtered-users'
@channelAutocomplete = new Mongo.Collection 'channel-autocomplete'
Template.messagePopupConfig.helpers
popupUserConfig: ->
self = this
template = Template.instance()
config =
title: 'People'
title: t('People')
collection: filteredUsers
template: 'messagePopupUser'
getInput: self.getInput
textFilterDelay: 200
getFilter: (collection, filter) ->
exp = new RegExp("^#{filter}", 'i')
Meteor.subscribe 'filteredUsers', filter
items = filteredUsers.find({$or: [{username: exp}, {name: exp}]}, {limit: 5}).fetch()
all =
_id: '@all'
username: '@all'
system: true
name: t 'Notify_all_in_this_room'
compatibility: 'channel group'
exp = new RegExp("(^|\\s)#{filter}", 'i')
if exp.test(all.username) or exp.test(all.compatibility)
items.unshift all
return items
exp = new RegExp("#{filter}", 'i')
template.userFilter.set filter
if template.userSubscription.ready()
items = filteredUsers.find({$or: [{username: exp}, {name: exp}]}, {limit: 5}).fetch()
all =
_id: '@all'
username: 'all'
system: true
name: t 'Notify_all_in_this_room'
compatibility: 'channel group'
exp = new RegExp("(^|\\s)#{filter}", 'i')
if exp.test(all.username) or exp.test(all.compatibility)
items.unshift all
return items
else
return []
getValue: (_id, collection, firstPartValue) ->
if _id is '@all'
@ -47,13 +51,20 @@ Template.messagePopupConfig.helpers
self = this
template = Template.instance()
config =
title: 'Channels'
collection: ChatSubscription
title: t('Channels')
collection: channelAutocomplete
trigger: '#'
template: 'messagePopupChannel'
getInput: self.getInput
textFilterDelay: 200
getFilter: (collection, filter) ->
return collection.find({t: 'c', name: new RegExp(filter, 'i')}, {limit: 10})
exp = new RegExp(filter, 'i')
template.channelFilter.set filter
if template.channelSubscription.ready()
return collection.find( { name: exp }, { limit: 5 }).fetch()
else
return []
getValue: (_id, collection) ->
return collection.findOne(_id)?.name
@ -64,7 +75,7 @@ Template.messagePopupConfig.helpers
template = Template.instance()
config =
title: 'Commands'
title: t('Commands')
collection: RocketChat.slashCommands.commands
trigger: '/'
triggerAnywhere: false
@ -97,7 +108,7 @@ Template.messagePopupConfig.helpers
self = this
template = Template.instance()
config =
title: 'Emoji'
title: t('Emoji')
collection: RocketChat.emoji.list
template: 'messagePopupEmoji'
trigger: ':'
@ -121,3 +132,18 @@ Template.messagePopupConfig.helpers
return results
return config
subscriptionNotReady: ->
return 'notready' unless Template.instance().subscriptionsReady()
Template.messagePopupConfig.onCreated ->
@userFilter = new ReactiveVar ''
@channelFilter = new ReactiveVar ''
template = @
@autorun ->
template.userSubscription = template.subscribe 'filteredUsers', template.userFilter.get()
@autorun ->
template.channelSubscription = template.subscribe 'channelAutocomplete', template.channelFilter.get()

@ -1,6 +1,10 @@
<template name="messagePopupConfig">
{{#if emojiEnabled}}{{> messagePopup popupEmojiConfig}}{{/if}}
{{> messagePopup popupChannelConfig}}
{{> messagePopup popupUserConfig}}
{{> messagePopup popupSlashCommandsConfig}}
</template>
<div class="message-popup-results {{subscriptionNotReady}}">
{{#if emojiEnabled}}
{{> messagePopup popupEmojiConfig}}
{{/if}}
{{> messagePopup popupChannelConfig}}
{{> messagePopup popupUserConfig}}
{{> messagePopup popupSlashCommandsConfig}}
</div>
</template>

@ -7,6 +7,12 @@ favoritesEnabled = ->
# @TODO bug com o botão para "rolar até o fim" (novas mensagens) quando uma mensagem com texto que gere rolagem horizontal
Template.room.helpers
showFormattingTips: ->
return RocketChat.Markdown or RocketChat.Highlight
showMarkdown: ->
return RocketChat.Markdown
showHighlight: ->
return RocketChat.Highlight
favorite: ->
sub = ChatSubscription.findOne { rid: this._id }, { fields: { f: 1 } }
return 'icon-star favorite-room' if sub?.f? and sub.f and favoritesEnabled

@ -16,7 +16,7 @@
<span class="room-title {{editingTitle}}">{{roomName}}</span>
{{#if canEditName}}
<input type="text" id="room-title-field" class="{{showEditingTitle}}" value="{{roomNameEdit}}" dir="auto">
<a href="#edit" class="edit-room-title"><i class="icon-pencil"></i></a>
<a href="#edit" class="edit-room-title"><i class="icon-pencil" aria-label="{{_ "Edit"}}"></i></a>
{{/if}}
</h2>
</header>
@ -51,7 +51,7 @@
<div class="messages-box">
<div class="ticks-bar"></div>
<div class="wrapper">
<ul>
<ul aria-live="polite">
{{#if hasMore}}
<li class="load-more">
{{#if isLoading}}
@ -86,14 +86,14 @@
<div class="input-message-container">
{{> messagePopupConfig getPupupConfig}}
<textarea dir="auto" name="msg" maxlength="{{maxMessageLength}}" class="input-message autogrow-short" placeholder="{{_ 'Message'}}"></textarea>
<i class="icon-paper-plane" title="{{_ "Send_Message"}}"></i>
<i class="icon-paper-plane" title="{{_ "Send_Message"}}" aria-label="{{_ "Send_Message"}}"></i>
</div>
{{#if canRecordAudio}}
<div class="mic">
<i class="icon-mic"></i>
<i class="icon-mic" aria-label="{{_ "Record"}}"></i>
</div>
<div class="stop-mic hidden">
<i class="icon-stop"></i>
<i class="icon-stop" aria-label="{{_ "Stop_Recording"}}"></i>
</div>
{{/if}}
</div>
@ -115,14 +115,22 @@
{{/if}}
{{/with}}
</div>
{{#if showFormattingTips}}
<div class="formatting-tips" aria-hidden="true" dir="auto">
{{#if showMarkdown}}
<b>*{{_ "bold"}}*</b>
<i>_{{_ "italics"}}_</i>
<span>~<strike>{{_ "strike"}}</strike>~</span>
<code class="inline">`{{_ "inline_code"}}`</code>
{{/if}}
{{#if showHighlight}}
<code class="inline"><span class="hidden-br"><br></span>```<span class="hidden-br"><br></span><i class="icon-level-down"></i>{{_ "multi"}}<span class="hidden-br"><br></span><i class="icon-level-down"></i>{{_ "line"}}<span class="hidden-br"><br></span><i class="icon-level-down"></i>```</code>
{{/if}}
{{#if showMarkdown}}
<q><span class="hidden-br"><br></span>&gt;{{_ "quote"}}</q>
{{/if}}
</div>
{{/if}}
<div class="editing-commands" aria-hidden="true" dir="auto">
<div class="editing-commands-cancel">{{_ 'Esc_to'}} <a href="">{{_ 'Cancel'}}</a></div>
<div class="editing-commands-save">{{_ 'Enter_to'}} <a href="">{{_ 'Save_changes'}}</a></div>

@ -0,0 +1,33 @@
roomFiles = new Mongo.Collection 'room_files'
Template.uploadedFilesList.helpers
files: ->
return roomFiles.find().fetch()
hasFiles: ->
return roomFiles.find().count() > 0
getFileIcon: (type) ->
if type.match(/^image\/.+$/)
return 'icon-picture'
return 'icon-docs'
customClassForFileType: ->
if this.type.match(/^image\/.+$/)
return 'room-files-swipebox'
Template.uploadedFilesList.events
'click .room-file-item': (e, t) ->
if $(e.currentTarget).siblings('.icon-picture').length
e.preventDefault()
Template.uploadedFilesList.onCreated ->
instance = this
this.autorun ->
instance.subscribe 'roomFiles', Session.get('openedRoom')
Template.uploadedFilesList.onRendered ->
$('.room-files-swipebox').swipebox()

@ -0,0 +1,25 @@
<template name="uploadedFilesList">
<div class="content">
<div class="list-view">
<div class="status">
<h2>{{_ "Room_uploaded_file_list"}}</h2>
</div>
{{#if Template.subscriptionsReady}}
{{#if hasFiles }}
<ul class='list clearfix lines'>
{{#each files}}
<li>
<i class="{{getFileIcon type}}"></i>
<a title="{{name}}" href="{{url}}" target="_blank" class="room-file-item {{customClassForFileType}}">{{name}}</a>
</li>
{{/each}}
</ul>
{{else}}
<h2>{{_ "Room_uploaded_file_list_empty"}}</h2>
{{/if}}
{{else}}
<span>{{_ "Loading..."}}</span>
{{/if}}
</div>
</div>
</template>

@ -71,6 +71,7 @@
"Chat_Rooms" : "Chat Rooms",
"close" : "close",
"coming_soon" : "coming soon",
"Commands" : "Commands",
"Confirm_password" : "Confirm your password",
"Contact" : "Contact",
"Conversation" : "Conversation",
@ -100,6 +101,7 @@
"Email_already_exists" : "Email already exists",
"Email_or_username" : "Email or username",
"Email_verified" : "Email verified",
"Emoji" : "Emoji",
"Enter_info" : "Enter your information",
"Enter_to" : "Enter to",
"Error_changing_password" : "Error changing password",
@ -119,6 +121,7 @@
"Hide_room" : "Hide room",
"History" : "History",
"hours" : "hours",
"Incorrect_Password" : "Incorrect Password",
"inline_code" : "inline_code",
"Invalid_confirm_pass" : "The password confirmation does not match password",
"Invalid_email" : "The e-mail entered is invalid",
@ -213,6 +216,8 @@
"Not_found_or_not_allowed" : "Not Found or Not Allowed",
"Nothing_found" : "Nothing found",
"Notify_all_in_this_room" : "Notify all in this room",
"Old_Password" : "Old Password",
"Old_and_new_password_required" : "You need to provide both old and new password for changing your password.",
"Only_you_can_see_this_message" : "Only you can see this message",
"Online" : "Online",
"Oops!" : "Oops",
@ -221,6 +226,7 @@
"others" : "others",
"Password" : "Password",
"Password_changed_successfully" : "Password changed successfully",
"People" : "People",
"Please_wait" : "Please wait",
"Please_wait_activation" : "Please wait, this can take some time.",
"Please_wait_statistics" : "Please wait, statistics are being generated.",
@ -246,6 +252,7 @@
"Quick_Search" : "Quick Search",
"quote" : "quote",
"Recents" : "Recents",
"Record" : "Record",
"Register" : "Register a new account",
"Registration_Succeeded" : "Registration Succeeded",
"Remember_me" : "Remember me",
@ -257,6 +264,8 @@
"Room_name_changed" : "Room name changed to: <em>__room_name__</em> by <em>__user_by__</em>",
"Room_name_changed_successfully" : "Room name changed successfully",
"Room_not_found" : "Room not found",
"Room_uploaded_file_list" : "Files list",
"Room_uploaded_file_list_empty" : "No files available.",
"room_user_count" : "%s users",
"Rooms" : "Rooms",
"Save" : "Save",
@ -319,6 +328,7 @@
"Stats_Total_Private_Groups" : "Total Private Groups",
"Stats_Total_Rooms" : "Total Rooms",
"Stats_Total_Users" : "Total Users",
"Stop_Recording" : "Stop Recording",
"strike" : "strike",
"Submit" : "Submit",
"S_new_messages_since_s" : "%s new messages since %s",

@ -251,6 +251,8 @@
"Room_name_changed" : "Nome da sala alterado para: <em>__room_name__</em> por <em>__user_by__</em>",
"Room_name_changed_successfully" : "Nome da sala alterado com sucesso",
"Room_not_found" : "Sala não encontrada",
"Room_uploaded_file_list" : "Lista de arquivos",
"Room_uploaded_file_list_empty" : "Nenhum arquivo disponível",
"room_user_count" : "%s usuários",
"Rooms" : "Salas",
"Save" : "Salvar",

@ -52,12 +52,12 @@ App.setPreference('StatusBarOverlaysWebView', false);
App.setPreference('StatusBarStyle', 'lightcontent');
App.setPreference('StatusBarBackgroundColor', '#000000');
App.setPreference('ShowSplashScreenSpinner', false);
App.setPreference('android-targetSdkVersion', '19');
App.setPreference('android-targetSdkVersion', '22');
App.setPreference('android-minSdkVersion', '19');
App.accessRule('*');
// Pass preferences for a particular PhoneGap/Cordova plugin
App.configurePlugin('com.phonegap.plugins.facebookconnect', {
App.configurePlugin('phonegap-facebook-plugin', {
APP_NAME: 'Rocket.Chat',
APP_ID: '835103589938459'
});

@ -7,19 +7,20 @@ class AutoLinker
constructor: (message) ->
if _.trim message.html
# Separate text in code blocks and non code blocks
msgParts = message.html.split(/(```\w*[\n\ ]?[\s\S]*?```+?)/)
msgParts = message.html.split /(```\w*[\n ]?[\s\S]*?```+?)|(`(?:[^`]+)`)/
for part, index in msgParts
# Verify if this part is code
codeMatch = part.match(/```(\w*)[\n\ ]?([\s\S]*?)```+?/)
if not codeMatch?
msgParts[index] = Autolinker.link part,
stripPrefix: false
twitter: false
replaceFn: (autolinker, match) ->
if match.getType() is 'url'
return /(:\/\/|www\.).+/.test match.matchedText
return true
if part?.length? > 0
# Verify if this part is code
codeMatch = part.match /(?:```(\w*)[\n ]?([\s\S]*?)```+?)|(?:`(?:[^`]+)`)/
if not codeMatch?
msgParts[index] = Autolinker.link part,
stripPrefix: false
twitter: false
replaceFn: (autolinker, match) ->
if match.getType() is 'url'
return /(:\/\/|www\.).+/.test match.matchedText
return true
# Re-mount message
message.html = msgParts.join('')

@ -14,4 +14,4 @@ class ColorsClient
message.html = msg
return message
RocketChat.callbacks.add 'renderMessage', ColorsClient, RocketChat.callbacks.priority.HIGH
RocketChat.callbacks.add 'renderMessage', ColorsClient, RocketChat.callbacks.priority.MEDIUM

@ -50,3 +50,4 @@ class Highlight
return message
RocketChat.callbacks.add 'renderMessage', Highlight, RocketChat.callbacks.priority.HIGH
RocketChat.Highlight = true

@ -38,6 +38,7 @@ RocketChat.TabBar = new class
if status is -1 or (status isnt 1 and open.get())
open.set false
else
$('.flex-tab .content').scrollTop(0)
# added a delay to make sure the template is already rendered before animating it
setTimeout ->
open.set true

@ -15,7 +15,7 @@ RocketChat.models.Users = new class extends RocketChat.models._Base
findOneByEmailAddress: (emailAddress, options) ->
query =
'email.address': emailAddress
'emails.address': emailAddress
return @findOne query, options

@ -12,25 +12,25 @@ RocketChat.settings.add 'Accounts_AvatarStorePath', '/var/www/rocket.chat/upload
RocketChat.settings.add 'Accounts_AvatarResize', false, { type: 'boolean', group: 'Accounts', section: 'Avatar' }
RocketChat.settings.add 'Accounts_AvatarSize', 200, { type: 'int', group: 'Accounts', section: 'Avatar' }
RocketChat.settings.add 'Accounts_OAuth_Facebook', false, { type: 'boolean', group: 'Accounts', section: 'Facebook' }
RocketChat.settings.add 'Accounts_OAuth_Facebook', false, { type: 'boolean', group: 'Accounts', section: 'Facebook', public: true }
RocketChat.settings.add 'Accounts_OAuth_Facebook_id', '', { type: 'string', group: 'Accounts', section: 'Facebook' }
RocketChat.settings.add 'Accounts_OAuth_Facebook_secret', '', { type: 'string', group: 'Accounts', section: 'Facebook' }
RocketChat.settings.add 'Accounts_OAuth_Google', false, { type: 'boolean', group: 'Accounts', section: 'Google' }
RocketChat.settings.add 'Accounts_OAuth_Google', false, { type: 'boolean', group: 'Accounts', section: 'Google', public: true }
RocketChat.settings.add 'Accounts_OAuth_Google_id', '', { type: 'string', group: 'Accounts', section: 'Google' }
RocketChat.settings.add 'Accounts_OAuth_Google_secret', '', { type: 'string', group: 'Accounts', section: 'Google' }
RocketChat.settings.add 'Accounts_OAuth_Github', false, { type: 'boolean', group: 'Accounts', section: 'Github' }
RocketChat.settings.add 'Accounts_OAuth_Github', false, { type: 'boolean', group: 'Accounts', section: 'Github', public: true }
RocketChat.settings.add 'Accounts_OAuth_Github_id', '', { type: 'string', group: 'Accounts', section: 'Github' }
RocketChat.settings.add 'Accounts_OAuth_Github_secret', '', { type: 'string', group: 'Accounts', section: 'Github' }
RocketChat.settings.add 'Accounts_OAuth_Gitlab', false, { type: 'boolean', group: 'Accounts', section: 'Gitlab' }
RocketChat.settings.add 'Accounts_OAuth_Gitlab', false, { type: 'boolean', group: 'Accounts', section: 'Gitlab', public: true }
RocketChat.settings.add 'Accounts_OAuth_Gitlab_id', '', { type: 'string', group: 'Accounts', section: 'Gitlab' }
RocketChat.settings.add 'Accounts_OAuth_Gitlab_secret', '', { type: 'string', group: 'Accounts', section: 'Gitlab' }
RocketChat.settings.add 'Accounts_OAuth_Linkedin', false, { type: 'boolean', group: 'Accounts', section: 'Linkedin' }
RocketChat.settings.add 'Accounts_OAuth_Linkedin', false, { type: 'boolean', group: 'Accounts', section: 'Linkedin', public: true }
RocketChat.settings.add 'Accounts_OAuth_Linkedin_id', '', { type: 'string', group: 'Accounts', section: 'Linkedin' }
RocketChat.settings.add 'Accounts_OAuth_Linkedin_secret', '', { type: 'string', group: 'Accounts', section: 'Linkedin' }
RocketChat.settings.add 'Accounts_OAuth_Meteor', false, { type: 'boolean', group: 'Accounts', section: 'Meteor' }
RocketChat.settings.add 'Accounts_OAuth_Meteor', false, { type: 'boolean', group: 'Accounts', section: 'Meteor', public: true }
RocketChat.settings.add 'Accounts_OAuth_Meteor_id', '', { type: 'string', group: 'Accounts', section: 'Meteor' }
RocketChat.settings.add 'Accounts_OAuth_Meteor_secret', '', { type: 'string', group: 'Accounts', section: 'Meteor' }
RocketChat.settings.add 'Accounts_OAuth_Twitter', false, { type: 'boolean', group: 'Accounts', section: 'Twitter' }
RocketChat.settings.add 'Accounts_OAuth_Twitter', false, { type: 'boolean', group: 'Accounts', section: 'Twitter', public: true }
RocketChat.settings.add 'Accounts_OAuth_Twitter_id', '', { type: 'string', group: 'Accounts', section: 'Twitter' }
RocketChat.settings.add 'Accounts_OAuth_Twitter_secret', '', { type: 'string', group: 'Accounts', section: 'Twitter' }
@ -98,4 +98,4 @@ Meteor.startup ->
if process?.env? and not process.env['MAIL_URL']? and RocketChat.settings.get('SMTP_Host') and RocketChat.settings.get('SMTP_Username') and RocketChat.settings.get('SMTP_Password')
process.env['MAIL_URL'] = "smtp://" + encodeURIComponent(RocketChat.settings.get('SMTP_Username')) + ':' + encodeURIComponent(RocketChat.settings.get('SMTP_Password')) + '@' + encodeURIComponent(RocketChat.settings.get('SMTP_Host'))
if RocketChat.settings.get('SMTP_Port')
process.env['MAIL_URL'] += ':' + parseInt(RocketChat.settings.get('SMTP_Port'))
process.env['MAIL_URL'] += ':' + parseInt(RocketChat.settings.get('SMTP_Port'))

@ -25,3 +25,4 @@ class Markdown
return message
RocketChat.callbacks.add 'renderMessage', Markdown, RocketChat.callbacks.priority.LOW
RocketChat.Markdown = true

@ -0,0 +1,4 @@
{
"User_doesnt_exist" : "No user exists by the name of `@%s`.",
"Username_is_already_in_here": "`@%s` is already in here."
}

@ -0,0 +1,4 @@
{
"User_doesnt_exist" : "O usuário `@%s` não existe.",
"Username_is_already_in_here": "`@%s` já está aqui."
}

@ -20,12 +20,26 @@ else
username = username.replace('@', '')
user = Meteor.users.findOne({ username: username })
currentUser = Meteor.users.findOne Meteor.userId()
if not user?
console.log 'notify user_doesnt_exist'
RocketChat.Notifications.notifyUser Meteor.userId(), 'message', {
_id: Random.id()
rid: item.rid
ts: new Date
msg: TAPi18n.__('User_doesnt_exist', { postProcess: 'sprintf', sprintf: [ username ] }, currentUser.language)
}
return
# cancel if the user is already in this room
if RocketChat.models.Rooms.findOneByIdContainigUsername(item.rid, user.username)?
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
Meteor.runAsUser user._id, ->

@ -15,6 +15,19 @@ Package.onUse(function(api) {
]);
api.addFiles('invite.coffee');
// TAPi18n
api.use('templating', 'client');
var _ = Npm.require('underscore');
var fs = Npm.require('fs');
tapi18nFiles = _.compact(_.map(fs.readdirSync('packages/rocketchat-slashcommands-invite/i18n'), function(filename) {
if (fs.statSync('packages/rocketchat-slashcommands-invite/i18n/' + filename).size > 16) {
return 'i18n/' + filename;
}
}));
api.use('tap:i18n@1.6.1', ['client', 'server']);
api.imply('tap:i18n');
api.addFiles(tapi18nFiles, ['client', 'server']);
});
Package.onTest(function(api) {

@ -0,0 +1,3 @@
{
"Channel_doesnt_exist" : "The channel `#%s` does not exist."
}

@ -0,0 +1,3 @@
{
"Channel_doesnt_exist" : "O canal `#%s` não existe."
}

@ -9,7 +9,7 @@ if Meteor.isClient
params: '#channel'
else
class Join
constructor: (command, params) ->
constructor: (command, params, item) ->
if command isnt 'join' or not Match.test params, String
return
@ -23,6 +23,12 @@ else
room = RocketChat.models.Rooms.findOneByNameAndTypeNotContainigUsername(channel, 'c', user.username)
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);
}
return
Meteor.call 'joinRoom', room._id

@ -6,15 +6,31 @@ Package.describe({
});
Package.onUse(function(api) {
api.versionsFrom('1.0');
var client = 'client';
var both = ['client', 'server'];
api.versionsFrom('1.0');
api.use([
'coffeescript',
'check',
'rocketchat:lib@0.0.1'
]);
], both);
api.addFiles('join.coffee', both);
api.addFiles('join.coffee');
// TAPi18n
api.use('templating', client);
var _ = Npm.require('underscore');
var fs = Npm.require('fs');
tapi18nFiles = _.compact(_.map(fs.readdirSync('packages/rocketchat-slashcommands-join/i18n'), function(filename) {
if (fs.statSync('packages/rocketchat-slashcommands-join/i18n/' + filename).size > 16) {
return 'i18n/' + filename;
}
}));
api.use('tap:i18n@1.6.1', both);
api.imply('tap:i18n');
api.addFiles(tapi18nFiles, both);
});
Package.onTest(function(api) {

@ -7,13 +7,14 @@ class Spotify
process = (message, source, callback) ->
if _.trim source
# Separate text in code blocks and non code blocks
msgParts = source.split(/(```\w*[\n\ ]?[\s\S]*?```+?)/)
msgParts = source.split /(```\w*[\n ]?[\s\S]*?```+?)|(`(?:[^`]+)`)/
for part, index in msgParts
# Verify if this part is code
codeMatch = part.match(/```(\w*)[\n\ ]?([\s\S]*?)```+?/)
if not codeMatch?
callback message, msgParts, index, part
if part?.length? > 0
codeMatch = part.match /(?:```(\w*)[\n ]?([\s\S]*?)```+?)|(?:`(?:[^`]+)`)/
if not codeMatch?
callback message, msgParts, index, part
@transform: (message) ->
urls = []
@ -47,8 +48,7 @@ class Spotify
if item.source
quotedSource = item.source.replace /[\\^$.*+?()[\]{}|]/g, '\\$&'
re = new RegExp '(^|\\s)' + quotedSource + '(\\s|$)', 'g'
part = part.replace re, '$1<a href="' + item.url + '" target="_blank">' + item.source + '</a>$2'
msgParts[index] = part
msgParts[index] = part.replace re, '$1<a href="' + item.url + '" target="_blank">' + item.source + '</a>$2'
message.html = msgParts.join ''
return message

@ -1,26 +1,36 @@
@getAvatarSuggestionForUser = (user) ->
avatars = []
if user.services.facebook?.id?
if user.services.facebook?.id? and RocketChat.settings.get 'Accounts_OAuth_Facebook'
avatars.push
service: 'facebook'
url: "https://graph.facebook.com/#{user.services.facebook.id}/picture?type=large"
if user.services.google?.picture? and user.services.google.picture isnt "https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg"
if user.services.google?.picture? and user.services.google.picture isnt "https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg" and RocketChat.settings.get 'Accounts_OAuth_Google'
avatars.push
service: 'google'
url: user.services.google.picture
if user.services.github?.username?
if user.services.github?.username? and RocketChat.settings.get 'Accounts_OAuth_Github'
avatars.push
service: 'github'
url: "https://avatars.githubusercontent.com/#{user.services.github.username}?s=200"
if user.services.linkedin?.pictureUrl?
if user.services.linkedin?.pictureUrl? and RocketChat.settings.get 'Accounts_OAuth_Linkedin'
avatars.push
service: 'linkedin'
url: user.services.linkedin.pictureUrl
if user.services.twitter?.profile_image_url_https? and RocketChat.settings.get 'Accounts_OAuth_Twitter'
avatars.push
service: 'twitter'
url: user.services.twitter.profile_image_url_https
if user.services.gitlab?.avatar_url? and RocketChat.settings.get 'Accounts_OAuth_Gitlab'
avatars.push
service: 'gitlab'
url: user.services.gitlab.avatar_url
if user.emails?.length > 0
for email in user.emails when email.verified is true
avatars.push
@ -37,7 +47,7 @@
try
result = HTTP.get avatar.url, npmRequestOptions: {encoding: 'binary'}
if result.statusCode is 200
blob = "data:#{result.headers['content-type']};base64,"
blob = "data:#{result.headers['content-type']};base64,"
blob += Buffer(result.content, 'binary').toString('base64')
avatar.blob = blob
avatar.contentType = result.headers['content-type']

@ -0,0 +1,7 @@
Meteor.methods
getTotalChannels: ->
if not Meteor.userId()
throw new Meteor.Error 'invalid-user', '[methods] getTotalChannels -> Invalid user'
console.log '[methods] getTotalChannels -> '.green, 'userId:', Meteor.userId()
return RocketChat.models.Rooms.find({ t: 'c' }).count()

@ -4,8 +4,8 @@ Meteor.methods
if settings.language?
RocketChat.models.Users.setLanguage Meteor.userId(), settings.language
if settings.password?
Accounts.setPassword Meteor.userId(), settings.password, { logout: false }
# if settings.password?
# Accounts.setPassword Meteor.userId(), settings.password, { logout: false }
if settings.username?
Meteor.call 'setUsername', settings.username

@ -1,5 +0,0 @@
Meteor.methods
setPassword: (password) ->
if Meteor.userId()
Accounts.setPassword Meteor.userId(), password, { logout: false }
return true

@ -4,6 +4,9 @@ Meteor.methods
throw new Meteor.Error('invalid-user', "[methods] updateMessage -> Invalid user")
originalMessage = RocketChat.models.Messages.findOneById message._id
if not originalMessage?._id?
return
hasPermission = RocketChat.authz.hasPermission(Meteor.userId(), 'edit-message', message.rid)
editAllowed = RocketChat.settings.get 'Message_AllowEditing'
@ -42,4 +45,3 @@ Meteor.methods
# Meteor.defer ->
# RocketChat.callbacks.run 'afterSaveMessage', RocketChat.models.Messages.findOneById(message.id)

@ -0,0 +1,28 @@
Meteor.publish 'channelAutocomplete', (name) ->
unless this.userId
return this.ready()
console.log '[publish] channelAutocomplete -> '.green, name
pub = this
options =
fields:
_id: 1
name: 1
limit: 5
cursorHandle = RocketChat.models.Rooms.findByNameContainingAndTypes(name, ['c'], options).observeChanges
added: (_id, record) ->
pub.added('channel-autocomplete', _id, record)
changed: (_id, record) ->
pub.changed('channel-autocomplete', _id, record)
removed: (_id, record) ->
pub.removed('channel-autocomplete', _id, record)
@ready()
@onStop ->
cursorHandle.stop()
return

@ -2,8 +2,6 @@ Meteor.publish 'fullUserData', (filter, limit) ->
unless @userId
return @ready()
user = RocketChat.models.Users.findOneById @userId
fields =
name: 1
username: 1

@ -0,0 +1,27 @@
Meteor.publish 'roomFiles', (rid) ->
unless this.userId
return this.ready()
console.log '[publish] roomFiles'.green, rid
pub = this
fileQuery =
rid: rid
complete: true
uploading: false
fileOptions =
fields:
_id: 1
name: 1
type: 1
url: 1
cursorFileListHandle = fileCollection.find(fileQuery, fileOptions).observeChanges
added: (_id, record) ->
pub.added('room_files', _id, record)
this.ready()
this.onStop ->
cursorFileListHandle.stop()

@ -0,0 +1,27 @@
Meteor.startup ->
Migrations.add
version: 20
up: ->
###
# Migrate existing `rocketchat_uploads` documents to include the room Id
# where the file was uploaded to. The room Id is retrieved from the message
# document created after the file upload.
###
# list of channel messages which were created after uploading a file
msgQuery =
rid: { $exists: true }
'file._id': { $exists: true }
msgOptions =
fields:
_id: 1
rid: 1
'file._id': 1
cursorFileMessages = RocketChat.models.Messages.find(msgQuery, msgOptions);
return unless cursorFileMessages.count()
_.each( cursorFileMessages.fetch(), (msg) ->
fileCollection.update({ _id: msg?.file?._id }, { $set: { rid: msg.rid } }, { $multi: true })
)
console.log 'Updated rocketchat_uploads documents to include the room Id in which they were sent.'
Loading…
Cancel
Save