Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into develop

pull/1738/merge
Diego Sampaio 10 years ago
commit 7d9561eec0
  1. 1
      .gitignore
  2. 1
      .meteor/packages
  3. 5
      .meteor/versions
  4. 2
      .sandstorm/sandstorm-pkgdef.capnp
  5. 0
      example-build.sh
  6. 11
      i18n/en.i18n.json
  7. 3
      packages/rocketchat-authorization/server/startup.coffee
  8. 11
      packages/rocketchat-channel-settings-mail-messages/client/lib/startup.coffee
  9. 24
      packages/rocketchat-channel-settings-mail-messages/client/stylesheets/mail-messages.less
  10. 10
      packages/rocketchat-channel-settings-mail-messages/client/views/channelSettingsMailMessages.coffee
  11. 8
      packages/rocketchat-channel-settings-mail-messages/client/views/channelSettingsMailMessages.html
  12. 79
      packages/rocketchat-channel-settings-mail-messages/client/views/mailMessagesInstructions.coffee
  13. 44
      packages/rocketchat-channel-settings-mail-messages/client/views/mailMessagesInstructions.html
  14. 17
      packages/rocketchat-channel-settings-mail-messages/i18n/en.i18n.json
  15. 51
      packages/rocketchat-channel-settings-mail-messages/package.js
  16. 3
      packages/rocketchat-channel-settings-mail-messages/server/lib/startup.coffee
  17. 46
      packages/rocketchat-channel-settings-mail-messages/server/methods/mailMessages.coffee
  18. 29
      packages/rocketchat-channel-settings/client/lib/ChannelSettings.coffee
  19. 30
      packages/rocketchat-channel-settings/client/stylesheets/channel-settings.less
  20. 37
      packages/rocketchat-channel-settings/client/views/channelSettings.coffee
  21. 95
      packages/rocketchat-channel-settings/client/views/channelSettings.html
  22. 1
      packages/rocketchat-channel-settings/i18n/en.i18n.json
  23. 3
      packages/rocketchat-channel-settings/package.js
  24. 2
      packages/rocketchat-lib/client/lib/roomExit.coffee
  25. 26
      packages/rocketchat-lib/lib/Message.coffee
  26. 0
      packages/rocketchat-lib/lib/MessageTypes.coffee
  27. 3
      packages/rocketchat-lib/package.js
  28. 2
      packages/rocketchat-lib/rocketchat.info
  29. 4
      packages/rocketchat-lib/server/methods/updateUser.coffee
  30. 9
      packages/rocketchat-lib/server/models/Messages.coffee
  31. 11
      packages/rocketchat-lib/server/models/Rooms.coffee
  32. 2
      packages/rocketchat-lib/server/models/Subscriptions.coffee
  33. 1
      packages/rocketchat-livechat/client/collections/AgentUsers.js
  34. 0
      packages/rocketchat-livechat/client/collections/LivechatDepartment.js
  35. 1
      packages/rocketchat-livechat/client/collections/LivechatDepartmentAgents.js
  36. 0
      packages/rocketchat-livechat/client/collections/LivechatTrigger.js
  37. 26
      packages/rocketchat-livechat/client/stylesheets/livechat.less
  38. 69
      packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.html
  39. 89
      packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.js
  40. 5
      packages/rocketchat-livechat/client/views/app/livechatDepartments.js
  41. 2
      packages/rocketchat-livechat/client/views/app/livechatUsers.js
  42. 2
      packages/rocketchat-livechat/client/views/sideNav/livechatFlex.html
  43. 4
      packages/rocketchat-livechat/client/views/sideNav/livechatFlex.js
  44. 6
      packages/rocketchat-livechat/i18n/en.i18n.json
  45. 13
      packages/rocketchat-livechat/package.js
  46. 21
      packages/rocketchat-livechat/server/lib/getNextAgent.js
  47. 4
      packages/rocketchat-livechat/server/methods/saveDepartment.js
  48. 39
      packages/rocketchat-livechat/server/models/LivechatDepartment.js
  49. 71
      packages/rocketchat-livechat/server/models/LivechatDepartmentAgents.js
  50. 11
      packages/rocketchat-livechat/server/publications/departmentAgents.js
  51. 6
      packages/rocketchat-theme/assets/stylesheets/base.less
  52. 3
      packages/rocketchat-ui-admin/admin/users/adminUserEdit.coffee
  53. 8
      packages/rocketchat-ui-admin/admin/users/adminUserEdit.html
  54. 3
      packages/rocketchat-ui-sidenav/side-nav/createChannelFlex.coffee
  55. 6
      packages/rocketchat-ui-sidenav/side-nav/createChannelFlex.html
  56. 2
      packages/rocketchat-ui-sidenav/side-nav/listPrivateGroupsFlex.coffee
  57. 3
      packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.coffee
  58. 6
      packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.html
  59. 9
      packages/rocketchat-ui/views/app/privateHistory.coffee
  60. 23
      packages/rocketchat-ui/views/app/privateHistory.html
  61. 70
      packages/rocketchat-ui/views/app/room.coffee
  62. 2
      packages/rocketchat-ui/views/app/room.html
  63. 2
      server/methods/channelsList.coffee
  64. 5
      server/methods/createChannel.coffee
  65. 5
      server/methods/createPrivateGroup.coffee
  66. 4
      server/methods/unarchiveRoom.coffee
  67. 1
      server/publications/subscription.coffee
  68. 2
      server/startup/roomPublishes.coffee

1
.gitignore vendored

@ -66,3 +66,4 @@ tramp
ecosystem.json
pm2.json
settings.json
build.sh

@ -41,6 +41,7 @@ rocketchat:lib
rocketchat:authorization
rocketchat:autolinker
rocketchat:channel-settings
rocketchat:channel-settings-mail-messages
rocketchat:colors
rocketchat:custom-oauth
rocketchat:emojione

@ -7,7 +7,7 @@ accounts-oauth@1.1.8
accounts-password@1.1.4
accounts-twitter@1.0.6
alanning:roles@1.2.14
aldeed:simple-schema@1.5.1
aldeed:simple-schema@1.5.2
arunoda:streams@0.1.17
autoupdate@1.2.4
babel-compiler@5.8.24_1
@ -25,7 +25,7 @@ cfs:http-methods@0.0.30
check@1.1.0
chrismbeckett:toastr@2.1.2_1
coffeescript@1.0.11
cosmos:browserify@0.9.2
cosmos:browserify@0.9.3
dandv:caret-position@2.1.1
ddp@1.2.2
ddp-client@1.2.1
@ -125,6 +125,7 @@ rocketchat:assets@0.0.1
rocketchat:authorization@0.0.1
rocketchat:autolinker@0.0.1
rocketchat:channel-settings@0.0.1
rocketchat:channel-settings-mail-messages@0.0.1
rocketchat:colors@0.0.1
rocketchat:cors@0.0.1
rocketchat:custom-oauth@1.0.0

