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 @@
+
+
+
+ {{> soundEdit .}}
+
+
+
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 @@
+
+
+
+ {{> soundInfo .}}
+
+
+
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 @@
+
+
+
+
+ {{> burger}}
+
+ {{_ "Custom_Sounds"}}
+
+
+
+ {{#requiresPermission 'manage-sounds'}}
+
+
+ {{{_ "Showing_results" customsounds.length}}}
+
+
+
+
+
+ {{_ "Name"}} |
+
+
+
+ {{#each customsounds}}
+
+ {{name}} |
+
+ {{/each}}
+
+
+ {{#if hasMore}}
+
+ {{/if}}
+
+ {{/requiresPermission}}
+
+
+ {{#with flexData}}
+ {{> flexTabBar}}
+ {{/with}}
+
+
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 @@
+
+ {{#requiresPermission 'manage-sounds'}}
+
+ {{/requiresPermission}}
+
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 @@
+
+ {{#if editingSound}}
+ {{> soundEdit (soundToEdit)}}
+ {{else}}
+ {{#with sound}}
+
+ {{/with}}
+
+ {{/if}}
+
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 = $('', { id: sound._id, preload: true }).append(
+ $('', { src: sound.src })
+ );
+ const list = this.list.get();
+ list[sound._id] = sound;
+ this.list.set(list);
+ $('body').append(audio);
+ }
+
+ remove(sound) {
+ const list = this.list.get();
+ delete list[sound._id];
+ this.list.set(list);
+ $('#' + sound._id).remove();
+ }
+
+ update(sound) {
+ const audio = $(`#${sound._id}`);
+ if (audio && audio[0]) {
+ const list = this.list.get();
+ list[sound._id] = sound;
+ this.list.set(list);
+ $('source', audio).attr('src', this.getURL(sound));
+ audio[0].load();
+ } else {
+ this.add(sound);
+ }
+ }
+
+ getURL(sound) {
+ const path = (Meteor.isCordova) ? Meteor.absoluteUrl().replace(/\/$/, '') : __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || '';
+ return `${path}/custom-sounds/${sound._id}.${sound.extension}?_dc=${sound.random || 0}`;
+ }
+
+ getList() {
+ const list = Object.values(this.list.get());
+ return _.sortBy(list, 'name');
+ }
+}
+
+RocketChat.CustomSounds = new CustomSounds;
+
+Meteor.startup(() =>
+ Meteor.call('listCustomSounds', (error, result) => {
+ for (const sound of result) {
+ RocketChat.CustomSounds.add(sound);
+ }
+ })
+);
diff --git a/packages/rocketchat-custom-sounds/client/models/CustomSounds.js b/packages/rocketchat-custom-sounds/client/models/CustomSounds.js
new file mode 100644
index 000000000000..59c847dd1b79
--- /dev/null
+++ b/packages/rocketchat-custom-sounds/client/models/CustomSounds.js
@@ -0,0 +1,8 @@
+class CustomSounds extends RocketChat.models._Base {
+ constructor() {
+ super();
+ this._initModel('custom_sounds');
+ }
+}
+
+RocketChat.models.CustomSounds = new CustomSounds();
diff --git a/packages/rocketchat-custom-sounds/client/notifications/deleteCustomSound.js b/packages/rocketchat-custom-sounds/client/notifications/deleteCustomSound.js
new file mode 100644
index 000000000000..81323dabc760
--- /dev/null
+++ b/packages/rocketchat-custom-sounds/client/notifications/deleteCustomSound.js
@@ -0,0 +1,3 @@
+Meteor.startup(() =>
+ RocketChat.Notifications.onAll('deleteCustomSound', data => RocketChat.CustomSounds.remove(data.soundData))
+);
diff --git a/packages/rocketchat-custom-sounds/client/notifications/updateCustomSound.js b/packages/rocketchat-custom-sounds/client/notifications/updateCustomSound.js
new file mode 100644
index 000000000000..73b16ae4aa96
--- /dev/null
+++ b/packages/rocketchat-custom-sounds/client/notifications/updateCustomSound.js
@@ -0,0 +1,3 @@
+Meteor.startup(() =>
+ RocketChat.Notifications.onAll('updateCustomSound', data => RocketChat.CustomSounds.update(data.soundData))
+);
diff --git a/packages/rocketchat-custom-sounds/package.js b/packages/rocketchat-custom-sounds/package.js
new file mode 100644
index 000000000000..7931efc269af
--- /dev/null
+++ b/packages/rocketchat-custom-sounds/package.js
@@ -0,0 +1,53 @@
+Package.describe({
+ name: 'rocketchat:custom-sounds',
+ version: '1.0.0',
+ summary: 'Custom sounds',
+ git: ''
+});
+
+Package.onUse(function(api) {
+ api.use([
+ 'ecmascript',
+ 'less',
+ 'rocketchat:file',
+ 'rocketchat:lib',
+ 'templating',
+ 'reactive-var',
+ 'underscore',
+ 'webapp'
+ ]);
+
+ api.use('kadira:flow-router', 'client');
+
+ api.addFiles('server/startup/custom-sounds.js', 'server');
+ api.addFiles('server/startup/permissions.js', 'server');
+ api.addFiles('server/startup/settings.js', 'server');
+
+ api.addFiles('server/models/CustomSounds.js', 'server');
+ api.addFiles('server/publications/customSounds.js', 'server');
+
+ api.addFiles([
+ 'server/methods/deleteCustomSound.js',
+ 'server/methods/insertOrUpdateSound.js',
+ 'server/methods/listCustomSounds.js',
+ 'server/methods/uploadCustomSound.js'
+ ], 'server');
+
+ api.addFiles('assets/stylesheets/customSoundsAdmin.less', 'client');
+
+ api.addFiles('admin/startup.js', 'client');
+ api.addFiles('admin/adminSounds.html', 'client');
+ api.addFiles('admin/adminSounds.js', 'client');
+ api.addFiles('admin/adminSoundEdit.html', 'client');
+ api.addFiles('admin/adminSoundInfo.html', 'client');
+ api.addFiles('admin/soundEdit.html', 'client');
+ api.addFiles('admin/soundEdit.js', 'client');
+ api.addFiles('admin/soundInfo.html', 'client');
+ api.addFiles('admin/soundInfo.js', 'client');
+ api.addFiles('admin/route.js', 'client');
+
+ api.addFiles('client/lib/CustomSounds.js', 'client');
+ api.addFiles('client/models/CustomSounds.js', 'client');
+ api.addFiles('client/notifications/updateCustomSound.js', 'client');
+ api.addFiles('client/notifications/deleteCustomSound.js', 'client');
+});
diff --git a/packages/rocketchat-custom-sounds/server/methods/deleteCustomSound.js b/packages/rocketchat-custom-sounds/server/methods/deleteCustomSound.js
new file mode 100644
index 000000000000..82d01933e02b
--- /dev/null
+++ b/packages/rocketchat-custom-sounds/server/methods/deleteCustomSound.js
@@ -0,0 +1,22 @@
+/* globals RocketChatFileCustomSoundsInstance */
+Meteor.methods({
+ deleteCustomSound(_id) {
+ let sound = null;
+
+ if (RocketChat.authz.hasPermission(this.userId, 'manage-sounds')) {
+ sound = RocketChat.models.CustomSounds.findOneByID(_id);
+ } else {
+ throw new Meteor.Error('not_authorized');
+ }
+
+ if (sound == null) {
+ throw new Meteor.Error('Custom_Sound_Error_Invalid_Sound', 'Invalid sound', { method: 'deleteCustomSound' });
+ }
+
+ RocketChatFileCustomSoundsInstance.deleteFile(`${sound._id}.${sound.extension}`);
+ RocketChat.models.CustomSounds.removeByID(_id);
+ RocketChat.Notifications.notifyAll('deleteCustomSound', {soundData: sound});
+
+ return true;
+ }
+});
diff --git a/packages/rocketchat-custom-sounds/server/methods/insertOrUpdateSound.js b/packages/rocketchat-custom-sounds/server/methods/insertOrUpdateSound.js
new file mode 100644
index 000000000000..c0dc2b72009f
--- /dev/null
+++ b/packages/rocketchat-custom-sounds/server/methods/insertOrUpdateSound.js
@@ -0,0 +1,62 @@
+/* globals RocketChatFileCustomSoundsInstance */
+Meteor.methods({
+ insertOrUpdateSound(soundData) {
+ if (!RocketChat.authz.hasPermission(this.userId, 'manage-sounds')) {
+ throw new Meteor.Error('not_authorized');
+ }
+
+ if (!s.trim(soundData.name)) {
+ throw new Meteor.Error('error-the-field-is-required', 'The field Name is required', { method: 'insertOrUpdateSound', field: 'Name' });
+ }
+
+ //let nameValidation = new RegExp('^[0-9a-zA-Z-_+;.]+$');
+
+ //allow all characters except colon, whitespace, comma, >, <, &, ", ', /, \, (, )
+ //more practical than allowing specific sets of characters; also allows foreign languages
+ const nameValidation = /[\s,:><&"'\/\\\(\)]/;
+
+ //silently strip colon; this allows for uploading :soundname: as soundname
+ soundData.name = soundData.name.replace(/:/g, '');
+
+ if (nameValidation.test(soundData.name)) {
+ throw new Meteor.Error('error-input-is-not-a-valid-field', `${soundData.name} is not a valid name`, { method: 'insertOrUpdateSound', input: soundData.name, field: 'Name' });
+ }
+
+ let matchingResults = [];
+
+ if (soundData._id) {
+ matchingResults = RocketChat.models.CustomSounds.findByNameExceptID(soundData.name, soundData._id).fetch();
+ } else {
+ matchingResults = RocketChat.models.CustomSounds.findByName(soundData.name).fetch();
+ }
+
+ if (matchingResults.length > 0) {
+ throw new Meteor.Error('Custom_Sound_Error_Name_Already_In_Use', 'The custom sound name is already in use', { method: 'insertOrUpdateSound' });
+ }
+
+ if (!soundData._id) {
+ //insert sound
+ const createSound = {
+ name: soundData.name,
+ extension: soundData.extension
+ };
+
+ const _id = RocketChat.models.CustomSounds.create(createSound);
+ createSound._id = _id;
+
+ return _id;
+ } else {
+ //update sound
+ if (soundData.newFile) {
+ RocketChatFileCustomSoundsInstance.deleteFile(`${soundData._id}.${soundData.previousExtension}`);
+ }
+
+ if (soundData.name !== soundData.previousName) {
+ RocketChat.models.CustomSounds.setName(soundData._id, soundData.name);
+ RocketChat.Notifications.notifyAll('updateCustomSound', {soundData});
+ }
+
+ return soundData._id;
+ }
+ }
+});
diff --git a/packages/rocketchat-custom-sounds/server/methods/listCustomSounds.js b/packages/rocketchat-custom-sounds/server/methods/listCustomSounds.js
new file mode 100644
index 000000000000..e16afd389f97
--- /dev/null
+++ b/packages/rocketchat-custom-sounds/server/methods/listCustomSounds.js
@@ -0,0 +1,5 @@
+Meteor.methods({
+ listCustomSounds() {
+ return RocketChat.models.CustomSounds.find({}).fetch();
+ }
+});
diff --git a/packages/rocketchat-custom-sounds/server/methods/uploadCustomSound.js b/packages/rocketchat-custom-sounds/server/methods/uploadCustomSound.js
new file mode 100644
index 000000000000..9dabf715edcf
--- /dev/null
+++ b/packages/rocketchat-custom-sounds/server/methods/uploadCustomSound.js
@@ -0,0 +1,20 @@
+/* globals RocketChatFileCustomSoundsInstance */
+Meteor.methods({
+ uploadCustomSound(binaryContent, contentType, soundData) {
+ if (!RocketChat.authz.hasPermission(this.userId, 'manage-sounds')) {
+ throw new Meteor.Error('not_authorized');
+ }
+
+ const file = new Buffer(binaryContent, 'binary');
+
+ const rs = RocketChatFile.bufferToStream(file);
+ RocketChatFileCustomSoundsInstance.deleteFile(`${soundData._id}.${soundData.extension}`);
+ const ws = RocketChatFileCustomSoundsInstance.createWriteStream(`${soundData._id}.${soundData.extension}`, contentType);
+ ws.on('end', Meteor.bindEnvironment(() =>
+ Meteor.setTimeout(() => RocketChat.Notifications.notifyAll('updateCustomSound', {soundData})
+ , 500)
+ ));
+
+ rs.pipe(ws);
+ }
+});
diff --git a/packages/rocketchat-custom-sounds/server/models/CustomSounds.js b/packages/rocketchat-custom-sounds/server/models/CustomSounds.js
new file mode 100644
index 000000000000..25e7bf27b4bc
--- /dev/null
+++ b/packages/rocketchat-custom-sounds/server/models/CustomSounds.js
@@ -0,0 +1,54 @@
+class CustomSounds extends RocketChat.models._Base {
+ constructor() {
+ super('custom_sounds');
+
+ this.tryEnsureIndex({ 'name': 1 });
+ }
+
+ //find one
+ findOneByID(_id, options) {
+ return this.findOne(_id, options);
+ }
+
+ //find
+ findByName(name, options) {
+ const query = {
+ name
+ };
+
+ return this.find(query, options);
+ }
+
+ findByNameExceptID(name, except, options) {
+ const query = {
+ _id: { $nin: [ except ] },
+ name
+ };
+
+ return this.find(query, options);
+ }
+
+ //update
+ setName(_id, name) {
+ const update = {
+ $set: {
+ name
+ }
+ };
+
+ return this.update({_id}, update);
+ }
+
+ // INSERT
+ create(data) {
+ return this.insert(data);
+ }
+
+
+ // REMOVE
+ removeByID(_id) {
+ return this.remove(_id);
+ }
+}
+
+RocketChat.models.CustomSounds = new CustomSounds();
diff --git a/packages/rocketchat-custom-sounds/server/publications/customSounds.js b/packages/rocketchat-custom-sounds/server/publications/customSounds.js
new file mode 100644
index 000000000000..1ecefc930c72
--- /dev/null
+++ b/packages/rocketchat-custom-sounds/server/publications/customSounds.js
@@ -0,0 +1,25 @@
+Meteor.publish('customSounds', function(filter, limit) {
+ if (!this.userId) {
+ return this.ready();
+ }
+
+ const fields = {
+ name: 1,
+ extension: 1
+ };
+
+ filter = s.trim(filter);
+
+ const options = {
+ fields,
+ limit,
+ sort: { name: 1 }
+ };
+
+ if (filter) {
+ const filterReg = new RegExp(s.escapeRegExp(filter), 'i');
+ return RocketChat.models.CustomSounds.findByName(filterReg, options);
+ }
+
+ return RocketChat.models.CustomSounds.find({}, options);
+});
diff --git a/packages/rocketchat-custom-sounds/server/startup/custom-sounds.js b/packages/rocketchat-custom-sounds/server/startup/custom-sounds.js
new file mode 100644
index 000000000000..2cc290603f6b
--- /dev/null
+++ b/packages/rocketchat-custom-sounds/server/startup/custom-sounds.js
@@ -0,0 +1,76 @@
+/* globals RocketChatFileCustomSoundsInstance */
+Meteor.startup(function() {
+ let storeType = 'GridFS';
+
+ if (RocketChat.settings.get('CustomSounds_Storage_Type')) {
+ storeType = RocketChat.settings.get('CustomSounds_Storage_Type');
+ }
+
+ const RocketChatStore = RocketChatFile[storeType];
+
+ if (RocketChatStore == null) {
+ throw new Error(`Invalid RocketChatStore type [${storeType}]`);
+ }
+
+ console.log(`Using ${storeType} for custom sounds storage`.green);
+
+ let path = '~/uploads';
+ if (RocketChat.settings.get('CustomSounds_FileSystemPath') != null) {
+ if (RocketChat.settings.get('CustomSounds_FileSystemPath').trim() !== '') {
+ path = RocketChat.settings.get('CustomSounds_FileSystemPath');
+ }
+ }
+
+ this.RocketChatFileCustomSoundsInstance = new RocketChatStore({
+ name: 'custom_sounds',
+ absolutePath: path
+ });
+
+ self = this;
+
+ return WebApp.connectHandlers.use('/custom-sounds/', Meteor.bindEnvironment(function(req, res/*, next*/) {
+ const params =
+ { sound: decodeURIComponent(req.url.replace(/^\//, '').replace(/\?.*$/, '')) };
+
+ if (_.isEmpty(params.sound)) {
+ res.writeHead(403);
+ res.write('Forbidden');
+ res.end();
+ return;
+ }
+
+ const file = RocketChatFileCustomSoundsInstance.getFileWithReadStream(params.sound);
+ if (!file) {
+ return;
+ }
+
+ res.setHeader('Content-Disposition', 'inline');
+
+ let fileUploadDate = undefined;
+ if (file.uploadDate != null) {
+ fileUploadDate = file.uploadDate.toUTCString();
+ }
+
+ const reqModifiedHeader = req.headers['if-modified-since'];
+ if (reqModifiedHeader != null) {
+ if (reqModifiedHeader === fileUploadDate) {
+ res.setHeader('Last-Modified', reqModifiedHeader);
+ res.writeHead(304);
+ res.end();
+ return;
+ }
+ }
+
+ res.setHeader('Cache-Control', 'public, max-age=0');
+ res.setHeader('Expires', '-1');
+ if (fileUploadDate != null) {
+ res.setHeader('Last-Modified', fileUploadDate);
+ } else {
+ res.setHeader('Last-Modified', new Date().toUTCString());
+ }
+ res.setHeader('Content-Type', 'audio/mpeg');
+ res.setHeader('Content-Length', file.length);
+
+ file.readStream.pipe(res);
+ }));
+});
diff --git a/packages/rocketchat-custom-sounds/server/startup/permissions.js b/packages/rocketchat-custom-sounds/server/startup/permissions.js
new file mode 100644
index 000000000000..c86644103c97
--- /dev/null
+++ b/packages/rocketchat-custom-sounds/server/startup/permissions.js
@@ -0,0 +1,5 @@
+Meteor.startup(() => {
+ if (RocketChat.models && RocketChat.models.Permissions) {
+ RocketChat.models.Permissions.createOrUpdate('manage-sounds', ['admin']);
+ }
+});
diff --git a/packages/rocketchat-custom-sounds/server/startup/settings.js b/packages/rocketchat-custom-sounds/server/startup/settings.js
new file mode 100644
index 000000000000..78acd4c18768
--- /dev/null
+++ b/packages/rocketchat-custom-sounds/server/startup/settings.js
@@ -0,0 +1,22 @@
+RocketChat.settings.addGroup('CustomSoundsFilesystem', function() {
+ this.add('CustomSounds_Storage_Type', 'GridFS', {
+ type: 'select',
+ values: [{
+ key: 'GridFS',
+ i18nLabel: 'GridFS'
+ }, {
+ key: 'FileSystem',
+ i18nLabel: 'FileSystem'
+ }],
+ i18nLabel: 'FileUpload_Storage_Type'
+ });
+
+ this.add('CustomSounds_FileSystemPath', '', {
+ type: 'string',
+ enableQuery: {
+ _id: 'CustomSounds_Storage_Type',
+ value: 'FileSystem'
+ },
+ i18nLabel: 'FileUpload_FileSystemPath'
+ });
+});
diff --git a/packages/rocketchat-emoji-custom/admin/adminEmoji.js b/packages/rocketchat-emoji-custom/admin/adminEmoji.js
index 29a437c3bd89..a1ddce4c9c58 100644
--- a/packages/rocketchat-emoji-custom/admin/adminEmoji.js
+++ b/packages/rocketchat-emoji-custom/admin/adminEmoji.js
@@ -1,7 +1,7 @@
-/* globals isSetNotNull, RocketChatTabBar */
+/* globals RocketChatTabBar */
Template.adminEmoji.helpers({
isReady() {
- if (isSetNotNull(() => Template.instance().ready)) {
+ if (Template.instance().ready != null) {
return Template.instance().ready.get();
}
return undefined;
@@ -10,14 +10,14 @@ Template.adminEmoji.helpers({
return Template.instance().customemoji();
},
isLoading() {
- if (isSetNotNull(() => Template.instance().ready)) {
+ if (Template.instance().ready != null) {
if (!Template.instance().ready.get()) {
return 'btn-loading';
}
}
},
hasMore() {
- if (isSetNotNull(() => Template.instance().limit)) {
+ if (Template.instance().limit != null) {
if (typeof Template.instance().customemoji === 'function') {
return Template.instance().limit.get() === Template.instance().customemoji().length;
}
@@ -61,13 +61,13 @@ Template.adminEmoji.onCreated(function() {
});
this.autorun(function() {
- const limit = (isSetNotNull(() => instance.limit))? instance.limit.get() : 0;
+ const limit = (instance.limit != null) ? instance.limit.get() : 0;
const subscription = instance.subscribe('fullEmojiData', '', limit);
instance.ready.set(subscription.ready());
});
this.customemoji = function() {
- const filter = (isSetNotNull(() => instance.filter))? _.trim(instance.filter.get()) : '';
+ const filter = (instance.filter != null) ? _.trim(instance.filter.get()) : '';
let query = {};
@@ -76,7 +76,7 @@ Template.adminEmoji.onCreated(function() {
query = { $or: [ { name: filterReg }, {aliases: filterReg } ] };
}
- const limit = (isSetNotNull(() => instance.limit))? instance.limit.get() : 0;
+ const limit = (instance.limit != null) ? instance.limit.get() : 0;
return RocketChat.models.EmojiCustom.find(query, { limit: limit, sort: { name: 1 }}).fetch();
};
@@ -90,7 +90,7 @@ Template.adminEmoji.onRendered(() =>
);
Template.adminEmoji.events({
- ['keydown #emoji-filter'](e) {
+ 'keydown #emoji-filter'(e) {
//stop enter key
if (e.which === 13) {
e.stopPropagation();
@@ -98,19 +98,19 @@ Template.adminEmoji.events({
}
},
- ['keyup #emoji-filter'](e, t) {
+ 'keyup #emoji-filter'(e, t) {
e.stopPropagation();
e.preventDefault();
t.filter.set(e.currentTarget.value);
},
- ['click .emoji-info'](e, instance) {
+ 'click .emoji-info'(e, instance) {
e.preventDefault();
instance.tabBarData.set(RocketChat.models.EmojiCustom.findOne({_id: this._id}));
instance.tabBar.open('admin-emoji-info');
},
- ['click .load-more'](e, t) {
+ 'click .load-more'(e, t) {
e.preventDefault();
e.stopPropagation();
t.limit.set(t.limit.get() + 50);
diff --git a/packages/rocketchat-emoji-custom/admin/emojiEdit.js b/packages/rocketchat-emoji-custom/admin/emojiEdit.js
index 1ac31ba7d097..22177814881b 100644
--- a/packages/rocketchat-emoji-custom/admin/emojiEdit.js
+++ b/packages/rocketchat-emoji-custom/admin/emojiEdit.js
@@ -1,5 +1,5 @@
import toastr from 'toastr';
-/* globals isSetNotNull */
+
Template.emojiEdit.helpers({
emoji() {
return Template.instance().emoji;
@@ -11,24 +11,24 @@ Template.emojiEdit.helpers({
});
Template.emojiEdit.events({
- ['click .cancel'](e, t) {
+ 'click .cancel'(e, t) {
e.stopPropagation();
e.preventDefault();
delete Template.instance().emojiFile;
t.cancel(t.find('form'));
},
- ['submit form'](e, t) {
+ 'submit form'(e, t) {
e.stopPropagation();
e.preventDefault();
t.save(e.currentTarget);
},
- ['change input[type=file]'](ev) {
- const e = (isSetNotNull(() => ev.originalEvent)) ? ev.originalEvent : ev;
+ 'change input[type=file]'(ev) {
+ const e = ev.originalEvent != null ? ev.originalEvent : ev;
let files = e.target.files;
- if (!isSetNotNull(() => e.target.files) || files.length === 0) {
- if (isSetNotNull(() => e.dataTransfer.files)) {
+ if (files == null || files.length === 0) {
+ if (e.dataTransfer != null && e.dataTransfer.files != null) {
files = e.dataTransfer.files;
} else {
files = [];
@@ -45,7 +45,7 @@ Template.emojiEdit.events({
});
Template.emojiEdit.onCreated(function() {
- if (isSetNotNull(() => this.data)) {
+ if (this.data != null) {
this.emoji = this.data.emoji;
} else {
this.emoji = undefined;
@@ -63,7 +63,7 @@ Template.emojiEdit.onCreated(function() {
this.getEmojiData = () => {
const emojiData = {};
- if (isSetNotNull(() => this.emoji)) {
+ if (this.emoji != null) {
emojiData._id = this.emoji._id;
emojiData.previousName = this.emoji.name;
emojiData.extension = this.emoji.extension;
diff --git a/packages/rocketchat-emoji-custom/admin/emojiInfo.js b/packages/rocketchat-emoji-custom/admin/emojiInfo.js
index 97f497f883ce..f839c38312dd 100644
--- a/packages/rocketchat-emoji-custom/admin/emojiInfo.js
+++ b/packages/rocketchat-emoji-custom/admin/emojiInfo.js
@@ -1,4 +1,3 @@
-/* globals isSetNotNull */
Template.emojiInfo.helpers({
name() {
const emoji = Template.instance().emoji.get();
@@ -26,9 +25,9 @@ Template.emojiInfo.helpers({
back(name) {
instance.editingEmoji.set();
- if (isSetNotNull(() => name)) {
+ if (name != null) {
const emoji = instance.emoji.get();
- if (isSetNotNull(() => emoji.name) && emoji.name !== name) {
+ if (emoji != null && emoji.name != null && emoji.name !== name) {
return instance.loadedName.set(name);
}
}
@@ -38,15 +37,15 @@ Template.emojiInfo.helpers({
});
Template.emojiInfo.events({
- ['click .thumb'](e) {
+ 'click .thumb'(e) {
$(e.currentTarget).toggleClass('bigger');
},
- ['click .delete'](e, instance) {
+ 'click .delete'(e, instance) {
e.stopPropagation();
e.preventDefault();
const emoji = instance.emoji.get();
- if (isSetNotNull(() => emoji)) {
+ if (emoji != null) {
const _id = emoji._id;
swal({
title: t('Are_you_sure'),
@@ -81,7 +80,7 @@ Template.emojiInfo.events({
}
},
- ['click .edit-emoji'](e, instance) {
+ 'click .edit-emoji'(e, instance) {
e.stopPropagation();
e.preventDefault();
@@ -100,7 +99,7 @@ Template.emojiInfo.onCreated(function() {
this.autorun(() => {
const data = Template.currentData();
- if (isSetNotNull(() => data.clear)) {
+ if (data != null && data.clear != null) {
this.clear = data.clear;
}
});
@@ -108,9 +107,9 @@ Template.emojiInfo.onCreated(function() {
this.autorun(() => {
const data = Template.currentData();
const emoji = this.emoji.get();
- if (isSetNotNull(() => emoji.name)) {
+ if (emoji != null && emoji.name != null) {
this.loadedName.set(emoji.name);
- } else if (isSetNotNull(() => data.name)) {
+ } else if (data != null && data.name != null) {
this.loadedName.set(data.name);
}
});
diff --git a/packages/rocketchat-emoji-custom/server/methods/deleteEmojiCustom.js b/packages/rocketchat-emoji-custom/server/methods/deleteEmojiCustom.js
index bcfebb41dd71..09269cac0ebf 100644
--- a/packages/rocketchat-emoji-custom/server/methods/deleteEmojiCustom.js
+++ b/packages/rocketchat-emoji-custom/server/methods/deleteEmojiCustom.js
@@ -1,4 +1,4 @@
-/* globals isSetNotNull, RocketChatFileEmojiCustomInstance */
+/* globals RocketChatFileEmojiCustomInstance */
Meteor.methods({
deleteEmojiCustom(emojiID) {
let emoji = null;
@@ -9,7 +9,7 @@ Meteor.methods({
throw new Meteor.Error('not_authorized');
}
- if (!isSetNotNull(() => emoji)) {
+ if (emoji == null) {
throw new Meteor.Error('Custom_Emoji_Error_Invalid_Emoji', 'Invalid emoji', { method: 'deleteEmojiCustom' });
}
diff --git a/packages/rocketchat-emoji-custom/server/startup/emoji-custom.js b/packages/rocketchat-emoji-custom/server/startup/emoji-custom.js
index 6003a31f9549..a69e748c3acd 100644
--- a/packages/rocketchat-emoji-custom/server/startup/emoji-custom.js
+++ b/packages/rocketchat-emoji-custom/server/startup/emoji-custom.js
@@ -1,4 +1,4 @@
-/* globals isSetNotNull, RocketChatFileEmojiCustomInstance */
+/* globals RocketChatFileEmojiCustomInstance */
Meteor.startup(function() {
let storeType = 'GridFS';
@@ -8,14 +8,14 @@ Meteor.startup(function() {
const RocketChatStore = RocketChatFile[storeType];
- if (!isSetNotNull(() => RocketChatStore)) {
+ if (RocketChatStore == null) {
throw new Error(`Invalid RocketChatStore type [${storeType}]`);
}
console.log(`Using ${storeType} for custom emoji storage`.green);
let path = '~/uploads';
- if (isSetNotNull(() => RocketChat.settings.get('EmojiUpload_FileSystemPath'))) {
+ if (RocketChat.settings.get('EmojiUpload_FileSystemPath') != null) {
if (RocketChat.settings.get('EmojiUpload_FileSystemPath').trim() !== '') {
path = RocketChat.settings.get('EmojiUpload_FileSystemPath');
}
@@ -41,7 +41,7 @@ Meteor.startup(function() {
res.setHeader('Content-Disposition', 'inline');
- if (!isSetNotNull(() => file)) {
+ if (file == null) {
//use code from username initials renderer until file upload is complete
res.setHeader('Content-Type', 'image/svg+xml');
res.setHeader('Cache-Control', 'public, max-age=0');
@@ -73,12 +73,12 @@ Meteor.startup(function() {
}
let fileUploadDate = undefined;
- if (isSetNotNull(() => file.uploadDate)) {
+ if (file.uploadDate != null) {
fileUploadDate = file.uploadDate.toUTCString();
}
const reqModifiedHeader = req.headers['if-modified-since'];
- if (isSetNotNull(() => reqModifiedHeader)) {
+ if (reqModifiedHeader != null) {
if (reqModifiedHeader === fileUploadDate) {
res.setHeader('Last-Modified', reqModifiedHeader);
res.writeHead(304);
@@ -89,7 +89,7 @@ Meteor.startup(function() {
res.setHeader('Cache-Control', 'public, max-age=0');
res.setHeader('Expires', '-1');
- if (isSetNotNull(() => fileUploadDate)) {
+ if (fileUploadDate != null) {
res.setHeader('Last-Modified', fileUploadDate);
} else {
res.setHeader('Last-Modified', new Date().toUTCString());
diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json
index 52386d532aa7..25f103e89097 100644
--- a/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -342,8 +342,17 @@
"Custom_oauth_unique_name": "Custom oauth unique name",
"Custom_Script_Logged_In": "Custom Script for logged in users",
"Custom_Script_Logged_Out": "Custom Script for logged out users",
+ "Custom_Sounds": "Custom Sounds",
+ "Custom_Sound_Add": "Add Custom Sound",
+ "Custom_Sound_Delete_Warning": "Deleting a sound cannot be undone.",
+ "Custom_Sound_Error_Invalid_Sound": "Invalid sound",
+ "Custom_Sound_Error_Name_Already_In_Use": "The custom sound name is already in use.",
+ "Custom_Sound_Has_Been_Deleted": "The custom sound has been deleted.",
+ "Custom_Sound_Info": "Custom Sound Info",
+ "Custom_Sound_Saved_Successfully": "Custom sound saved successfully",
"Custom_Translations": "Custom Translations",
"Custom_Translations_Description": "Should be a valid JSON where keys are languages containing a dictionary of key and translations. Example:{\n\t\"en\": {\n\t\t\"key\": \"translation\"\n\t},\n\t\"pt\": {\n\t\t\"key\": \"tradução\"\n\t}\n}
",
+ "CustomSoundsFilesystem": "Custom Sounds Filesystem",
"Dashboard": "Dashboard",
"Date": "Date",
"Date_From": "From",
@@ -518,6 +527,7 @@
"File_exceeds_allowed_size_of_bytes": "File exceeds allowed size of __size__.",
"File_not_allowed_direct_messages": "File sharing not allowed in direct messages.",
"File_type_is_not_accepted": "File type is not accepted.",
+ "File_uploaded": "File uploaded",
"FileUpload": "File Upload",
"FileUpload_Enabled": "File Uploads Enabled",
"FileUpload_Disabled": "File uploads are disabled.",
@@ -1270,6 +1280,7 @@
"Snippet_Messages": "Snippet Messages",
"Snippeted_a_message": "Created a snippet __snippetLink__",
"Sound": "Sound",
+ "Sound_File_mp3": "Sound File (mp3)",
"SSL": "SSL",
"Star_Message": "Star Message",
"Starred_Messages": "Starred Messages",
diff --git a/packages/rocketchat-lib/server/models/Subscriptions.coffee b/packages/rocketchat-lib/server/models/Subscriptions.coffee
index c2376b069273..f0c9f6ba36f8 100644
--- a/packages/rocketchat-lib/server/models/Subscriptions.coffee
+++ b/packages/rocketchat-lib/server/models/Subscriptions.coffee
@@ -12,6 +12,7 @@ class ModelSubscriptions extends RocketChat.models._Base
@tryEnsureIndex { 'unread': 1 }
@tryEnsureIndex { 'ts': 1 }
@tryEnsureIndex { 'ls': 1 }
+ @tryEnsureIndex { 'audioNotification': 1 }, { sparse: 1 }
@tryEnsureIndex { 'desktopNotifications': 1 }, { sparse: 1 }
@tryEnsureIndex { 'mobilePushNotifications': 1 }, { sparse: 1 }
@tryEnsureIndex { 'emailNotifications': 1 }, { sparse: 1 }
diff --git a/packages/rocketchat-push-notifications/client/stylesheets/pushNotifications.less b/packages/rocketchat-push-notifications/client/stylesheets/pushNotifications.less
index ad6f5d912f52..fe24c8c50c91 100644
--- a/packages/rocketchat-push-notifications/client/stylesheets/pushNotifications.less
+++ b/packages/rocketchat-push-notifications/client/stylesheets/pushNotifications.less
@@ -28,7 +28,8 @@
text-align: center;
}
- [data-edit] {
+ [data-edit],
+ [data-play] {
cursor: pointer;
}
}
diff --git a/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.html b/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.html
index edcc49c4c9ff..8e7038988ad9 100644
--- a/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.html
+++ b/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.html
@@ -6,6 +6,27 @@
{{_ "Notifications"}}