parent
736e5202b1
commit
8219f13448
@ -0,0 +1,19 @@ |
||||
<template name="soundInfo"> |
||||
{{#if editingSound}} |
||||
{{> soundEdit (soundToEdit)}} |
||||
{{else}} |
||||
{{#with sound}} |
||||
<div class="about clearfix"> |
||||
<div class="info"> |
||||
<h3 title="{{name}}">{{name}}</h3> |
||||
</div> |
||||
</div> |
||||
{{/with}} |
||||
<nav> |
||||
{{#if hasPermission 'manage-sounds'}} |
||||
<button class='button button-block danger delete'><span><i class='icon-trash'></i> {{_ "Delete"}}</span></button> |
||||
<button class='button button-block primary edit-sound'><span><i class='icon-edit'></i> {{_ "Edit"}}</span></button> |
||||
{{/if}} |
||||
</nav> |
||||
{{/if}} |
||||
</template> |
@ -0,0 +1,111 @@ |
||||
/* globals isSetNotNull */ |
||||
Template.soundInfo.helpers({ |
||||
name() { |
||||
let sound = Template.instance().sound.get(); |
||||
return sound.name; |
||||
}, |
||||
|
||||
sound() { |
||||
return Template.instance().sound.get(); |
||||
}, |
||||
|
||||
editingSound() { |
||||
return Template.instance().editingSound.get(); |
||||
}, |
||||
|
||||
soundToEdit() { |
||||
let instance = Template.instance(); |
||||
return { |
||||
sound: instance.sound.get(), |
||||
back(name) { |
||||
instance.editingSound.set(); |
||||
|
||||
if (isSetNotNull(() => name)) { |
||||
let sound = instance.sound.get(); |
||||
if (isSetNotNull(() => sound.name) && sound.name !== name) { |
||||
return instance.loadedName.set(name); |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
} |
||||
}); |
||||
|
||||
Template.soundInfo.events({ |
||||
['click .delete'](e, instance) { |
||||
e.stopPropagation(); |
||||
e.preventDefault(); |
||||
let sound = instance.sound.get(); |
||||
if (isSetNotNull(() => sound)) { |
||||
let _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 |
||||
}); |
||||
|
||||
RocketChat.TabBar.showGroup('adminSounds'); |
||||
RocketChat.TabBar.closeFlex(); |
||||
} |
||||
}); |
||||
}); |
||||
} |
||||
}, |
||||
|
||||
['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(() => { |
||||
let data = Template.currentData(); |
||||
if (isSetNotNull(() => data.clear)) { |
||||
this.clear = data.clear; |
||||
} |
||||
}); |
||||
|
||||
this.autorun(() => { |
||||
let data = Template.currentData(); |
||||
let sound = this.sound.get(); |
||||
if (isSetNotNull(() => sound.name)) { |
||||
this.loadedName.set(sound.name); |
||||
} else if (isSetNotNull(() => data.name)) { |
||||
this.loadedName.set(data.name); |
||||
} |
||||
}); |
||||
|
||||
this.autorun(() => { |
||||
let data = Template.currentData(); |
||||
this.sound.set(data); |
||||
}); |
||||
}); |
@ -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; |
||||
} |
||||
} |
@ -0,0 +1,56 @@ |
||||
/* globals isSetNotNull */ |
||||
class CustomSounds { |
||||
constructor() { |
||||
this.list = new ReactiveVar({}); |
||||
} |
||||
|
||||
add(sound) { |
||||
sound.src = this.getURL(sound); |
||||
const audio = $('<audio />', { id: sound._id, preload: true }).append( |
||||
$('<source />', { 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 this.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) { |
||||
let 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() { |
||||
return Object.values(this.list.get()); |
||||
} |
||||
} |
||||
|
||||
RocketChat.CustomSounds = new CustomSounds; |
||||
|
||||
Meteor.startup(() => |
||||
Meteor.call('listCustomSounds', (error, result) => { |
||||
for (let sound of result) { |
||||
RocketChat.CustomSounds.add(sound); |
||||
} |
||||
}) |
||||
); |
@ -1,3 +0,0 @@ |
||||
RocketChat.sounds = { |
||||
list: [] |
||||
}; |
@ -0,0 +1,3 @@ |
||||
Meteor.startup(() => |
||||
RocketChat.Notifications.onAll('deleteCustomSound', data => RocketChat.CustomSounds.remove(data.soundData)) |
||||
); |
@ -0,0 +1,3 @@ |
||||
Meteor.startup(() => |
||||
RocketChat.Notifications.onAll('updateCustomSound', data => RocketChat.CustomSounds.update(data.soundData)) |
||||
); |
@ -0,0 +1,22 @@ |
||||
/* globals isSetNotNull, 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 (!isSetNotNull(() => sound)) { |
||||
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; |
||||
} |
||||
}); |
@ -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
|
||||
let 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
|
||||
let createSound = { |
||||
name: soundData.name, |
||||
extension: soundData.extension |
||||
}; |
||||
|
||||
let _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; |
||||
} |
||||
} |
||||
}); |
@ -0,0 +1,5 @@ |
||||
Meteor.methods({ |
||||
listCustomSounds() { |
||||
return RocketChat.models.CustomSounds.find({}).fetch(); |
||||
} |
||||
}); |
@ -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'); |
||||
} |
||||
|
||||
let file = new Buffer(binaryContent, 'binary'); |
||||
|
||||
let rs = RocketChatFile.bufferToStream(file); |
||||
RocketChatFileCustomSoundsInstance.deleteFile(`${soundData._id}.${soundData.extension}`); |
||||
let ws = RocketChatFileCustomSoundsInstance.createWriteStream(`${soundData._id}.${soundData.extension}`, contentType); |
||||
ws.on('end', Meteor.bindEnvironment(() => |
||||
Meteor.setTimeout(() => RocketChat.Notifications.notifyAll('updateCustomSound', {soundData}) |
||||
, 500) |
||||
)); |
||||
|
||||
rs.pipe(ws); |
||||
} |
||||
}); |
@ -1,15 +1,10 @@ |
||||
<template name="audioNotification"> |
||||
<audio id="chime" preload> |
||||
<source src="sounds/chime.mp3" type="audio/mpeg" /> |
||||
</audio> |
||||
<audio id="chatNewRoomNotification" preload> |
||||
<source src="sounds/door.mp3" type="audio/mpeg" /> |
||||
</audio> |
||||
{{#each audioAssets}} |
||||
<audio id="{{_id}}" preload> |
||||
{{#each sources}} |
||||
<source src="{{src}}" type="{{type}}" /> |
||||
{{/each}} |
||||
<div id="audioFilesPreload"> |
||||
<audio id="chime" preload> |
||||
<source src="sounds/chime.mp3" type="audio/mpeg" /> |
||||
</audio> |
||||
{{/each}} |
||||
<audio id="chatNewRoomNotification" preload> |
||||
<source src="sounds/door.mp3" type="audio/mpeg" /> |
||||
</audio> |
||||
</div> |
||||
</template> |
||||
|
@ -1,6 +0,0 @@ |
||||
/* globals KonchatNotification */ |
||||
Template.audioNotification.helpers({ |
||||
audioAssets() { |
||||
return KonchatNotification.audioAssets; |
||||
} |
||||
}) |
Loading…
Reference in new issue