@ -21,7 +21,7 @@ const pkgdef :Spk.PackageDefinition = (
appVersion = 6, # Increment this for every release.
appMarketingVersion = (defaultText = "0.10.1"),
appMarketingVersion = (defaultText = "0.10.2"),
# Human-readable representation of appVersion. Should match the way you
# identify versions of your app in documentation and marketing.

@ -76,6 +76,7 @@
"API_Embed" : "Embed",
"API_EmbedDisabledFor" : "Disable Embed for Users",
"API_EmbedDisabledFor_Description" : "Comma-separated list of usernames",
"Archive" : "Archive",
"are_also_typing" : "are also typing",
"are_typing" : "are typing",
"Are_you_sure" : "Are you sure?",
@ -139,6 +140,8 @@
"Disable_New_Room_Notification" : "Disable New Room Notification",
"Do_you_want_to_change_to_s_question" : "Do you want to change to <strong>%s</strong>?",
"Drop_to_upload_file" : "Drop to upload file",
"Duplicate_archived_channel_name" : "An archived Channel with name '%s' exists",
"Duplicate_archived_private_group_name" : "An archived Private Group with name '%s' exists",
"Duplicate_channel_name" : "A Channel with name '%s' exists",
"Duplicate_private_group_name" : "A Private Group with name '%s' exists",
"E-mail" : "E-mail",
@ -217,8 +220,8 @@
"italics" : "italics",
"join" : "Join",
"Join_the_Community" : "Join the Community",
"Jump_to_recent_messages" : "Jump to recent messages",
"Jump_to_message" : "Jump to message",
"Jump_to_recent_messages" : "Jump to recent messages",
"Language" : "Language",
"Language_Version" : "English Version",
"Last_login" : "Last login",
@ -383,10 +386,12 @@
"Restart" : "Restart",
"Restart_the_server" : "Restart the server",
"Room" : "Room",
"Room_archived" : "Room archived",
"Room_has_been_deleted" : "Room has been deleted",
"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_unarchived" : "Room unarchived",
"Room_uploaded_file_list" : "Files List",
"Room_uploaded_file_list_empty" : "No files available.",
"room_user_count" : "%s users",
@ -426,6 +431,7 @@
"Settings_updated" : "Settings updated",
"Should_be_a_URL_of_an_image" : "Should be a URL of an image.",
"Should_exists_a_user_with_this_username" : "The user must already exist.",
"Showing_archived_results" : "<p>Showing <b>%s</b> archived results</p>",
"Showing_online_users" : "Showing <b>__total_online__</b> of __total__ users",
"Showing_results" : "<p>Showing <b>%s</b> results</p>",
"Silence" : "Silence",
@ -477,6 +483,7 @@
"There_is_no_integrations" : "There is no integrations",
"This_is_a_push_test_messsage" : "This is a push test messsage",
"True" : "True",
"Unarchive" : "Unarchive",
"Unmute_user" : "Unmute user",
"Unnamed" : "Unnamed",
"Unread_Rooms" : "Unread Rooms",
@ -548,4 +555,4 @@
"Your_entry_has_been_deleted" : "Your entry has been deleted.",
"Your_Open_Source_solution" : "Your own Open Source chat solution",
"Your_push_was_sent_to_s_devices" : "Your push was sent to %s devices"
}
}

@ -27,6 +27,9 @@ Meteor.startup ->
{ _id: 'edit-other-user-info',
roles : ['admin']}
{ _id: 'edit-other-user-password',
roles : ['admin']}
{ _id: 'assign-admin-role',
roles : ['admin']}

@ -0,0 +1,11 @@
Meteor.startup ->
RocketChat.ChannelSettings.addOption
id: 'mail-messages'
template: 'channelSettingsMailMessages'
validation: ->
return RocketChat.authz.hasAllPermission('mail-messages')
RocketChat.callbacks.add 'roomExit', (mainNode) ->
instance = Blaze.getView($('.messages-box')?[0])?.templateInstance()
instance?.resetSelection(false)
, RocketChat.callbacks.priority.MEDIUM, 'room-exit-mail-messages'

@ -0,0 +1,24 @@
.flex-tab {
.mail-message {
form {
margin-top: 20px;
.input-line.double-col {
margin-bottom: 20px;
label {
line-height: 15px;
}
div {
line-height: 15px;
i.octicon {
font-size: 13px;
opacity: 0.4;
vertical-align: top;
}
}
}
}
}
}

@ -0,0 +1,10 @@
Template.channelSettingsMailMessages.events
'click button.mail-messages': (e, t) ->
Session.set 'channelSettingsMailMessages', Session.get('openedRoom')
RocketChat.TabBar.setTemplate('mailMessagesInstructions')
view = Blaze.getView($('.messages-box')[0])
view?.templateInstance?().resetSelection?(true)
Template.channelSettingsMailMessages.onCreated ->
view = Blaze.getView($('.messages-box')[0])
view?.templateInstance?().resetSelection?(false)

@ -0,0 +1,8 @@
<template name="channelSettingsMailMessages">
<li>
<label>{{_ "Mail_Messages"}}</label>
<div>
<button type="button" class="button primary mail-messages">{{_ "Choose_messages"}}</button>
</div>
</li>
</template>

@ -0,0 +1,79 @@
Template.mailMessagesInstructions.helpers
name: ->
return Meteor.user().name
email: ->
return Meteor.user().emails?[0]?.address
roomName: ->
return ChatRoom.findOne(Session.get('openedRoom'))?.name
erroredEmails: ->
return Template.instance()?.erroredEmails.get().join(', ')
Template.mailMessagesInstructions.events
'click .cancel': (e, t) ->
t.reset()
'click .send': (e, t) ->
t.$('.error').hide()
$btn = t.$('button.send')
oldBtnValue = $btn.html()
$btn.html(TAPi18n.__('Sending'))
selectedMessages = $('.messages-box .message.selected')
error = false
if selectedMessages.length is 0
t.$('.error-select').show()
error = true
if t.$('input[name=to]').val().trim()
rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/
emails = t.$('input[name=to]').val().trim().split(',')
erroredEmails = []
for email in emails
unless rfcMailPatternWithName.test email.trim()
erroredEmails.push email.trim()
t.erroredEmails.set erroredEmails
if erroredEmails.length > 0
t.$('.error-invalid-emails').show()
error = true
else
t.$('.error-missing-to').show()
error = true
if error
$btn.html(oldBtnValue)
else
data =
rid: Session.get('openedRoom')
to: t.$('input[name=to]').val().trim()
subject: t.$('input[name=subject]').val().trim()
messages: selectedMessages.map((i, message) -> return message.id).toArray()
language: localStorage.getItem('userLanguage')
Meteor.call 'mailMessages', data, (err, result) ->
$btn.html(oldBtnValue)
if err?
return toastr.error(err.reason or err.message)
toastr.success(TAPi18n.__('Your_email_has_been_queued_for_sending'))
t.reset()
'click .select-all': (e, t) ->
t.$('.error-select').hide()
view = Blaze.getView($('.messages-box')[0])
view?.templateInstance?().selectedMessages = _.pluck(ChatMessage.find({rid: Session.get('openedRoom')})?.fetch(), '_id')
$(".messages-box .message").addClass('selected')
Template.mailMessagesInstructions.onCreated ->
@erroredEmails = new ReactiveVar []
@reset = ->
RocketChat.TabBar.setTemplate('channelSettings')
view = Blaze.getView($('.messages-box')[0])
view?.templateInstance?().resetSelection?(false)
@autorun =>
if Session.get('channelSettingsMailMessages') isnt Session.get('openedRoom')
this.reset()

