diff --git a/.meteor/packages b/.meteor/packages index 88892cbd1466..8f9f8ca1769b 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -50,6 +50,7 @@ rocketchat:channel-settings-mail-messages rocketchat:colors rocketchat:crowd rocketchat:custom-oauth +rocketchat:custom-sounds rocketchat:emoji rocketchat:emoji-custom rocketchat:emoji-emojione diff --git a/.meteor/versions b/.meteor/versions index 4e484f78e7a1..57d4cbe96850 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -132,6 +132,7 @@ rocketchat:colors@0.0.1 rocketchat:cors@0.0.1 rocketchat:crowd@1.0.0 rocketchat:custom-oauth@1.0.0 +rocketchat:custom-sounds@1.0.0 rocketchat:emoji@1.0.0 rocketchat:emoji-custom@1.0.0 rocketchat:emoji-emojione@0.0.1 diff --git a/client/notifications/notification.js b/client/notifications/notification.js index 15cfa755f2de..a9045cdb2b1f 100644 --- a/client/notifications/notification.js +++ b/client/notifications/notification.js @@ -27,12 +27,12 @@ Meteor.startup(function() { if (RocketChat.Layout.isEmbedded()) { if (!hasFocus && messageIsInOpenedRoom) { // Play a sound and show a notification. - KonchatNotification.newMessage(); + KonchatNotification.newMessage(notification.payload.rid); KonchatNotification.showDesktop(notification); } } else if (!(hasFocus && messageIsInOpenedRoom)) { // Play a sound and show a notification. - KonchatNotification.newMessage(); + KonchatNotification.newMessage(notification.payload.rid); KonchatNotification.showDesktop(notification); } }); diff --git a/packages/rocketchat-custom-sounds/admin/adminSoundEdit.html b/packages/rocketchat-custom-sounds/admin/adminSoundEdit.html new file mode 100644 index 000000000000..c6f4ef42f9bf --- /dev/null +++ b/packages/rocketchat-custom-sounds/admin/adminSoundEdit.html @@ -0,0 +1,7 @@ + diff --git a/packages/rocketchat-custom-sounds/admin/adminSoundInfo.html b/packages/rocketchat-custom-sounds/admin/adminSoundInfo.html new file mode 100644 index 000000000000..f9676c22303a --- /dev/null +++ b/packages/rocketchat-custom-sounds/admin/adminSoundInfo.html @@ -0,0 +1,7 @@ + diff --git a/packages/rocketchat-custom-sounds/admin/adminSounds.html b/packages/rocketchat-custom-sounds/admin/adminSounds.html new file mode 100644 index 000000000000..018aab665eaa --- /dev/null +++ b/packages/rocketchat-custom-sounds/admin/adminSounds.html @@ -0,0 +1,48 @@ + diff --git a/packages/rocketchat-custom-sounds/admin/adminSounds.js b/packages/rocketchat-custom-sounds/admin/adminSounds.js new file mode 100644 index 000000000000..62386d22f6a2 --- /dev/null +++ b/packages/rocketchat-custom-sounds/admin/adminSounds.js @@ -0,0 +1,132 @@ +/* globals RocketChatTabBar */ +Template.adminSounds.helpers({ + isReady() { + if (Template.instance().ready != null) { + return Template.instance().ready.get(); + } + return undefined; + }, + customsounds() { + return Template.instance().customsounds(); + }, + isLoading() { + if (Template.instance().ready != null) { + if (!Template.instance().ready.get()) { + return 'btn-loading'; + } + } + }, + hasMore() { + if (Template.instance().limit != null) { + if (typeof Template.instance().customsounds === 'function') { + return Template.instance().limit.get() === Template.instance().customsounds().length; + } + } + return false; + }, + flexData() { + return { + tabBar: Template.instance().tabBar, + data: Template.instance().tabBarData.get() + }; + } +}); + +Template.adminSounds.onCreated(function() { + const instance = this; + this.limit = new ReactiveVar(50); + this.filter = new ReactiveVar(''); + this.ready = new ReactiveVar(false); + + this.tabBar = new RocketChatTabBar(); + this.tabBar.showGroup(FlowRouter.current().route.name); + this.tabBarData = new ReactiveVar(); + + RocketChat.TabBar.addButton({ + groups: ['custom-sounds', 'custom-sounds-selected'], + id: 'add-sound', + i18nTitle: 'Custom_Sound_Add', + icon: 'icon-plus', + template: 'adminSoundEdit', + openClick(/*e, t*/) { + instance.tabBarData.set(); + return true; + }, + order: 1 + }); + + RocketChat.TabBar.addButton({ + groups: ['custom-sounds-selected'], + id: 'admin-sound-info', + i18nTitle: 'Custom_Sound_Info', + icon: 'icon-cog', + template: 'adminSoundInfo', + order: 2 + }); + + this.autorun(function() { + const limit = (instance.limit != null) ? instance.limit.get() : 0; + const subscription = instance.subscribe('customSounds', '', limit); + instance.ready.set(subscription.ready()); + }); + + this.customsounds = function() { + const filter = (instance.filter != null) ? _.trim(instance.filter.get()) : ''; + + let query = {}; + + if (filter) { + const filterReg = new RegExp(s.escapeRegExp(filter), 'i'); + query = { name: filterReg }; + } + + const limit = (instance.limit != null) ? instance.limit.get() : 0; + + return RocketChat.models.CustomSounds.find(query, { limit: limit, sort: { name: 1 }}).fetch(); + }; +}); + +Template.adminSounds.onRendered(() => + Tracker.afterFlush(function() { + SideNav.setFlex('adminFlex'); + SideNav.openFlex(); + }) +); + +Template.adminSounds.events({ + 'keydown #sound-filter'(e) { + //stop enter key + if (e.which === 13) { + e.stopPropagation(); + e.preventDefault(); + } + }, + + 'keyup #sound-filter'(e, t) { + e.stopPropagation(); + e.preventDefault(); + t.filter.set(e.currentTarget.value); + }, + + 'click .sound-info'(e, instance) { + e.preventDefault(); + instance.tabBarData.set(RocketChat.models.CustomSounds.findOne({_id: this._id})); + instance.tabBar.showGroup('custom-sounds-selected'); + instance.tabBar.open('admin-sound-info'); + }, + + 'click .load-more'(e, t) { + e.preventDefault(); + e.stopPropagation(); + t.limit.set(t.limit.get() + 50); + }, + + 'click .icon-play-circled'(e) { + e.preventDefault(); + e.stopPropagation(); + const $audio = $('audio#' + this._id); + if ($audio && $audio[0] && $audio[0].play) { + $audio[0].play(); + } + } +}); diff --git a/packages/rocketchat-custom-sounds/admin/route.js b/packages/rocketchat-custom-sounds/admin/route.js new file mode 100644 index 000000000000..de66f932b283 --- /dev/null +++ b/packages/rocketchat-custom-sounds/admin/route.js @@ -0,0 +1,9 @@ +FlowRouter.route('/admin/custom-sounds', { + name: 'custom-sounds', + subscriptions(/*params, queryParams*/) { + this.register('customSounds', Meteor.subscribe('customSounds')); + }, + action(/*params*/) { + BlazeLayout.render('main', {center: 'adminSounds'}); + } +}); diff --git a/packages/rocketchat-custom-sounds/admin/soundEdit.html b/packages/rocketchat-custom-sounds/admin/soundEdit.html new file mode 100644 index 000000000000..4de2d2f85193 --- /dev/null +++ b/packages/rocketchat-custom-sounds/admin/soundEdit.html @@ -0,0 +1,25 @@ + diff --git a/packages/rocketchat-custom-sounds/admin/soundEdit.js b/packages/rocketchat-custom-sounds/admin/soundEdit.js new file mode 100644 index 000000000000..bda3f53e18d4 --- /dev/null +++ b/packages/rocketchat-custom-sounds/admin/soundEdit.js @@ -0,0 +1,149 @@ +import toastr from 'toastr'; + +Template.soundEdit.helpers({ + sound() { + return Template.instance().sound; + }, + + name() { + return this.name || this._id; + } +}); + +Template.soundEdit.events({ + 'click .cancel'(e, t) { + e.stopPropagation(); + e.preventDefault(); + delete Template.instance().soundFile; + t.cancel(t.find('form')); + }, + + 'submit form'(e, t) { + e.stopPropagation(); + e.preventDefault(); + t.save(e.currentTarget); + }, + + 'change input[type=file]'(ev) { + const e = (ev.originalEvent != null) ? ev.originalEvent : ev; + let files = e.target.files; + if (e.target.files == null || files.length === 0) { + if (e.dataTransfer.files != null) { + files = e.dataTransfer.files; + } else { + files = []; + } + } + + //using let x of y here seems to have incompatibility with some phones + for (const file in files) { + if (files.hasOwnProperty(file)) { + Template.instance().soundFile = files[file]; + } + } + } +}); + +Template.soundEdit.onCreated(function() { + if (this.data != null) { + this.sound = this.data.sound; + } else { + this.sound = undefined; + this.data.tabBar.showGroup('custom-sounds'); + } + + this.cancel = (form, name) => { + form.reset(); + this.data.tabBar.close(); + if (this.sound) { + this.data.back(name); + } + }; + + this.getSoundData = () => { + const soundData = {}; + if (this.sound != null) { + soundData._id = this.sound._id; + soundData.previousName = this.sound.name; + soundData.extension = this.sound.extension; + soundData.previousExtension = this.sound.extension; + } + soundData.name = s.trim(this.$('#name').val()); + soundData.newFile = false; + return soundData; + }; + + this.validate = () => { + const soundData = this.getSoundData(); + + const errors = []; + if (!soundData.name) { + errors.push('Name'); + } + + if (!soundData._id) { + if (!this.soundFile) { + errors.push('Sound_File_mp3'); + } + } + + for (const error of errors) { + toastr.error(TAPi18n.__('error-the-field-is-required', { field: TAPi18n.__(error) })); + } + + if (this.soundFile) { + if (!/audio\/mp3/.test(this.soundFile.type)) { + errors.push('FileType'); + toastr.error(TAPi18n.__('error-invalid-file-type')); + } + } + + return errors.length === 0; + }; + + this.save = (form) => { + if (this.validate()) { + const soundData = this.getSoundData(); + + if (this.soundFile) { + soundData.newFile = true; + soundData.extension = this.soundFile.name.split('.').pop(); + soundData.type = this.soundFile.type; + } + + Meteor.call('insertOrUpdateSound', soundData, (error, result) => { + if (result) { + soundData._id = result; + soundData.random = Math.round(Math.random() * 1000); + + if (this.soundFile) { + toastr.info(TAPi18n.__('Uploading_file')); + + const reader = new FileReader(); + reader.readAsBinaryString(this.soundFile); + reader.onloadend = () => { + Meteor.call('uploadCustomSound', reader.result, this.soundFile.type, soundData, (uploadError/*, data*/) => { + if (uploadError != null) { + handleError(uploadError); + console.log(uploadError); + return; + } + } + ); + delete this.soundFile; + toastr.success(TAPi18n.__('File_uploaded')); + }; + } + + toastr.success(t('Custom_Sound_Saved_Successfully')); + + this.cancel(form, soundData.name); + } + + if (error) { + handleError(error); + } + }); + } + }; +}); diff --git a/packages/rocketchat-custom-sounds/admin/soundInfo.html b/packages/rocketchat-custom-sounds/admin/soundInfo.html new file mode 100644 index 000000000000..09374f00609a --- /dev/null +++ b/packages/rocketchat-custom-sounds/admin/soundInfo.html @@ -0,0 +1,19 @@ + diff --git a/packages/rocketchat-custom-sounds/admin/soundInfo.js b/packages/rocketchat-custom-sounds/admin/soundInfo.js new file mode 100644 index 000000000000..d02d367f07e2 --- /dev/null +++ b/packages/rocketchat-custom-sounds/admin/soundInfo.js @@ -0,0 +1,112 @@ +Template.soundInfo.helpers({ + name() { + const sound = Template.instance().sound.get(); + return sound.name; + }, + + sound() { + return Template.instance().sound.get(); + }, + + editingSound() { + return Template.instance().editingSound.get(); + }, + + soundToEdit() { + const instance = Template.instance(); + return { + tabBar: instance.data.tabBar, + data: instance.data.data, + sound: instance.sound.get(), + back(name) { + instance.editingSound.set(); + + if (name != null) { + const sound = instance.sound.get(); + if (sound.name != null && sound.name !== name) { + return instance.loadedName.set(name); + } + } + } + }; + } +}); + +Template.soundInfo.events({ + 'click .delete'(e, instance) { + e.stopPropagation(); + e.preventDefault(); + const sound = instance.sound.get(); + if (sound != null) { + const _id = sound._id; + swal({ + title: t('Are_you_sure'), + text: t('Custom_Sound_Delete_Warning'), + type: 'warning', + showCancelButton: true, + confirmButtonColor: '#DD6B55', + confirmButtonText: t('Yes_delete_it'), + cancelButtonText: t('Cancel'), + closeOnConfirm: false, + html: false + }, function() { + swal.disableButtons(); + + Meteor.call('deleteCustomSound', _id, (error/*, result*/) => { + if (error) { + handleError(error); + swal.enableButtons(); + } else { + swal({ + title: t('Deleted'), + text: t('Custom_Sound_Has_Been_Deleted'), + type: 'success', + timer: 2000, + showConfirmButton: false + }); + + instance.data.tabBar.showGroup('custom-sounds'); + instance.data.tabBar.close(); + } + }); + }); + } + }, + + 'click .edit-sound'(e, instance) { + e.stopPropagation(); + e.preventDefault(); + + instance.editingSound.set(instance.sound.get()._id); + } +}); + +Template.soundInfo.onCreated(function() { + this.sound = new ReactiveVar(); + + this.editingSound = new ReactiveVar(); + + this.loadedName = new ReactiveVar(); + + this.autorun(() => { + const data = Template.currentData(); + if (data.clear != null) { + this.clear = data.clear; + } + }); + + this.autorun(() => { + const data = Template.currentData(); + const sound = this.sound.get(); + if (sound.name != null) { + this.loadedName.set(sound.name); + } else if (data.name != null) { + this.loadedName.set(data.name); + } + }); + + this.autorun(() => { + const data = Template.currentData(); + this.sound.set(data); + }); +}); diff --git a/packages/rocketchat-custom-sounds/admin/startup.js b/packages/rocketchat-custom-sounds/admin/startup.js new file mode 100644 index 000000000000..d5d5fed7d3c5 --- /dev/null +++ b/packages/rocketchat-custom-sounds/admin/startup.js @@ -0,0 +1,7 @@ +RocketChat.AdminBox.addOption({ + href: 'custom-sounds', + i18nLabel: 'Custom_Sounds', + permissionGranted() { + return RocketChat.authz.hasAtLeastOnePermission(['manage-sounds']); + } +}); diff --git a/packages/rocketchat-custom-sounds/assets/stylesheets/customSoundsAdmin.less b/packages/rocketchat-custom-sounds/assets/stylesheets/customSoundsAdmin.less new file mode 100644 index 000000000000..4845388adc8a --- /dev/null +++ b/packages/rocketchat-custom-sounds/assets/stylesheets/customSoundsAdmin.less @@ -0,0 +1,102 @@ +.sound-info { + .icon-play-circled { + cursor: pointer; + } +} + +.sound-view { + z-index: 15; + overflow-y: auto; + overflow-x: hidden; + + .thumb { + width: 100%; + height: 350px; + padding: 20px; + } + + nav { + padding: 0 20px; + } + + .info { + white-space: normal; + padding: 0 20px; + + h3 { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + font-size: 24px; + margin: 8px 0; + line-height: 27px; + text-overflow: ellipsis; + width: 100%; + overflow: hidden; + white-space: nowrap; + + i::after { + content: " "; + display: inline-block; + width: 8px; + height: 8px; + border-radius: 4px; + vertical-align: middle; + } + } + + p { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + line-height: 18px; + font-size: 12px; + font-weight: 300; + } + } + + .edit-form { + padding: 20px 20px 0; + white-space: normal; + + h3 { + font-size: 24px; + margin-bottom: 8px; + line-height: 22px; + } + + p { + line-height: 18px; + font-size: 12px; + font-weight: 300; + } + + > .input-line { + margin-top: 20px; + } + + nav { + padding: 0; + + &.buttons { + margin-top: 2em; + } + } + + .form-divisor { + text-align: center; + margin: 2em 0; + height: 9px; + + > span { + padding: 0 1em; + } + } + } + + .room-info-content > div { + margin: 0 0 20px; + } +} diff --git a/packages/rocketchat-custom-sounds/client/lib/CustomSounds.js b/packages/rocketchat-custom-sounds/client/lib/CustomSounds.js new file mode 100644 index 000000000000..b977cd08f708 --- /dev/null +++ b/packages/rocketchat-custom-sounds/client/lib/CustomSounds.js @@ -0,0 +1,64 @@ +class CustomSounds { + constructor() { + this.list = new ReactiveVar({}); + this.add({ _id: 'beep', name: 'Beep', extension: 'mp3', src: '/sounds/beep.mp3' }); + this.add({ _id: 'chelle', name: 'Chelle', extension: 'mp3', src: '/sounds/chelle.mp3' }); + this.add({ _id: 'ding', name: 'Ding', extension: 'mp3', src: '/sounds/ding.mp3' }); + this.add({ _id: 'droplet', name: 'Droplet', extension: 'mp3', src: '/sounds/droplet.mp3' }); + this.add({ _id: 'highbell', name: 'Highbell', extension: 'mp3', src: '/sounds/highbell.mp3' }); + this.add({ _id: 'seasons', name: 'Seasons', extension: 'mp3', src: '/sounds/seasons.mp3' }); + } + + add(sound) { + if (!sound.src) { + sound.src = this.getURL(sound); + } + const audio = $('