diff --git a/.gitignore b/.gitignore index 2ae4ddd56cc016c35b9152208a8b4a890dcd55d9..fdec437e0c511fe7c8546584cc2139143273c897 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,4 @@ tramp ecosystem.json pm2.json settings.json +build.sh diff --git a/.meteor/packages b/.meteor/packages index 16ac606c7c0b9cca1be870b9529e95451a7807d9..2e0df8c465da7829a7707619d0fe4e256716afb5 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -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 diff --git a/.meteor/versions b/.meteor/versions index 179bf18b1d60e02913851bd389763500a3b0aacc..11a58480adcda44c7c7df3fa48ccabaf87c5a7d3 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -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 @@ -39,7 +39,7 @@ ecmascript@0.1.6 ecmascript-runtime@0.2.6 ejson@1.0.7 email@1.0.8 -emojione:emojione@1.5.2 +emojione:emojione@2.0.0 facebook@1.2.2 fastclick@1.0.7 francocatena:status@1.5.0 @@ -75,7 +75,7 @@ livedata@1.0.15 localstorage@1.0.5 logging@1.0.8 matb33:collection-hooks@0.8.1 -mdg:validation-error@0.1.0 +mdg:validation-error@0.2.0 meteor@1.1.10 meteor-base@1.0.1 meteor-developer@1.1.5 @@ -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 diff --git a/.sandstorm/sandstorm-pkgdef.capnp b/.sandstorm/sandstorm-pkgdef.capnp index ec6a58efe8ca90cd6b1fdbccbce2f8719b95d3a5..9e2a5f678efa6bdaf3a0b2f2f7c1c9bc03d10814 100644 --- a/.sandstorm/sandstorm-pkgdef.capnp +++ b/.sandstorm/sandstorm-pkgdef.capnp @@ -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. diff --git a/build.sh b/example-build.sh similarity index 100% rename from build.sh rename to example-build.sh diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 7de4cc7c6c107efd08eb10b10cec3e0911aeacb4..bfe22fc2fd4710b3c878aa2e6cc19e03f784b77c 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -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" -} \ No newline at end of file +} diff --git a/packages/rocketchat-authorization/server/startup.coffee b/packages/rocketchat-authorization/server/startup.coffee index 1fc3643b4d276786ecb0c5857ab6f8677bc420d1..663f7354970ade72d0a74e1c673df4574256d43f 100644 --- a/packages/rocketchat-authorization/server/startup.coffee +++ b/packages/rocketchat-authorization/server/startup.coffee @@ -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']} diff --git a/packages/rocketchat-channel-settings-mail-messages/client/lib/startup.coffee b/packages/rocketchat-channel-settings-mail-messages/client/lib/startup.coffee new file mode 100644 index 0000000000000000000000000000000000000000..9cf0b62207494f8797dd02f11c62b9e1f9bb2d36 --- /dev/null +++ b/packages/rocketchat-channel-settings-mail-messages/client/lib/startup.coffee @@ -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' diff --git a/packages/rocketchat-channel-settings-mail-messages/client/stylesheets/mail-messages.less b/packages/rocketchat-channel-settings-mail-messages/client/stylesheets/mail-messages.less new file mode 100644 index 0000000000000000000000000000000000000000..6b5e9dd4a5b695c7fb866492494ac21dbd808a9f --- /dev/null +++ b/packages/rocketchat-channel-settings-mail-messages/client/stylesheets/mail-messages.less @@ -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; + } + } + } + } + } +} diff --git a/packages/rocketchat-channel-settings-mail-messages/client/views/channelSettingsMailMessages.coffee b/packages/rocketchat-channel-settings-mail-messages/client/views/channelSettingsMailMessages.coffee new file mode 100644 index 0000000000000000000000000000000000000000..855a105f0f4c96ff6889df49149839c42f1054de --- /dev/null +++ b/packages/rocketchat-channel-settings-mail-messages/client/views/channelSettingsMailMessages.coffee @@ -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) diff --git a/packages/rocketchat-channel-settings-mail-messages/client/views/channelSettingsMailMessages.html b/packages/rocketchat-channel-settings-mail-messages/client/views/channelSettingsMailMessages.html new file mode 100644 index 0000000000000000000000000000000000000000..10b1cce82fe7934fea31b5bed1a2866b29dc3578 --- /dev/null +++ b/packages/rocketchat-channel-settings-mail-messages/client/views/channelSettingsMailMessages.html @@ -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> diff --git a/packages/rocketchat-channel-settings-mail-messages/client/views/mailMessagesInstructions.coffee b/packages/rocketchat-channel-settings-mail-messages/client/views/mailMessagesInstructions.coffee new file mode 100644 index 0000000000000000000000000000000000000000..ed30d543765229f35f1ef24db191f031b09ea7ca --- /dev/null +++ b/packages/rocketchat-channel-settings-mail-messages/client/views/mailMessagesInstructions.coffee @@ -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() diff --git a/packages/rocketchat-channel-settings-mail-messages/client/views/mailMessagesInstructions.html b/packages/rocketchat-channel-settings-mail-messages/client/views/mailMessagesInstructions.html new file mode 100644 index 0000000000000000000000000000000000000000..c8c27429f6d9b48fa9e491ea9cd387762f7613d4 --- /dev/null +++ b/packages/rocketchat-channel-settings-mail-messages/client/views/mailMessagesInstructions.html @@ -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> diff --git a/packages/rocketchat-channel-settings-mail-messages/i18n/en.i18n.json b/packages/rocketchat-channel-settings-mail-messages/i18n/en.i18n.json new file mode 100644 index 0000000000000000000000000000000000000000..32ec45e9cdb50673ef64eb9f8160454c6acc49d8 --- /dev/null +++ b/packages/rocketchat-channel-settings-mail-messages/i18n/en.i18n.json @@ -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" +} diff --git a/packages/rocketchat-channel-settings-mail-messages/package.js b/packages/rocketchat-channel-settings-mail-messages/package.js new file mode 100644 index 0000000000000000000000000000000000000000..b0fb444889dd0f9b284e5f21b963d8b23a24abf3 --- /dev/null +++ b/packages/rocketchat-channel-settings-mail-messages/package.js @@ -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) { + +}); diff --git a/packages/rocketchat-channel-settings-mail-messages/server/lib/startup.coffee b/packages/rocketchat-channel-settings-mail-messages/server/lib/startup.coffee new file mode 100644 index 0000000000000000000000000000000000000000..0a30dd195e4989f195fef4b0592e907b3bf0fda7 --- /dev/null +++ b/packages/rocketchat-channel-settings-mail-messages/server/lib/startup.coffee @@ -0,0 +1,3 @@ +Meteor.startup -> + permission = { _id: 'mail-messages', roles : [ 'admin' ] } + RocketChat.models.Permissions.upsert( permission._id, { $setOnInsert : permission }) diff --git a/packages/rocketchat-channel-settings-mail-messages/server/methods/mailMessages.coffee b/packages/rocketchat-channel-settings-mail-messages/server/methods/mailMessages.coffee new file mode 100644 index 0000000000000000000000000000000000000000..499543047cd730e4d3229c56847a438616fa98d1 --- /dev/null +++ b/packages/rocketchat-channel-settings-mail-messages/server/methods/mailMessages.coffee @@ -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 diff --git a/packages/rocketchat-channel-settings/client/lib/ChannelSettings.coffee b/packages/rocketchat-channel-settings/client/lib/ChannelSettings.coffee new file mode 100644 index 0000000000000000000000000000000000000000..f9322fbb446e609e226c5cb24962ee35d1bccf81 --- /dev/null +++ b/packages/rocketchat-channel-settings/client/lib/ChannelSettings.coffee @@ -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 diff --git a/packages/rocketchat-channel-settings/client/stylesheets/channel-settings.less b/packages/rocketchat-channel-settings/client/stylesheets/channel-settings.less index a09d3ee179c2e5800f74f5b334bc852563d37dca..872c1a7331835eeff8df795a9fd45e08f4bbb968 100644 --- a/packages/rocketchat-channel-settings/client/stylesheets/channel-settings.less +++ b/packages/rocketchat-channel-settings/client/stylesheets/channel-settings.less @@ -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; - } - } -} diff --git a/packages/rocketchat-channel-settings/client/views/channelSettings.coffee b/packages/rocketchat-channel-settings/client/views/channelSettings.coffee index 7f5639384fe1a1e628078e36028a9ca694cf9ce7..30529f9a8091ef7ff082cd6366ad5b25e14989ea 100644 --- a/packages/rocketchat-channel-settings/client/views/channelSettings.coffee +++ b/packages/rocketchat-channel-settings/client/views/channelSettings.coffee @@ -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 diff --git a/packages/rocketchat-channel-settings/client/views/channelSettings.html b/packages/rocketchat-channel-settings/client/views/channelSettings.html index bba2381095c2b6e0f5b6f4bd881374581e806e4d..8a326bd49117b25d98d02235dbe8fc83efd3ea35 100644 --- a/packages/rocketchat-channel-settings/client/views/channelSettings.html +++ b/packages/rocketchat-channel-settings/client/views/channelSettings.html @@ -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> diff --git a/packages/rocketchat-channel-settings/i18n/en.i18n.json b/packages/rocketchat-channel-settings/i18n/en.i18n.json index 44e8aa2ccb9567719ab90a880f031f2a47ba7d94..c6ebf4e70dc93148390527264bfcf51fc5c352c2 100644 --- a/packages/rocketchat-channel-settings/i18n/en.i18n.json +++ b/packages/rocketchat-channel-settings/i18n/en.i18n.json @@ -1,4 +1,5 @@ { + "Archive_Unarchive": "Archive / Unarchive", "Cancel": "Cancel", "Channel": "Channel", "Private_Group": "Private Group", diff --git a/packages/rocketchat-channel-settings/package.js b/packages/rocketchat-channel-settings/package.js index 127e12269405c9a3569bf37acb62640590698884..c6f17e5b597ba1e38a71bcca7a94a164173dbad7 100644 --- a/packages/rocketchat-channel-settings/package.js +++ b/packages/rocketchat-channel-settings/package.js @@ -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', diff --git a/packages/rocketchat-file/file.server.coffee b/packages/rocketchat-file/file.server.coffee index a47489fac2447895e0d34bf506f3bc9d1a1ce73c..80b6e64bfc7f4d051ebbf77e8b9e9e3d289e7dfe 100644 --- a/packages/rocketchat-file/file.server.coffee +++ b/packages/rocketchat-file/file.server.coffee @@ -20,34 +20,50 @@ RocketChatFile = RocketChat.settings.updateOptionsById 'Accounts_AvatarResize', {alert: 'The_image_resize_will_not_work_because_we_can_not_detect_ImageMagick_or_GraphicsMagick_installed_in_your_server'} -exec 'gm version', Meteor.bindEnvironment (error, stdout, stderr) -> - if not error? and stdout.indexOf('GraphicsMagick') > -1 - RocketChatFile.enable() - - RocketChat.Info.GraphicsMagick = - enabled: true - version: stdout - else - RocketChat.Info.GraphicsMagick = - enabled: false - - exec 'convert -version', Meteor.bindEnvironment (error, stdout, stderr) -> - if not error? and stdout.indexOf('ImageMagick') > -1 - if RocketChatFile.enabled isnt true - # Enable GM to work with ImageMagick if no GraphicsMagick - RocketChatFile.gm = RocketChatFile.gm.subClass({imageMagick: true}) - RocketChatFile.enable() - - RocketChat.Info.ImageMagick = +detectGM = -> + console.log 'GM: Getting GraphicsMagick version' + exec 'gm version', Meteor.bindEnvironment (error, stdout, stderr) -> + console.log 'GM: GraphicsMagick', arguments + if not error? and stdout.indexOf('GraphicsMagick') > -1 + console.log 'GM: GraphicsMagick installed' + RocketChatFile.enable() + + RocketChat.Info.GraphicsMagick = enabled: true version: stdout else - if RocketChatFile.enabled isnt true - RocketChatFile.disable() - - RocketChat.Info.ImageMagick = + console.log 'GM: GraphicsMagick not installed' + RocketChat.Info.GraphicsMagick = enabled: false + console.log 'GM: Getting ImageMagick version' + exec 'convert -version', Meteor.bindEnvironment (error, stdout, stderr) -> + console.log 'GM: ImageMagick', arguments + if not error? and stdout.indexOf('ImageMagick') > -1 + console.log 'GM: ImageMagick installed' + if RocketChatFile.enabled isnt true + # Enable GM to work with ImageMagick if no GraphicsMagick + RocketChatFile.gm = RocketChatFile.gm.subClass({imageMagick: true}) + RocketChatFile.enable() + + RocketChat.Info.ImageMagick = + enabled: true + version: stdout + else + console.log 'GM: ImageMagick not installed' + if RocketChatFile.enabled isnt true + RocketChatFile.disable() + + RocketChat.Info.ImageMagick = + enabled: false + +detectGM() + +Meteor.methods + 'detectGM': -> + detectGM() + return + RocketChatFile.bufferToStream = (buffer) -> bufferStream = new stream.PassThrough() diff --git a/packages/rocketchat-lib/client/lib/roomExit.coffee b/packages/rocketchat-lib/client/lib/roomExit.coffee index 7ee736f5e2ea1661e584d971b7224aee8e50131d..d7da9ed4a8718746e4b3c507e468a6411e9d59b6 100644 --- a/packages/rocketchat-lib/client/lib/roomExit.coffee +++ b/packages/rocketchat-lib/client/lib/roomExit.coffee @@ -1,4 +1,6 @@ @roomExit = -> + RocketChat.callbacks.run 'roomExit' + BlazeLayout.render 'main', {center: 'none'} if currentTracker? diff --git a/packages/rocketchat-lib/lib/Message.coffee b/packages/rocketchat-lib/lib/Message.coffee new file mode 100644 index 0000000000000000000000000000000000000000..cbf6e92a89ff0e80376e19e12bfa538ea6abdeb2 --- /dev/null +++ b/packages/rocketchat-lib/lib/Message.coffee @@ -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 diff --git a/packages/rocketchat-lib/client/MessageTypes.coffee b/packages/rocketchat-lib/lib/MessageTypes.coffee similarity index 100% rename from packages/rocketchat-lib/client/MessageTypes.coffee rename to packages/rocketchat-lib/lib/MessageTypes.coffee diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index aacfc1337ffca1168b1be25ee2d67bc9b887ac74..34e49b1c7f330b6d61801c73dbf9092942516390 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -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'); diff --git a/packages/rocketchat-lib/rocketchat.info b/packages/rocketchat-lib/rocketchat.info index 506e7cd2a4c1e179445526c6bbd32fba4b8ad694..f8dc5a090df3ce2c296fe9654038f7362423e808 100644 --- a/packages/rocketchat-lib/rocketchat.info +++ b/packages/rocketchat-lib/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "0.10.1" + "version": "0.10.2" } diff --git a/packages/rocketchat-lib/server/methods/updateUser.coffee b/packages/rocketchat-lib/server/methods/updateUser.coffee index 53f3536d0d8ee424e2d938d7e2f6f3ab37ce778e..2416d3d11846058197b35372e529bc6bc88c2c1c 100644 --- a/packages/rocketchat-lib/server/methods/updateUser.coffee +++ b/packages/rocketchat-lib/server/methods/updateUser.coffee @@ -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 diff --git a/packages/rocketchat-lib/server/models/Messages.coffee b/packages/rocketchat-lib/server/models/Messages.coffee index 5010fc05d4c69368b0fe4020b0a1ada3b42ec578..3c9375f075b69e90738ea223032771e0ca00267f 100644 --- a/packages/rocketchat-lib/server/models/Messages.coffee +++ b/packages/rocketchat-lib/server/models/Messages.coffee @@ -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 = diff --git a/packages/rocketchat-lib/server/models/Rooms.coffee b/packages/rocketchat-lib/server/models/Rooms.coffee index a611abaec164f6f43f75e074bd9a6e29b8de456e..4befae7bfe04eb56461e5ded048e15751f4760d4 100644 --- a/packages/rocketchat-lib/server/models/Rooms.coffee +++ b/packages/rocketchat-lib/server/models/Rooms.coffee @@ -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 diff --git a/packages/rocketchat-lib/server/models/Subscriptions.coffee b/packages/rocketchat-lib/server/models/Subscriptions.coffee index 209464bd1bb15f3f354aafeff151caa0735aa91e..77c697dd41aeed24082cb06e58c3cae4d456ad4c 100644 --- a/packages/rocketchat-lib/server/models/Subscriptions.coffee +++ b/packages/rocketchat-lib/server/models/Subscriptions.coffee @@ -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 diff --git a/packages/rocketchat-livechat/client/collections/AgentUsers.js b/packages/rocketchat-livechat/client/collections/AgentUsers.js new file mode 100644 index 0000000000000000000000000000000000000000..e571fad61d2b47890c451156e30a86abce926e07 --- /dev/null +++ b/packages/rocketchat-livechat/client/collections/AgentUsers.js @@ -0,0 +1 @@ +this.AgentUsers = new Mongo.Collection('agentUsers'); diff --git a/packages/rocketchat-livechat/client/lib/LivechatDepartment.js b/packages/rocketchat-livechat/client/collections/LivechatDepartment.js similarity index 100% rename from packages/rocketchat-livechat/client/lib/LivechatDepartment.js rename to packages/rocketchat-livechat/client/collections/LivechatDepartment.js diff --git a/packages/rocketchat-livechat/client/collections/LivechatDepartmentAgents.js b/packages/rocketchat-livechat/client/collections/LivechatDepartmentAgents.js new file mode 100644 index 0000000000000000000000000000000000000000..08ea1741134bb6e9997d804c392ef8898f4dffc3 --- /dev/null +++ b/packages/rocketchat-livechat/client/collections/LivechatDepartmentAgents.js @@ -0,0 +1 @@ +this.LivechatDepartmentAgents = new Mongo.Collection('rocketchat_livechat_department_agents'); diff --git a/packages/rocketchat-livechat/client/lib/LivechatTrigger.js b/packages/rocketchat-livechat/client/collections/LivechatTrigger.js similarity index 100% rename from packages/rocketchat-livechat/client/lib/LivechatTrigger.js rename to packages/rocketchat-livechat/client/collections/LivechatTrigger.js diff --git a/packages/rocketchat-livechat/client/stylesheets/livechat.less b/packages/rocketchat-livechat/client/stylesheets/livechat.less index dc12fb153ec48c1bf2259304f6abdba3a31321cf..b389c38d398b9e3b73031a76dc70c23c38959ca8 100644 --- a/packages/rocketchat-livechat/client/stylesheets/livechat.less +++ b/packages/rocketchat-livechat/client/stylesheets/livechat.less @@ -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; + } +} diff --git a/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.html b/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.html index 5a82ac77c719601b0ec0dbcb7df3d79463661222..fccbaa7f071818e87b02d36fd87981cd83680d6a 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.html +++ b/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.html @@ -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> </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> diff --git a/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.js b/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.js index d4ff6d0fcac65b7cd1ed2d59538d947dd2d37e8a..6f5017e0ca2efb8234d0696c6b4339f001cd23d4 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.js +++ b/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.js @@ -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); + }); } } }); diff --git a/packages/rocketchat-livechat/client/views/app/livechatDepartments.js b/packages/rocketchat-livechat/client/views/app/livechatDepartments.js index eba12b82bc14bdec7d16bca2d36465b25c14f4f0..e5d0746485dc5ca44f2183e639372807151d4e8e 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatDepartments.js +++ b/packages/rocketchat-livechat/client/views/app/livechatDepartments.js @@ -1,11 +1,6 @@ Template.livechatDepartments.helpers({ "departments": () => { return LivechatDepartment.find(); - }, - "numAgents"() { - if (Array.isArray(this.agents)) { - return this.agents.length; - } } }); diff --git a/packages/rocketchat-livechat/client/views/app/livechatUsers.js b/packages/rocketchat-livechat/client/views/app/livechatUsers.js index 5e29f332b938847d31fba73dcb20fde1d8c545f8..eeb2d1dfa783871db0ac7e895ea3ec79d40ff31a 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatUsers.js +++ b/packages/rocketchat-livechat/client/views/app/livechatUsers.js @@ -1,8 +1,6 @@ -var AgentUsers; var ManagerUsers; Meteor.startup(function() { - AgentUsers = new Mongo.Collection('agentUsers'); ManagerUsers = new Mongo.Collection('managerUsers'); }); diff --git a/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.html b/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.html index a859f5385a506e35d7a26bc9766037254c8fdff7..15d3ed86810d5ea9900b2019a5a1e405bcaf0040 100644 --- a/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.html +++ b/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.html @@ -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> diff --git a/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.js b/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.js index 4e2578cea84e482ae48969d0c515713f126c3d4d..f7a8f2f0accfa20b5f6c2ac43157e25744ef432b 100644 --- a/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.js +++ b/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.js @@ -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'; } } diff --git a/packages/rocketchat-livechat/i18n/en.i18n.json b/packages/rocketchat-livechat/i18n/en.i18n.json index 072aeddc3204eab22611987f8b9da8c5a5469d2c..754decc3449151c2131a047e0d8f0fab4c8b9bc0 100644 --- a/packages/rocketchat-livechat/i18n/en.i18n.json +++ b/packages/rocketchat-livechat/i18n/en.i18n.json @@ -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" -} \ No newline at end of file +} diff --git a/packages/rocketchat-livechat/package.js b/packages/rocketchat-livechat/package.js index b692038ae8d6f929d7b09cb48c743afdf3414b9c..f6b14d25635b5704926377669406c7b63bcd597e 100644 --- a/packages/rocketchat-livechat/package.js +++ b/packages/rocketchat-livechat/package.js @@ -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'); diff --git a/packages/rocketchat-livechat/server/lib/getNextAgent.js b/packages/rocketchat-livechat/server/lib/getNextAgent.js index 1e9aba08673c6f860d2f00c818f0ac0edbbeaa3c..3259ab642d93af8edf58f88aed3855b42bb3a3d2 100644 --- a/packages/rocketchat-livechat/server/lib/getNextAgent.js +++ b/packages/rocketchat-livechat/server/lib/getNextAgent.js @@ -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(); } diff --git a/packages/rocketchat-livechat/server/methods/saveDepartment.js b/packages/rocketchat-livechat/server/methods/saveDepartment.js index de3f656817bc755167fc25da10687862a884758d..2f626c395720add0dfd8b8e533a5363c955b21ea 100644 --- a/packages/rocketchat-livechat/server/methods/saveDepartment.js +++ b/packages/rocketchat-livechat/server/methods/saveDepartment.js @@ -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); } }); diff --git a/packages/rocketchat-livechat/server/models/LivechatDepartment.js b/packages/rocketchat-livechat/server/models/LivechatDepartment.js index 41141b8d68ef4943ed85dfe3b10ec25c8058b010..d474415ea9638d0e08ee60f2134c6ddb5fc5810c 100644 --- a/packages/rocketchat-livechat/server/models/LivechatDepartment.js +++ b/packages/rocketchat-livechat/server/models/LivechatDepartment.js @@ -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 }); } diff --git a/packages/rocketchat-livechat/server/models/LivechatDepartmentAgents.js b/packages/rocketchat-livechat/server/models/LivechatDepartmentAgents.js new file mode 100644 index 0000000000000000000000000000000000000000..a4d56fa7ba825bd433ef79164403bab624fb0b3b --- /dev/null +++ b/packages/rocketchat-livechat/server/models/LivechatDepartmentAgents.js @@ -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(); diff --git a/packages/rocketchat-livechat/server/publications/departmentAgents.js b/packages/rocketchat-livechat/server/publications/departmentAgents.js new file mode 100644 index 0000000000000000000000000000000000000000..b09f4fe3dd9c7f604544f97b557308f78a727dbf --- /dev/null +++ b/packages/rocketchat-livechat/server/publications/departmentAgents.js @@ -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 }); +}); diff --git a/packages/rocketchat-message-attachments/client/messageAttachment.html b/packages/rocketchat-message-attachments/client/messageAttachment.html index a4abddf40450aa55b08a05e5e09421fda2ecadba..dc02f0c03c968e20e0079ac796f64ee67e9cb910 100644 --- a/packages/rocketchat-message-attachments/client/messageAttachment.html +++ b/packages/rocketchat-message-attachments/client/messageAttachment.html @@ -9,7 +9,7 @@ {{#if author_link}} <div class="attachment-author"> {{#if author_icon}} - <img src="{{author_icon}}"> + <img src="{{fixCordova author_icon}}"> {{/if}} <a href="{{fixCordova author_link}}" target="_blank">{{author_name}}</a> </div> @@ -49,7 +49,7 @@ <div class="attachment-image"> {{#if showImage}} <a href="{{fixCordova image_url}}" class="swipebox" target="_blank"> - <div class="inline-image" style="background-image: url({{fixCordova image_url}});"> + <div class="inline-image" style="background-image: url('{{fixCordova image_url}}');"> <img src="{{fixCordova image_url}}" height="{{getImageHeight image_dimensions.height}}"> </div> </a> diff --git a/packages/rocketchat-oembed/client/oembedImageWidget.html b/packages/rocketchat-oembed/client/oembedImageWidget.html index d17ed9ff03a361714fc5637525a921418a07270c..144dd7665215be92aefe9b16c2d9d8c8584e22a8 100644 --- a/packages/rocketchat-oembed/client/oembedImageWidget.html +++ b/packages/rocketchat-oembed/client/oembedImageWidget.html @@ -3,7 +3,7 @@ {{#if parsedUrl}} <div> <a href="{{url}}" class="swipebox" target="_blank"> - <div class="inline-image" style="background-image: url({{url}});"> + <div class="inline-image" style="background-image: url('{{url}}');"> <img src="{{url}}" height="200"> </div> </a> diff --git a/packages/rocketchat-theme/assets/stylesheets/base.less b/packages/rocketchat-theme/assets/stylesheets/base.less index 34b5de48abc488a8ff71e69f61ea24d483ef0da7..471f876ec525bebd4f8ab1c4ce04a7cf5759273c 100644 --- a/packages/rocketchat-theme/assets/stylesheets/base.less +++ b/packages/rocketchat-theme/assets/stylesheets/base.less @@ -1583,7 +1583,7 @@ a.github-fork { } } h2 { - max-width: 90%; + width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; @@ -2475,6 +2475,12 @@ a.github-fork { border-radius: 4px; } } + &.selectable .message { + cursor: pointer; + &.selected { + background-color: #FFD; + } + } } .ticks-bar { @@ -2493,7 +2499,7 @@ a.github-fork { } .message { - padding: 11px 20px 11px 70px; + padding: 8px 20px 4px 70px; position: relative; line-height: 20px; min-height: 40px; @@ -2635,7 +2641,6 @@ a.github-fork { .thumb { position: absolute; left: 20px; - top: 11px; display: block; width: 40px; height: 40px; @@ -2671,6 +2676,7 @@ a.github-fork { min-height: 20px; padding-top: 4px; padding-bottom: 4px; + margin-top: 0px; .user { display: none; } @@ -2699,6 +2705,9 @@ a.github-fork { margin-left: 1px; } } + .body { + margin-top: 0px; + } // .message-dropdown { // top: 100%; @@ -2735,6 +2744,7 @@ a.github-fork { .body { opacity: 1; .transition(opacity 1s linear); + margin-top: 2px; .inline-image { background-size: contain; @@ -2765,9 +2775,9 @@ a.github-fork { .compact { .message { - padding: 5px 20px 5px 70px; - .thumb { - top: 5px; + padding: 4px 20px 4px 70px; + .body { + margin-top: 0px; } } } diff --git a/packages/rocketchat-theme/assets/stylesheets/rtl.less b/packages/rocketchat-theme/assets/stylesheets/rtl.less index 9f4d66fcd297e795c2343024855f88efe793687c..2ad79e63b2e44c0235c390d8688a0a13eed94c36 100644 --- a/packages/rocketchat-theme/assets/stylesheets/rtl.less +++ b/packages/rocketchat-theme/assets/stylesheets/rtl.less @@ -109,6 +109,7 @@ ul .opt { .left(0px); .padding-left(10px); + text-align: left; } .flex-nav { .right(0px); diff --git a/packages/rocketchat-ui-admin/admin/users/adminUserEdit.coffee b/packages/rocketchat-ui-admin/admin/users/adminUserEdit.coffee index f089dff3bed1190176132eeb2e9bea31b5b9da2a..aeb9352b1558b88b9733e57184d6fcfdcc410f76 100644 --- a/packages/rocketchat-ui-admin/admin/users/adminUserEdit.coffee +++ b/packages/rocketchat-ui-admin/admin/users/adminUserEdit.coffee @@ -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 \ No newline at end of file + toastr.error error.reason diff --git a/packages/rocketchat-ui-admin/admin/users/adminUserEdit.html b/packages/rocketchat-ui-admin/admin/users/adminUserEdit.html index 686f999ffdba88b0c4db402a401fba8d0247fbf8..f1e620f95f96097c3dc9e0066633a6be064e5e37 100644 --- a/packages/rocketchat-ui-admin/admin/users/adminUserEdit.html +++ b/packages/rocketchat-ui-admin/admin/users/adminUserEdit.html @@ -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> \ No newline at end of file +</template> diff --git a/packages/rocketchat-ui-sidenav/side-nav/createChannelFlex.coffee b/packages/rocketchat-ui-sidenav/side-nav/createChannelFlex.coffee index 7d5448acdb925e9d3ef806ce7acb198e5dbf9919..a6abfa19958ea9006224cad14569b3b2c0ecb2a3 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/createChannelFlex.coffee +++ b/packages/rocketchat-ui-sidenav/side-nav/createChannelFlex.coffee @@ -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 diff --git a/packages/rocketchat-ui-sidenav/side-nav/createChannelFlex.html b/packages/rocketchat-ui-sidenav/side-nav/createChannelFlex.html index 2f5e30431261b80a07e7c46c10ffa6f4489419b3..32f223608d24a7db70f8057a24cd895393877b1c 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/createChannelFlex.html +++ b/packages/rocketchat-ui-sidenav/side-nav/createChannelFlex.html @@ -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> diff --git a/packages/rocketchat-ui-sidenav/side-nav/listPrivateGroupsFlex.coffee b/packages/rocketchat-ui-sidenav/side-nav/listPrivateGroupsFlex.coffee index 6b15e8505425b34915df3dbfc4069c6c66f02c80..b542942819e06e1307a21719e8dac2415e925ce1 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/listPrivateGroupsFlex.coffee +++ b/packages/rocketchat-ui-sidenav/side-nav/listPrivateGroupsFlex.coffee @@ -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': -> diff --git a/packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.coffee b/packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.coffee index 2b8db2c1bd674a2a20d6cccd869cce013d9e0b55..260dec67a5caac176621354b0a0fcc7a4e0f5ec7 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.coffee +++ b/packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.coffee @@ -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() diff --git a/packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.html b/packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.html index 24e9050283e27813df21f4a69c9df52371dcb6d1..821a4361efa441be9540cea27067762e3d33c850 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.html +++ b/packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.html @@ -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> diff --git a/packages/rocketchat-ui/views/app/privateHistory.coffee b/packages/rocketchat-ui/views/app/privateHistory.coffee index b04ecd4f8037f91a75c05d9bb8c0e097e9a363a3..97a45d9aca7b7af95cb98de037015edd53392fb1 100644 --- a/packages/rocketchat-ui/views/app/privateHistory.coffee +++ b/packages/rocketchat-ui/views/app/privateHistory.coffee @@ -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() diff --git a/packages/rocketchat-ui/views/app/privateHistory.html b/packages/rocketchat-ui/views/app/privateHistory.html index 7b51de5161cd0af6e14acd06109220772742101b..fd8d6caa3a4644aad0f692f0ec0e7c551cc74e90 100644 --- a/packages/rocketchat-ui/views/app/privateHistory.html +++ b/packages/rocketchat-ui/views/app/privateHistory.html @@ -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> diff --git a/packages/rocketchat-ui/views/app/room.coffee b/packages/rocketchat-ui/views/app/room.coffee index f2b898cb3bbc725ef9616758bd61f4ce8032dd80..2127c7662af4519475c6aa461ad8a0ef2a9c6f70 100644 --- a/packages/rocketchat-ui/views/app/room.coffee +++ b/packages/rocketchat-ui/views/app/room.coffee @@ -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 -> diff --git a/packages/rocketchat-ui/views/app/room.html b/packages/rocketchat-ui/views/app/room.html index d63dcc7dd97e786eb00b6599084e8141d29f717b..9f7f7f0fd91438341962908f07ebba6fb01ee597 100644 --- a/packages/rocketchat-ui/views/app/room.html +++ b/packages/rocketchat-ui/views/app/room.html @@ -13,11 +13,8 @@ <a href="#favorite" class="toggle-favorite"><i class="{{favorite}}" aria-label="{{_ favoriteLabel}}"></i></a> {{/if}} <i class="{{roomIcon}} status-{{userStatus}}"></i> - <span class="room-title {{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" aria-label="{{_ "Edit"}}"></i></a> - {{/if}} + <span class="room-title">{{roomName}}</span> + <span class="room-topic">{{roomTopic}}</span> </h2> </header> <div class="container-bars"> @@ -50,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"> diff --git a/server/methods/channelsList.coffee b/server/methods/channelsList.coffee index 7aa42ff6351877a870f922e7286bfd9481a32fba..c57d66e3a5474f281e819d7d0c7535559ba27811 100644 --- a/server/methods/channelsList.coffee +++ b/server/methods/channelsList.coffee @@ -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() } diff --git a/server/methods/createChannel.coffee b/server/methods/createChannel.coffee index 47f97d22d407347fa10c852b774cb5c5d5f6a1bd..c2e8db8ce1da0be0424ea718f7bef65eaed48468 100644 --- a/server/methods/createChannel.coffee +++ b/server/methods/createChannel.coffee @@ -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 diff --git a/server/methods/createPrivateGroup.coffee b/server/methods/createPrivateGroup.coffee index d308e1e7ae9b039988e50ba67543d9108ebf5352..8bfa8304d98b5ba14b8c2d45a376e478f3cf6147 100644 --- a/server/methods/createPrivateGroup.coffee +++ b/server/methods/createPrivateGroup.coffee @@ -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, diff --git a/server/methods/unarchiveRoom.coffee b/server/methods/unarchiveRoom.coffee index 7b0ae819bf4b465567f1224cc4c653e67faed912..b78a843b8e598437042609c128a7d7f586494407 100644 --- a/server/methods/unarchiveRoom.coffee +++ b/server/methods/unarchiveRoom.coffee @@ -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 diff --git a/server/publications/subscription.coffee b/server/publications/subscription.coffee index dc3245e213592ee833945a8a0d45a2429f34a782..2aca0e8f1579cfcabc1dd58be47b601abe3e2ca6 100644 --- a/server/publications/subscription.coffee +++ b/server/publications/subscription.coffee @@ -13,3 +13,4 @@ Meteor.publish 'subscription', -> open: 1 alert: 1 unread: 1 + archived: 1 diff --git a/server/startup/roomPublishes.coffee b/server/startup/roomPublishes.coffee index 962fc7be3789cf9f6dd0ac45b09d5b89b675960a..e8a7ab2ad9493a7aa9c78534bef1ede169c188d8 100644 --- a/server/startup/roomPublishes.coffee +++ b/server/startup/roomPublishes.coffee @@ -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