@ -0,0 +1,44 @@
<template name="mailMessagesInstructions">
<div class="content">
<div class="list-view mail-message">
<div class="status">
<h2>{{_ "Mail_Messages"}}</h2>
</div>
<p>{{_ "Mail_Messages_Instructions"}}</p>
<form>
<fieldset>
<div class="input-line double-col">
<label>{{_ "From"}}</label>
<div>{{name}}</div>
<div>{{email}}</div>
</div>
<div class="input-line double-col">
<label>{{_ "To"}}</label>
<div>
<input type="text" name="to" value="" />
</div>
</div>
<div class="input-line double-col">
<label>{{_ "Subject"}}</label>
<div>
<input type="text" name="subject" value="{{_ "Mail_Messages_Subject" roomName}}" />
</div>
</div>
</fieldset>
</form>
<div class="error error-missing-to alert alert-danger" style="display: none">
{{_ "Mail_Message_Missing_to"}}
</div>
<div class="error error-invalid-emails alert alert-danger" style="display: none">
{{_ "Mail_Message_Invalid_emails" erroredEmails}}
</div>
<div class="error error-select alert alert-danger" style="display: none">
{{{_ "Mail_Message_No_messages_selected_select_all"}}}
</div>
<p style="margin-top: 30px">
<button type="button" class="button secondary cancel">{{_ "Cancel"}}</button>
<button type="button" class="button primary send">{{_ "Send"}}</button>
</p>
</div>
</div>
</template>

@ -0,0 +1,17 @@
{
"Body" : "Body",
"Cancel" : "Cancel",
"Choose_messages" : "Choose messages",
"From" : "From",
"Mail_Message_Missing_to" : "You must provide one or more To e-mail addresses, separated by commas.",
"Mail_Message_No_messages_selected_select_all" : "You haven't selected any messages. Would you like to <a href='#' class='select-all'>select all</a> visible messages?",
"Mail_Messages" : "Mail Messages",
"Mail_Messages_Instructions" : "Choose which messages you want to send via e-mail by clicking the messages",
"Mail_Messages_Subject" : "Here's a selected portion of %s messages",
"Mail_Message_Invalid_emails" : "You have provided one or more invalid e-mails: %s",
"Send" : "Send",
"Sending" : "Sending...",
"Subject" : "Subject",
"To" : "To",
"Your_email_has_been_queued_for_sending" : "Your email has been queued for sending"
}

@ -0,0 +1,51 @@
Package.describe({
name: 'rocketchat:channel-settings-mail-messages',
version: '0.0.1',
summary: 'Channel Settings - Mail Messages',
git: ''
});
Package.onUse(function(api) {
api.versionsFrom('1.0');
api.use([
'coffeescript',
'templating',
'reactive-var',
'less@2.5.0',
'rocketchat:lib@0.0.1',
'rocketchat:channel-settings',
'momentjs:moment'
]);
api.addFiles([
'client/lib/startup.coffee',
'client/stylesheets/mail-messages.less',
'client/views/channelSettingsMailMessages.html',
'client/views/channelSettingsMailMessages.coffee',
'client/views/mailMessagesInstructions.html',
'client/views/mailMessagesInstructions.coffee'
], 'client');
api.addFiles([
'server/lib/startup.coffee',
'server/methods/mailMessages.coffee'
], 'server');
// TAPi18n
var _ = Npm.require('underscore');
var fs = Npm.require('fs');
tapi18nFiles = _.compact(_.map(fs.readdirSync('packages/rocketchat-channel-settings-mail-messages/i18n'), function(filename) {
if (fs.statSync('packages/rocketchat-channel-settings-mail-messages/i18n/' + filename).size > 16) {
return 'i18n/' + filename;
}
}));
api.use('tap:i18n@1.6.1');
api.imply('tap:i18n');
api.addFiles(tapi18nFiles);
});
Package.onTest(function(api) {
});

@ -0,0 +1,3 @@
Meteor.startup ->
permission = { _id: 'mail-messages', roles : [ 'admin' ] }
RocketChat.models.Permissions.upsert( permission._id, { $setOnInsert : permission })

@ -0,0 +1,46 @@
Meteor.methods
'mailMessages': (data) ->
if not Meteor.userId()
throw new Meteor.Error('invalid-user', "[methods] mailMessages -> Invalid user")
check(data, Match.ObjectIncluding({ rid: String, to: String, subject: String, messages: [ String ], language: String }))
room = Meteor.call 'canAccessRoom', data.rid, Meteor.userId()
unless room
throw new Meteor.Error('invalid-room', "[methods] mailMessages -> Invalid room")
unless RocketChat.authz.hasPermission(Meteor.userId(), 'mail-messages')
throw new Meteor.Error 'not-authorized'
rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/
emails = data.to.trim().split(',')
for email in emails
unless rfcMailPatternWithName.test email.trim()
throw new Meteor.Error('invalid-email', "[methods] mailMessages -> Invalid e-mail")
user = Meteor.user()
name = user.name
email = user.emails?[0]?.address
if data.language isnt 'en'
localeFn = Meteor.call 'loadLocale', data.language
if localeFn
Function(localeFn)()
html = ""
RocketChat.models.Messages.findByRoomIdAndMessageIds(data.rid, data.messages, { sort: { ts: 1 } }).forEach (message) ->
dateTime = moment(message.ts).locale(data.language).format('L LT')
html += "<p style='margin-bottom: 5px'><b>#{message.u.username}</b> <span style='color: #aaa; font-size: 12px'>#{dateTime}</span><br />" + RocketChat.Message.parse(message, data.language) + "</p>"
Meteor.defer ->
Email.send
to: emails
from: RocketChat.settings.get('From_Email')
replyTo: email
subject: data.subject
html: html
console.log 'Sending email to ' + emails.join(', ')
return true

@ -0,0 +1,29 @@
RocketChat.ChannelSettings = new class
options = new ReactiveVar {}
###
# Adds an option in Channel Settings
# @config (object)
# id: option id (required)
# template (string): template name to render (required)
# validation (function): if option should be displayed
###
addOption = (config) ->
unless config?.id
throw new Meteor.Error "ChannelSettings-addOption-error", "Option id was not informed."
Tracker.nonreactive ->
opts = options.get()
opts[config.id] = config
options.set opts
getOptions = ->
allOptions = _.toArray options.get()
allowedOptions = _.compact _.map allOptions, (option) ->
if not option.validation? or option.validation()
return option
return _.sortBy allowedOptions, 'order'
addOption: addOption
getOptions: getOptions

@ -1,11 +1,25 @@
.flex-tab {
.channel-settings {
margin-top: 60px;
padding: 20px;
ul {
li {
margin-bottom: 20px;
}
}
form {
label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
div span {
font-size: 14px;
i.octicon {
font-size: 12px;
vertical-align: middle;
margin-left: 3px;
}
}
}
@ -19,15 +33,3 @@
}
}
}
.input-line.double-col {
div {
line-height: 15px;
padding: 10px 20px 10px 0;
i.octicon {
font-size: 13px;
opacity: 0.4;
vertical-align: top;
}
}
}

@ -7,6 +7,8 @@ Template.channelSettings.helpers
return ChatRoom.findOne(@rid)?.t isnt 'd'
roomType: ->
return ChatRoom.findOne(@rid)?.t
channelSettings: ->
return RocketChat.ChannelSettings.getOptions()
roomTypeDescription: ->
roomType = ChatRoom.findOne(@rid)?.t
if roomType is 'c'
@ -17,27 +19,10 @@ Template.channelSettings.helpers
return ChatRoom.findOne(@rid)?.name
roomTopic: ->
return ChatRoom.findOne(@rid)?.topic
archived: ->
return ChatRoom.findOne(@rid)?.archived
Template.channelSettings.events
# 'click .save': (e, t) ->
# e.preventDefault()
# settings =
# roomType: t.$('input[name=roomType]:checked').val()
# roomName: t.$('input[name=roomName]').val()
# roomTopic: t.$('input[name=roomTopic]').val()
# if t.validate()
# Meteor.call 'saveRoomSettings', t.data.rid, settings, (err, results) ->
# if err
# if err.error in [ 'duplicate-name', 'name-invalid' ]
# return toastr.error TAPi18n.__(err.reason, err.details.channelName)
# if err.error is 'invalid-room-type'
# return toastr.error TAPi18n.__(err.reason, err.details.roomType)
# return toastr.error TAPi18n.__(err.reason)
# toastr.success TAPi18n.__ 'Settings_updated'
'keydown input[type=text]': (e, t) ->
if e.keyCode is 13
e.preventDefault()
@ -56,6 +41,20 @@ Template.channelSettings.events
e.preventDefault()
t.saveSetting()
'click .archive': (e, t) ->
e.preventDefault()
Meteor.call 'archiveRoom', t.data.rid, true, (err, results) ->
return toastr.error err.reason if err
toastr.success TAPi18n.__ 'Room_archived'
'click .unarchive': (e, t) ->
e.preventDefault()
Meteor.call 'unarchiveRoom', t.data.rid, true, (err, results) ->
return toastr.error err.reason if err
toastr.success TAPi18n.__ 'Room_unarchived'
Template.channelSettings.onCreated ->
@editing = new ReactiveVar

@ -1,50 +1,63 @@
<template name="channelSettings">
<div class="control">
<div class="header">
<h2>{{_ "Room_Info"}}</h2>
</div>
</div>
<div class="channel-settings scrollable">
<form>
<fieldset>
{{#if notDirect}}
<div class="input-line double-col">
<label>{{_ "Name"}}</label>
<div class="content">
<div class="list-view channel-settings">
<div class="status">
<h2>{{_ "Room_Info"}}</h2>
</div>
<form>
<ul class="list clearfix">
{{#if notDirect}}
<li>
<label>{{_ "Name"}}</label>
<div>
{{#if editing 'roomName'}}
<input type="text" name="roomName" value="{{roomName}}" class="editing" /> <button type="button" class="button secondary cancel">{{_ "Cancel"}}</button> <button type="button" class="button primary save">{{_ "Save"}}</button>
{{else}}
<span>{{roomName}}{{#if canEdit}} <i class="octicon octicon-pencil" data-edit="roomName"></i>{{/if}}</span>
{{/if}}
</div>
</li>
{{/if}}
<li>
<label>{{_ "Topic"}}</label>
<div>
{{#if editing 'roomName'}}
<input type="text" name="roomName" value="{{roomName}}" class="editing" /> <button type="button" class="button secondary cancel">{{_ "Cancel"}}</button> <button type="button" class="button primary save">{{_ "Save"}}</button>
{{#if editing 'roomTopic'}}
<input type="text" name="roomTopic" value="{{roomTopic}}" class="editing" /> <button type="button" class="button secondary cancel">{{_ "Cancel"}}</button> <button type="button" class="button primary save">{{_ "Save"}}</button>
{{else}}
{{roomName}}{{#if canEdit}} <i class="octicon octicon-pencil" data-edit="roomName"></i>{{/if}}
<span>{{roomTopic}}{{#if canEdit}} <i class="octicon octicon-pencil" data-edit="roomTopic"></i>{{/if}}</span>
{{/if}}
</div>
</div>
{{/if}}
<div class="input-line double-col">
<label>{{_ "Topic"}}</label>
<div>
{{#if editing 'roomTopic'}}
<input type="text" name="roomTopic" value="{{roomTopic}}" class="editing" /> <button type="button" class="button secondary cancel">{{_ "Cancel"}}</button> <button type="button" class="button primary save">{{_ "Save"}}</button>
{{else}}
{{roomTopic}}{{#if canEdit}} <i class="octicon octicon-pencil" data-edit="roomTopic"></i>{{/if}}
{{/if}}
</div>
</div>
{{#if notDirect}}
<div class="input-line double-col">
<label>{{_ "Type"}}</label>
<div>
{{#if editing 'roomType'}}
<label><input type="radio" name="roomType" class="editing" value="c" checked="{{$eq roomType 'c'}}" /> {{_ "Channel"}}</label>
<label><input type="radio" name="roomType" value="p" checked="{{$eq roomType 'p'}}" /> {{_ "Private_Group"}}</label>
<button type="button" class="button secondary cancel">{{_ "Cancel"}}</button>
<button type="button" class="button primary save">{{_ "Save"}}</button>
</li>
{{#if notDirect}}
<li>
<label>{{_ "Type"}}</label>
<div>
{{#if editing 'roomType'}}
<label><input type="radio" name="roomType" class="editing" value="c" checked="{{$eq roomType 'c'}}" /> {{_ "Channel"}}</label>
<label><input type="radio" name="roomType" value="p" checked="{{$eq roomType 'p'}}" /> {{_ "Private_Group"}}</label>
<button type="button" class="button secondary cancel">{{_ "Cancel"}}</button>
<button type="button" class="button primary save">{{_ "Save"}}</button>
{{else}}
<span>{{roomTypeDescription}}{{#if canEdit}} <i class="octicon octicon-pencil" data-edit="roomType"></i>{{/if}}</span>
{{/if}}
</div>
</li>
{{/if}}
{{#if notDirect}}
<li>
<label>{{_ "Archive_Unarchive"}}</label>
{{#if archived}}
<button class="button unarchive"><span>{{_ "Unarchive"}}</span></button>
{{else}}
{{roomTypeDescription}}{{#if canEdit}} <i class="octicon octicon-pencil" data-edit="roomType"></i>{{/if}}
<button class="button archive"><span>{{_ "Archive"}}</span></button>
{{/if}}
</div>
</div>
{{/if}}
</fieldset>
</form>
</li>
{{/if}}
{{#each channelSettings}}
{{> Template.dynamic template=template data=data}}
{{/each}}
</ul>
</form>
</div>
</div>
</template>

@ -1,4 +1,5 @@
{
"Archive_Unarchive": "Archive / Unarchive",
"Cancel": "Cancel",
"Channel": "Channel",
"Private_Group": "Private Group",

@ -10,12 +10,15 @@ Package.onUse(function(api) {
api.use([
'coffeescript',
'reactive-var',
'tracker',
'templating',
'less@2.5.0',
'rocketchat:lib@0.0.1'
]);
api.addFiles([
'client/lib/ChannelSettings.coffee',
'client/startup/messageTypes.coffee',
'client/startup/tabBar.coffee',
'client/startup/trackSettingsChange.coffee',

@ -1,4 +1,6 @@
@roomExit = ->
RocketChat.callbacks.run 'roomExit'
BlazeLayout.render 'main', {center: 'none'}
if currentTracker?

@ -0,0 +1,26 @@
RocketChat.Message =
parse: (msg, language) ->
messageType = RocketChat.MessageTypes.getType(msg)
if messageType?.render?
return messageType.render(msg)
else if messageType?.template?
# render template
else if messageType?.message?
if not language and localStorage?.getItem('userLanguage')
language = localStorage.getItem('userLanguage')
if messageType.data?(msg)?
return TAPi18n.__(messageType.message, messageType.data(msg), language)
else
return TAPi18n.__(messageType.message, {}, language)
else
if msg.u?.username is RocketChat.settings.get('Chatops_Username')
msg.html = msg.msg
return msg.html
msg.html = msg.msg
if _.trim(msg.html) isnt ''
msg.html = _.escapeHTML msg.html
# message = RocketChat.callbacks.run 'renderMessage', msg
msg.html = msg.html.replace /\n/gm, '<br/>'
return msg.html

@ -33,6 +33,8 @@ Package.onUse(function(api) {
api.addFiles('lib/settings.coffee');
api.addFiles('lib/callbacks.coffee');
api.addFiles('lib/slashCommand.coffee');
api.addFiles('lib/Message.coffee');
api.addFiles('lib/MessageTypes.coffee');
// SERVER LIB
api.addFiles('server/lib/RateLimiter.coffee', 'server');
@ -92,7 +94,6 @@ Package.onUse(function(api) {
api.addFiles('client/Notifications.coffee', 'client');
api.addFiles('client/TabBar.coffee', 'client');
api.addFiles('client/MessageAction.coffee', 'client');
api.addFiles('client/MessageTypes.coffee', 'client');
// VERSION
api.addFiles('rocketchat.info');

@ -1,3 +1,3 @@
{
"version": "0.10.1"
"version": "0.10.2"
}

@ -23,4 +23,8 @@ Meteor.methods
Meteor.runAsUser userData._id, ->
Meteor.call 'setUsername', userData.username
canEditUserPassword = RocketChat.authz.hasPermission( user._id, 'edit-other-user-password')
if canEditUserPassword and userData.password.trim()
Accounts.setPassword userData._id, userData.password.trim()
return true

@ -116,6 +116,14 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base
return @find(query, options)?.fetch?()?[0]?.ts
findByRoomIdAndMessageIds: (rid, messageIds, options) ->
query =
rid: rid
_id:
$in: messageIds
return @find query, options
cloneAndSaveAsHistoryById: (_id) ->
me = RocketChat.models.Users.findOneById Meteor.userId()
record = @findOneById _id
@ -134,7 +142,6 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base
return @insert record
# UPDATE
setHiddenById: (_id, hidden=true) ->
query =

@ -147,6 +147,17 @@ RocketChat.models.Rooms = new class extends RocketChat.models._Base
return @find query, options
findByTypeAndArchivationState: (type, archivationstate, options) ->
query =
t: type
if archivationstate
query.archived = true
else
query.archived = { $ne: true }
return @find query, options
findByVisitorToken: (visitorToken, options) ->
query =
"v.token": visitorToken

@ -54,7 +54,7 @@ RocketChat.models.Subscriptions = new class extends RocketChat.models._Base
update =
$set:
alert: false
open: false
open: true
archived: false
return @update query, update

@ -0,0 +1 @@
this.AgentUsers = new Mongo.Collection('agentUsers');

@ -0,0 +1 @@
this.LivechatDepartmentAgents = new Mongo.Collection('rocketchat_livechat_department_agents');

@ -411,3 +411,29 @@
}
}
}
.department-agents {
list-style-type: none;
li {
display: inline-block;
background-color: #DDD;
border-radius: 10px;
padding: 2px 8px 2px 2px;
margin: 1px 0;
cursor: pointer;
.icon-plus-circled {
opacity: 0.5;
font-size: 0.8rem;
}
}
}
.agent-info {
input[type='text'] {
width: auto;
line-height: 24px;
height: 24px;
}
}

@ -24,34 +24,51 @@
</div>
<hr />
<h2>{{_ "Agents"}}</h2>
<div class="input-line double-col">
<input type="text" name="agent" placeholder="{{_ "Enter_a_username"}}">
<button name="addAgent" type="button" class="button add-agent">{{_ "Add_agent"}}</button>
</div>
<div class="list">
<table>
<thead>
<tr>
<th width="25%">{{_ "Username"}}</th>
<th>{{_ "Delete"}}</th>
</tr>
</thead>
<tbody>
{{#if agents.length}}
{{#each agents}}
<tr class="agent-info" data-id="{{_id}}">
<td>{{username}}</td>
<td><a href="#remove" class="remove-agent"><i class="icon-trash"></i></a></td>
</tr>
{{/each}}
{{else}}
<fieldset>
<legend>{{_ "Available_agents"}}</legend>
<ul class="department-agents available-agents">
{{#each availableAgents}}
<li><i class="icon-plus-circled"></i>{{username}}</li>
{{/each}}
</ul>
</fieldset>
<fieldset>
<legend>{{_ "Selected_agents"}}</legend>
<div class="list">
<table>
<thead>
<tr>
<td colspan="2">{{_ "There_are_no_agents_added_to_this_department_yet"}}</td>
<th width="25%">{{_ "Username"}}</th>
<th>{{_ "Count"}}</th>
<th>{{_ "Order"}}</th>
<th>&nbsp;</th>
</tr>
{{/if}}
</tbody>
</table>
</div>
</thead>
<tbody>
{{#if selectedAgents}}
{{#each selectedAgents}}
<tr class="agent-info">
<td>{{username}}</td>
<td><input type="text" class="count-{{agentId}}" name="count" value="{{count}}" size="3"></td>
<td><input type="text" class="order-{{agentId}}" name="order" value="{{order}}" size="3"></td>
<td><a href="#remove" class="remove-agent"><i class="icon-trash"></i></a></td>
</tr>
{{/each}}
{{else}}
<tr>
<td colspan="4">{{_ "There_are_no_agents_added_to_this_department_yet"}}</td>
</tr>
{{/if}}
</tbody>
</table>
</div>
</fieldset>
</fieldset>
<div class="submit">
<button type="button" class="button secondary back"><i class="icon-left-big"></i><span>{{_ "Back"}}</span></button>

@ -1,10 +1,16 @@
Template.livechatDepartmentForm.helpers({
department() {
// return Template.instance().department && !_.isEmpty(Template.instance().department.get()) ? Template.instance().department.get() : { enabled: true };
return Template.instance().department.get();
},
agents() {
return Template.instance().department && !_.isEmpty(Template.instance().department.get()) ? Template.instance().department.get().agents : []
},
selectedAgents() {
return _.sortBy(Template.instance().selectedAgents.get(), 'username');
},
availableAgents() {
var selected = _.pluck(Template.instance().selectedAgents.get(), 'username');
return AgentUsers.find({ username: { $nin: selected }}, { sort: { username: 1 } });
}
});
@ -29,16 +35,22 @@ Template.livechatDepartmentForm.events({
var oldBtnValue = $btn.html();
$btn.html(t('Saving'));
agents = instance.department && !_.isEmpty(instance.department.get()) ? instance.department.get().agents : [];
departmentData = {
var departmentData = {
enabled: enabled === "1" ? true : false,
name: name.trim(),
description: description.trim(),
agents: agents
}
description: description.trim()
};
var departmentAgents = [];
instance.selectedAgents.get().forEach((agent) => {
agent.count = instance.$('.count-' + agent.agentId).val();
agent.order = instance.$('.order-' + agent.agentId).val();
departmentAgents.push(agent);
});
Meteor.call('livechat:saveDepartment', _id, departmentData, function(error, result) {
Meteor.call('livechat:saveDepartment', _id, departmentData, departmentAgents, function(error, result) {
$btn.html(oldBtnValue);
if (error) {
return toastr.error(t(error.reason || error.error));
@ -54,59 +66,44 @@ Template.livechatDepartmentForm.events({
FlowRouter.go('livechat-departments');
},
'click button.add-agent' (e, instance) {
'click .remove-agent' (e, instance) {
e.preventDefault();
var $btn = $(e.currentTarget);
var $agent = instance.$('input[name=agent]')
if ($agent.val().trim() === '') {
return toastr.error(t('Please_fill_a_username'));
}
var oldBtnValue = $btn.html();
$btn.html(t('Saving'));
Meteor.call('livechat:searchAgent', $agent.val(), function(error, user) {
$btn.html(oldBtnValue);
if (error) {
return toastr.error(t(error.reason || error.error));
}
department = instance.department.get() || {};
if (department.agents === undefined || !_.isArray(department.agents)) {
department.agents = [];
}
if (!_.findWhere(department.agents, { _id: user._id })) {
department.agents.push(user);
}
instance.department.set(department);
$agent.val('');
});
var selectedAgents = instance.selectedAgents.get();
selectedAgents = _.reject(selectedAgents, (agent) => { return agent._id === this._id });
instance.selectedAgents.set(selectedAgents);
},
'click a.remove-agent' (e, instance) {
e.preventDefault();
department = instance.department.get();
department.agents = _.reject(department.agents, (agent) => { return agent._id === this._id });
instance.department.set(department);
},
'keydown input[name=agent]' (e, instance) {
if (e.keyCode === 13) {
e.preventDefault();
$("button.add-agent").click();
}
'click .available-agents li' (e, instance) {
var selectedAgents = instance.selectedAgents.get();
var agent = _.clone(this);
agent.agentId = this._id;
delete agent._id;
selectedAgents.push(agent);
instance.selectedAgents.set(selectedAgents);
}
});
Template.livechatDepartmentForm.onCreated(function() {
this.department = new ReactiveVar({ enabled: true });
this.selectedAgents = new ReactiveVar([]);
this.subscribe('livechat:agents');
this.autorun(() => {
var sub = this.subscribe('livechat:departments', FlowRouter.getParam('_id'));
if (sub.ready()) {
department = LivechatDepartment.findOne({ _id: FlowRouter.getParam('_id') });
if (department) {
this.department.set(department);
this.subscribe('livechat:departmentAgents', department._id, () => {
var newSelectedAgents = [];
LivechatDepartmentAgents.find({ departmentId: department._id }).forEach((agent) => {
newSelectedAgents.push(agent);
});
this.selectedAgents.set(newSelectedAgents);
});
}
}
});

@ -1,11 +1,6 @@
Template.livechatDepartments.helpers({
"departments": () => {
return LivechatDepartment.find();
},
"numAgents"() {
if (Array.isArray(this.agents)) {
return this.agents.length;
}
}
});

@ -1,8 +1,6 @@
var AgentUsers;
var ManagerUsers;
Meteor.startup(function() {
AgentUsers = new Mongo.Collection('agentUsers');
ManagerUsers = new Mongo.Collection('managerUsers');
});

@ -10,7 +10,7 @@
<li>
<!-- <a href="{{pathFor 'livechat-dashboard'}}" class="{{active 'livechat-dashboard'}}">{{_ "Dashboard"}}</a> -->
<a href="{{pathFor 'livechat-users'}}" class="{{active 'livechat-users'}}">{{_ "User_management"}}</a>
<a href="{{pathFor 'livechat-departments'}}" class="{{active 'livechat-departments'}}">{{_ "Departments"}}</a>
<a href="{{pathFor 'livechat-departments'}}" class="{{active 'livechat-departments' 'livechat-department-edit'}}">{{_ "Departments"}}</a>
<a href="{{pathFor 'livechat-triggers'}}" class="{{active 'livechat-triggers'}}">{{_ "Triggers"}}</a>
<a href="{{pathFor 'livechat-installation'}}" class="{{active 'livechat-installation'}}">{{_ "Installation"}}</a>
<a href="{{pathFor 'livechat-appearance'}}" class="{{active 'livechat-appearance'}}">{{_ "Appearance"}}</a>

@ -1,7 +1,7 @@
Template.livechatFlex.helpers({
active (route) {
active (...routes) {
FlowRouter.watchPathChange();
if (FlowRouter.current().route.name === route) {
if (routes.indexOf(FlowRouter.current().route.name) !== -1) {
return 'active';
}
}

@ -4,9 +4,11 @@
"Add_manager" : "Add manager",
"Agent_added" : "Agent added",
"Agent_removed" : "Agent removed",
"Available_agents" : "Available agents",
"Back" : "Back",
"Closed" : "Closed",
"Copy_to_clipboard" : "Copy to clipboard",
"Count" : "Count",
"Dashboard" : "Dashboard",
"Department_not_found" : "Department not found",
"Department_removed" : "Department removed",
@ -32,10 +34,12 @@
"New_Department" : "New Department",
"Num_Agents" : "# Agents",
"Opened" : "Opened",
"Order" : "Order",
"Please_fill_a_name" : "Please fill a name",
"Please_fill_a_username" : "Please fill a username",
"Please_select_enabled_yes_or_no" : "Please select an option for Enabled",
"Saved" : "Saved",
"Selected_agents" : "Selected agents",
"Send_a_message" : "Send a message",
"Theme" : "Theme",
"There_are_no_agents_added_to_this_department_yet" : "There are no agents added to this department yet.",
@ -48,4 +52,4 @@
"Username_not_found" : "Username not found",
"Visitor_page_URL" : "Visitor page URL",
"Visitor_time_on_site" : "Visitor time on site"
}
}

@ -38,6 +38,12 @@ Package.onUse(function(api) {
api.addFiles('client/stylesheets/livechat.less', 'client');
// collections
api.addFiles('client/collections/AgentUsers.js', 'client');
api.addFiles('client/collections/LivechatDepartment.js', 'client');
api.addFiles('client/collections/LivechatDepartmentAgents.js', 'client');
api.addFiles('client/collections/LivechatTrigger.js', 'client');
// client views
api.addFiles('client/views/app/livechatAppearance.html', 'client');
api.addFiles('client/views/app/livechatAppearance.js', 'client');
@ -80,13 +86,14 @@ Package.onUse(function(api) {
api.addFiles('server/models/Users.js', 'server');
api.addFiles('server/models/Rooms.js', 'server');
api.addFiles('server/models/LivechatDepartment.js', 'server');
api.addFiles('server/models/LivechatDepartmentAgents.js', 'server');
api.addFiles('server/models/LivechatTrigger.js', 'server');
// collections
api.addFiles('client/lib/LivechatDepartment.js', 'client');
api.addFiles('client/lib/LivechatTrigger.js', 'client');
// server lib
api.addFiles('server/lib/getNextAgent.js', 'server');
// publications
api.addFiles('server/publications/departmentAgents.js', 'server');
api.addFiles('server/publications/livechatAgents.js', 'server');
api.addFiles('server/publications/livechatManagers.js', 'server');
api.addFiles('server/publications/livechatDepartments.js', 'server');

@ -1,27 +1,8 @@
this.getNextAgent = function(department) {
var agentFilter = {};
// find agents from that department
if (department) {
var agents = RocketChat.models.LivechatDepartment.getNextAgent(department);
if (!agents) {
return;
}
// sort = {
// count: 1,
// order: 1,
// 'user.name': 1
// }
// update = {
// $inc: {
// count: 1
// }
// }
// queueUser = findAndModify query, sort, update
return RocketChat.models.LivechatDepartment.getNextAgent(department);
} else {
return RocketChat.models.Users.getNextAgent();
}

@ -1,5 +1,5 @@
Meteor.methods({
'livechat:saveDepartment' (_id, departmentData) {
'livechat:saveDepartment' (_id, departmentData, departmentAgents) {
if (!Meteor.userId() || !RocketChat.authz.hasPermission(Meteor.userId(), 'view-livechat-manager')) {
throw new Meteor.Error("not-authorized");
}
@ -17,6 +17,6 @@ Meteor.methods({
}
}
return RocketChat.models.LivechatDepartment.createOrUpdateDepartment(_id, departmentData.enabled, departmentData.name, departmentData.description, departmentData.agents);
return RocketChat.models.LivechatDepartment.createOrUpdateDepartment(_id, departmentData.enabled, departmentData.name, departmentData.description, departmentAgents);
}
});

@ -19,23 +19,42 @@ class LivechatDepartment extends RocketChat.models._Base {
return this.find(query, options);
}
// UPSERT
createOrUpdateDepartment(_id, enabled, name, description, agents, extraData) {
record = {
var agents = [].concat(agents);
var record = {
enabled: enabled,
name: name,
description: description,
agents: []
}
numAgents: agents.length
};
_.extend(record, extraData);
if (!_.isEmpty(agents)) {
for (agent of agents) {
record.agents.push({ _id: agent._id, username: agent.username });
}
if (_id) {
this.update({ _id: _id }, { $set: record });
} else {
_id = this.insert(record);
}
_.extend(record, extraData);
this.upsert({ _id: _id }, { $set: record });
var savedAgents = _.pluck(RocketChat.models.LivechatDepartmentAgents.findByDepartmentId(_id).fetch(), 'agentId');
var agentsToSave = _.pluck(agents, 'agentId');
// remove other agents
_.difference(savedAgents, agentsToSave).forEach((agentId) => {
RocketChat.models.LivechatDepartmentAgents.removeByDepartmentIdAndAgentId(_id, agentId);
});
agents.forEach((agent) => {
RocketChat.models.LivechatDepartmentAgents.saveAgent({
agentId: agent.agentId,
departmentId: _id,
username: agent.username,
count: parseInt(agent.count),
order: parseInt(agent.order)
});
});
return _.extend(record, { _id: _id });
}

@ -0,0 +1,71 @@
/**
* Livechat Department model
*/
class LivechatDepartmentAgents extends RocketChat.models._Base {
constructor() {
super();
this._initModel('livechat_department_agents');
}
findByDepartmentId(departmentId) {
return this.find({ departmentId: departmentId });
}
saveAgent(agent) {
if (agent._id) {
return this.update({ _id: _id }, { $set: agent });
} else {
return this.upsert({
agentId: agent.agentId,
departmentId: agent.departmentId
}, {
$set: {
username: agent.username,
count: parseInt(agent.count),
order: parseInt(agent.order)
}
});
}
}
removeByDepartmentIdAndAgentId(departmentId, agentId) {
this.remove({ departmentId: departmentId, agentId: agentId });
}
getNextAgentForDepartment(departmentId) {
var agents = this.findByDepartmentId(departmentId).fetch();
if (agents.length === 0) {
return;
}
var onlineUsers = RocketChat.models.Users.findOnlineUserFromList(_.pluck(agents, 'username'));
var onlineUsernames = _.pluck(onlineUsers.fetch(), 'username');
var query = {
departmentId: departmentId,
username: {
$in: onlineUsernames
}
};
var sort = {
count: 1,
sort: 1,
username: 1
};
var update = {
$inc: {
count: 1
}
};
var collectionObj = this.model.rawCollection();
var findAndModify = Meteor.wrapAsync(collectionObj.findAndModify, collectionObj);
return findAndModify(query, sort, update);
}
}
RocketChat.models.LivechatDepartmentAgents = new LivechatDepartmentAgents();

@ -0,0 +1,11 @@
Meteor.publish('livechat:departmentAgents', function(departmentId) {
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
if (!RocketChat.authz.hasPermission(this.userId, 'view-livechat-manager')) {
throw new Meteor.Error('not-authorized');
}
return RocketChat.models.LivechatDepartmentAgents.find({ departmentId: departmentId });
});

@ -2475,6 +2475,12 @@ a.github-fork {
border-radius: 4px;
}
}
&.selectable .message {
cursor: pointer;
&.selected {
background-color: #FFD;
}
}
}
.ticks-bar {

@ -23,6 +23,7 @@ Template.adminUserEdit.onCreated ->
userData = { _id: Template.currentData()._id }
userData.name = $("#name", ".edit-form").val()
userData.username = $("#username", ".edit-form").val()
userData.password = $("#password", ".edit-form").val()
unless userData._id and userData.name
toastr.error TAPi18n.__('The_field_is_required'), TAPi18n.__('Name')
@ -32,4 +33,4 @@ Template.adminUserEdit.onCreated ->
toastr.success t('User_updated_successfully')
instance.cancel()
if error
toastr.error error.reason
toastr.error error.reason

@ -13,6 +13,12 @@
<label for="username">{{_ "Username"}}</label>
<input type="text" id="username" autocomplete="off" value="{{username}}">
</div>
{{#if hasPermission 'edit-other-user-password'}}
<div class="input-line">
<label for="password">{{_ "Password"}}</label>
<input type="password" id="password" autocomplete="off" value="">
</div>
{{/if}}
</form>
</div>
<nav>
@ -20,4 +26,4 @@
<button class='button button-block blue save'><span>{{_ "Save"}}</span></button>
</nav>
{{/unless}}
</template>
</template>

@ -90,6 +90,9 @@ Template.createChannelFlex.events
if err.error is 'duplicate-name'
instance.error.set({ duplicate: true })
return
if err.error is 'archived-duplicate-name'
instance.error.set({ archivedduplicate: true })
return
else
return toastr.error err.reason

@ -40,6 +40,12 @@
{{{_ "Duplicate_channel_name" roomName}}}
</div>
{{/if}}
{{#if error.archivedduplicate}}
<div class="input-error">
<strong>{{_ "Oops!"}}</strong>
{{{_ "Duplicate_archived_channel_name" roomName}}}
</div>
{{/if}}
<div class="input-submit">
<button class="button clean primary save-channel">{{_ "Save" }}</button>
<button class="button clean cancel-channel">{{_ "Cancel" }}</button>

@ -1,6 +1,6 @@
Template.listPrivateGroupsFlex.helpers
groups: ->
return ChatSubscription.find { t: { $in: ['p']}, f: { $ne: true } }, { sort: 't': 1, 'name': 1 }
return ChatSubscription.find { t: { $in: ['p']}, f: { $ne: true }, archived: { $ne: true } }, { sort: 't': 1, 'name': 1 }
Template.listPrivateGroupsFlex.events
'click header': ->

@ -85,6 +85,9 @@ Template.privateGroupsFlex.events
if err.error is 'duplicate-name'
instance.error.set({ duplicate: true })
return
if err.error is 'archived-duplicate-name'
instance.error.set({ archivedduplicate: true })
return
return toastr.error err.reason
SideNav.closeFlex()
instance.clearForm()

@ -40,6 +40,12 @@
{{{_ "Duplicate_private_group_name" groupName}}}
</div>
{{/if}}
{{#if error.archivedduplicate}}
<div class="input-error">
<strong>{{_ "Oops!"}}</strong>
{{{_ "Duplicate_archived_private_group_name" groupName}}}
</div>
{{/if}}
<div class="input-submit">
<button class="button clean primary save-pvt-group">{{_ "Save" }}</button>
<button class="button clean cancel-pvt-group">{{_ "Cancel" }}</button>

@ -1,6 +1,13 @@
Template.privateHistory.helpers
history: ->
items = ChatSubscription.find { name: { $regex: Session.get('historyFilter'), $options: 'i' }, t: { $in: ['d', 'c', 'p'] } }, {'sort': { 'ts': -1 } }
items = ChatSubscription.find { name: { $regex: Session.get('historyFilter'), $options: 'i' }, t: { $in: ['d', 'c', 'p'] }, archived: { $ne: true } }, {'sort': { 'ts': -1 } }
return {
items: items
length: items.count()
}
archivedHistory: ->
items = ChatSubscription.find { name: { $regex: Session.get('historyFilter'), $options: 'i' }, t: { $in: ['d', 'c', 'p'] }, archived: true }, {'sort': { 'ts': -1 } }
return {
items: items
length: items.count()

@ -35,6 +35,29 @@
</a>
{{/each}}
</div>
<div class="results">
{{{_ "Showing_archived_results" archivedHistory.length}}}
</div>
<div class="list">
{{#each archivedHistory.items}}
<a href="{{path}}">
<div class="info">
<h3><i class="{{type}}"></i><span class="enter-room">{{name}}</span></h3>
<ul>
{{#with roomOf rid}}
<li>{{_ "n_messages" msgs}}</li>
<li>{{_ "since_creation" creation}}</li>
{{/with}}
</ul>
</div>
<div class="status">
{{#with roomOf rid}}
<strong>{{lastMessage}}</strong>
{{/with}}
</div>
</a>
{{/each}}
</div>
</div>
</section>
</template>

@ -201,6 +201,8 @@ Template.room.helpers
compactView: ->
return 'compact' if Meteor.user()?.settings?.preferences?.compactView
selectable: ->
return Template.instance().selectable.get()
Template.room.events
"click, touchend": (e, t) ->
@ -408,6 +410,31 @@ Template.room.events
template.atBottom = true
RoomHistoryManager.clear(template?.data?._id)
'click .message': (e, template) ->
if template.selectable.get()
document.selection?.empty() or window.getSelection?().removeAllRanges()
data = Blaze.getData(e.currentTarget)
_id = data?._arguments?[1]?._id
if !template.selectablePointer
template.selectablePointer = _id
if !e.shiftKey
template.selectedMessages = template.getSelectedMessages()
template.selectedRange = []
template.selectablePointer = _id
template.selectMessages _id
selectedMessages = $('.messages-box .message.selected').map((i, message) -> message.id)
removeClass = _.difference selectedMessages, template.getSelectedMessages()
addClass = _.difference template.getSelectedMessages(), selectedMessages
for message in removeClass
$(".messages-box ##{message}").removeClass('selected')
for message in addClass
$(".messages-box ##{message}").addClass('selected')
Template.room.onCreated ->
# this.scrollOnBottom = true
# this.typing = new msgTyping this.data._id
@ -415,10 +442,47 @@ Template.room.onCreated ->
this.atBottom = true
this.unreadCount = new ReactiveVar 0
self = @
this.selectable = new ReactiveVar false
this.selectedMessages = []
this.selectedRange = []
this.selectablePointer = null
this.resetSelection = (enabled) =>
this.selectable.set(enabled)
$('.messages-box .message.selected').removeClass 'selected'
this.selectedMessages = []
this.selectedRange = []
this.selectablePointer = null
this.selectMessages = (to) =>
if this.selectablePointer is to and this.selectedRange.length > 0
this.selectedRange = []
else
message1 = ChatMessage.findOne this.selectablePointer
message2 = ChatMessage.findOne to
minTs = _.min([message1.ts, message2.ts])
maxTs = _.max([message1.ts, message2.ts])
this.selectedRange = _.pluck(ChatMessage.find({ rid: message1.rid, ts: { $gte: minTs, $lte: maxTs } }).fetch(), '_id')
this.getSelectedMessages = =>
messages = this.selectedMessages
addMessages = false
for message in this.selectedRange
if messages.indexOf(message) is -1
addMessages = true
break
if addMessages
previewMessages = _.compact(_.uniq(this.selectedMessages.concat(this.selectedRange)))
else
previewMessages = _.compact(_.difference(this.selectedMessages, this.selectedRange))
return previewMessages
@autorun ->
self.subscribe 'fullUserData', Session.get('showUserInfo'), 1
@autorun =>
@subscribe 'fullUserData', Session.get('showUserInfo'), 1
Template.room.onDestroyed ->

@ -47,7 +47,7 @@
{{/if}}
{{/if}}
</div>
<div class="messages-box {{compactView}}">
<div class="messages-box {{#if selectable}}selectable{{/if}} {{compactView}}">
<div class="ticks-bar"></div>
<div class="wrapper {{#if hasMoreNext}}has-more-next{{/if}}">
<ul aria-live="polite">

@ -1,3 +1,3 @@
Meteor.methods
channelsList: ->
return { channels: RocketChat.models.Rooms.findByType('c', { sort: { msgs:-1 } }).fetch() }
return { channels: RocketChat.models.Rooms.findByTypeAndArchivationState('c', false, { sort: { msgs:-1 } }).fetch() }

@ -21,7 +21,10 @@ Meteor.methods
# avoid duplicate names
if RocketChat.models.Rooms.findOneByName name
throw new Meteor.Error 'duplicate-name'
if RocketChat.models.Rooms.findOneByName(name).archived
throw new Meteor.Error 'archived-duplicate-name'
else
throw new Meteor.Error 'duplicate-name'
# name = s.slugify name

@ -24,7 +24,10 @@ Meteor.methods
# avoid duplicate names
if RocketChat.models.Rooms.findOneByName name
throw new Meteor.Error 'duplicate-name'
if RocketChat.models.Rooms.findOneByName(name).archived
throw new Meteor.Error 'archived-duplicate-name'
else
throw new Meteor.Error 'duplicate-name'
# create new room
room = RocketChat.models.Rooms.createWithTypeNameUserAndUsernames 'p', name, me, members,

@ -1,7 +1,7 @@
Meteor.methods
unArchiveRoom: (rid) ->
unarchiveRoom: (rid) ->
if not Meteor.userId()
throw new Meteor.Error 'invalid-user', '[methods] unArchiveRoom -> Invalid user'
throw new Meteor.Error 'invalid-user', '[methods] unarchiveRoom -> Invalid user'
room = RocketChat.models.Rooms.findOneById rid

@ -13,3 +13,4 @@ Meteor.publish 'subscription', ->
open: 1
alert: 1
unread: 1
archived: 1

@ -9,6 +9,7 @@ Meteor.startup ->
usernames: 1
topic: 1
muted: 1
archived: 1
return RocketChat.models.Rooms.findByTypeAndName 'c', identifier, options
@ -22,6 +23,7 @@ Meteor.startup ->
usernames: 1
topic: 1
muted: 1
archived: 1
user = RocketChat.models.Users.findOneById this.userId, fields: username: 1
return RocketChat.models.Rooms.findByTypeAndNameContainigUsername 'p', identifier, user.username, options

Loading…
Cancel
Save