|
@@ -7,18 +7,57 @@ import audioContext from 'audio-context'
|
|
|
import ko from 'knockout'
|
|
|
import _dompurify from 'dompurify'
|
|
|
import keyboardjs from 'keyboardjs'
|
|
|
+import anchorme from 'anchorme'
|
|
|
|
|
|
import { ContinuousVoiceHandler, PushToTalkVoiceHandler, VADVoiceHandler, initVoice } from './voice'
|
|
|
import {initialize as localizationInitialize, translate} from './loc';
|
|
|
|
|
|
const dompurify = _dompurify(window)
|
|
|
|
|
|
+// from: https://gist.github.com/haliphax/5379454
|
|
|
+ko.extenders.scrollFollow = function (target, selector) {
|
|
|
+ target.subscribe(function (chat) {
|
|
|
+ const el = document.querySelector(selector);
|
|
|
+
|
|
|
+ // the scroll bar is all the way down, so we know they want to follow the text
|
|
|
+ if (el.scrollTop == el.scrollHeight - el.clientHeight) {
|
|
|
+ // have to push our code outside of this thread since the text hasn't updated yet
|
|
|
+ setTimeout(function () { el.scrollTop = el.scrollHeight - el.clientHeight; }, 0);
|
|
|
+ } else {
|
|
|
+ // send notification
|
|
|
+ const last = chat[chat.length - 1]
|
|
|
+ if (Notification.permission == 'granted' && last.type != 'chat-message-self') {
|
|
|
+ let sender = 'Mumble Server'
|
|
|
+ if (last.user && last.user.name) sender=last.user.name()
|
|
|
+ new Notification(sender, {body: dompurify.sanitize(last.message, {ALLOWED_TAGS:[]})})
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return target;
|
|
|
+};
|
|
|
+
|
|
|
function sanitize (html) {
|
|
|
return dompurify.sanitize(html, {
|
|
|
- ALLOWED_TAGS: ['br', 'b', 'i', 'u', 'a', 'span', 'p']
|
|
|
+ ALLOWED_TAGS: ['br', 'b', 'i', 'u', 'a', 'span', 'p', 'img', 'center']
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+const anchormeOptions = {
|
|
|
+ // force target _blank attribute
|
|
|
+ attributes: {
|
|
|
+ target: "_blank"
|
|
|
+ },
|
|
|
+ // force https protocol except email
|
|
|
+ protocol: function(s) {
|
|
|
+ if (anchorme.validate.email(s)) {
|
|
|
+ return "mailto:";
|
|
|
+ } else {
|
|
|
+ return "https://";
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
function openContextMenu (event, contextMenu, target) {
|
|
|
contextMenu.posX(event.clientX)
|
|
|
contextMenu.posY(event.clientY)
|
|
@@ -47,6 +86,20 @@ function ContextMenu () {
|
|
|
self.target = ko.observable()
|
|
|
}
|
|
|
|
|
|
+function AddChannelDialog () {
|
|
|
+ var self = this;
|
|
|
+ self.channelName = ko.observable('')
|
|
|
+ self.parentID = 0;
|
|
|
+ self.visible = ko.observable(false);
|
|
|
+ self.show = self.visible.bind(self.visible, true)
|
|
|
+ self.hide = self.visible.bind(self.visible, false)
|
|
|
+
|
|
|
+ self.addchannel = function() {
|
|
|
+ self.hide();
|
|
|
+ ui.addchannel(self.channelName());
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
function ConnectDialog () {
|
|
|
var self = this
|
|
|
self.address = ko.observable('')
|
|
@@ -286,6 +339,7 @@ class GlobalBindings {
|
|
|
this.userContextMenu = new ContextMenu()
|
|
|
this.channelContextMenu = new ContextMenu()
|
|
|
this.connectDialog = new ConnectDialog()
|
|
|
+ this.addChannelDialog = new AddChannelDialog()
|
|
|
this.connectErrorDialog = new ConnectErrorDialog(this.connectDialog)
|
|
|
this.connectionInfo = new ConnectionInfo(this)
|
|
|
this.commentDialog = new CommentDialog()
|
|
@@ -300,8 +354,8 @@ class GlobalBindings {
|
|
|
this.messageBox = ko.observable('')
|
|
|
this.toolbarHorizontal = ko.observable(!this.settings.toolbarVertical)
|
|
|
this.selected = ko.observable()
|
|
|
- this.selfMute = ko.observable()
|
|
|
- this.selfDeaf = ko.observable()
|
|
|
+ this.selfMute = ko.observable(this.config.defaults.startMute)
|
|
|
+ this.selfDeaf = ko.observable(this.config.defaults.startDeaf)
|
|
|
|
|
|
this.selfMute.subscribe(mute => {
|
|
|
if (voiceHandler) {
|
|
@@ -309,6 +363,14 @@ class GlobalBindings {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
+ this.submitOnEnter = function(data, e) {
|
|
|
+ if (e.which == 13 && !e.shiftKey) {
|
|
|
+ this.submitMessageBox();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
this.toggleToolbarOrientation = () => {
|
|
|
this.toolbarHorizontal(!this.toolbarHorizontal())
|
|
|
this.settings.toolbarVertical = !this.toolbarHorizontal()
|
|
@@ -323,6 +385,32 @@ class GlobalBindings {
|
|
|
this.settingsDialog(new SettingsDialog(this.settings))
|
|
|
}
|
|
|
|
|
|
+ this.openAddChannel = (user, channel) => {
|
|
|
+ this.addChannelDialog.parentID = channel.model._id;
|
|
|
+ this.addChannelDialog.show()
|
|
|
+ }
|
|
|
+
|
|
|
+ this.addchannel = (channelName) => {
|
|
|
+ var msg = {
|
|
|
+ name: 'ChannelState',
|
|
|
+ payload: {
|
|
|
+ parent: this.addChannelDialog.parentID || 0,
|
|
|
+ name: channelName
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.client._send(msg);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.ChannelRemove = (user, channel) => {
|
|
|
+ var msg = {
|
|
|
+ name: 'ChannelRemove',
|
|
|
+ payload: {
|
|
|
+ channel_id: channel.model._id
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.client._send(msg);
|
|
|
+ }
|
|
|
+
|
|
|
this.applySettings = () => {
|
|
|
const settingsDialog = this.settingsDialog()
|
|
|
|
|
@@ -342,10 +430,14 @@ class GlobalBindings {
|
|
|
}
|
|
|
|
|
|
this.getTimeString = () => {
|
|
|
- return '[' + new Date().toLocaleTimeString('en-US') + ']'
|
|
|
+ return '[' + new Date().toLocaleTimeString(navigator.language) + ']'
|
|
|
}
|
|
|
|
|
|
this.connect = (username, host, port, tokens = [], password, channelName = "") => {
|
|
|
+
|
|
|
+ // if browser support Notification request permission
|
|
|
+ if ('Notification' in window) Notification.requestPermission()
|
|
|
+
|
|
|
this.resetClient()
|
|
|
|
|
|
this.remoteHost(host)
|
|
@@ -412,7 +504,7 @@ class GlobalBindings {
|
|
|
type: 'chat-message',
|
|
|
user: sender.__ui,
|
|
|
channel: channels.length > 0,
|
|
|
- message: sanitize(message)
|
|
|
+ message: anchorme({input: sanitize(message), options: anchormeOptions})
|
|
|
})
|
|
|
})
|
|
|
|
|
@@ -639,13 +731,13 @@ class GlobalBindings {
|
|
|
return true // TODO check for perms
|
|
|
}
|
|
|
ui.canAdd = () => {
|
|
|
- return false // TODO check for perms and implement
|
|
|
+ return true // TODO check for perms
|
|
|
}
|
|
|
ui.canEdit = () => {
|
|
|
return false // TODO check for perms and implement
|
|
|
}
|
|
|
ui.canRemove = () => {
|
|
|
- return false // TODO check for perms and implement
|
|
|
+ return true // TODO check for perms
|
|
|
}
|
|
|
ui.canLink = () => {
|
|
|
return false // TODO check for perms and implement
|
|
@@ -784,18 +876,23 @@ class GlobalBindings {
|
|
|
if (target === this.thisUser()) {
|
|
|
target = target.channel()
|
|
|
}
|
|
|
+ // Avoid blank message
|
|
|
+ if (sanitize(message).trim().length == 0) return;
|
|
|
+ // Support multiline
|
|
|
+ message = message.replace(/\n\n+/g,"\n\n");
|
|
|
+ message = message.replace(/\n/g,"<br>");
|
|
|
// Send message
|
|
|
- target.model.sendMessage(message)
|
|
|
+ target.model.sendMessage(anchorme(message))
|
|
|
if (target.users) { // Channel
|
|
|
this.log.push({
|
|
|
type: 'chat-message-self',
|
|
|
- message: sanitize(message),
|
|
|
+ message: anchorme({input: sanitize(message), options: anchormeOptions}),
|
|
|
channel: target
|
|
|
})
|
|
|
} else { // User
|
|
|
this.log.push({
|
|
|
type: 'chat-message-self',
|
|
|
- message: sanitize(message),
|
|
|
+ message: anchorme({input: sanitize(message), options: anchormeOptions}),
|
|
|
user: target
|
|
|
})
|
|
|
}
|
|
@@ -1134,6 +1231,31 @@ function translateEverything() {
|
|
|
translatePiece('.channel-context-menu .copy-mumble-url', 'textcontent', {}, 'channelcontextmenu.copy_mumble_url');
|
|
|
translatePiece('.channel-context-menu .copy-mumble-web-url', 'textcontent', {}, 'channelcontextmenu.copy_mumble_web_url');
|
|
|
translatePiece('.channel-context-menu .send-message', 'textcontent', {}, 'channelcontextmenu.send_message');
|
|
|
+
|
|
|
+ translatePiece('.toolbar .tb-horizontal', 'attribute', {'name': 'title'}, 'toolbar.orientation');
|
|
|
+ translatePiece('.toolbar .tb-horizontal', 'attribute', {'name': 'alt'}, 'toolbar.orientation');
|
|
|
+ translatePiece('.toolbar .tb-vertical', 'attribute', {'name': 'title'}, 'toolbar.orientation');
|
|
|
+ translatePiece('.toolbar .tb-vertical', 'attribute', {'name': 'alt'}, 'toolbar.orientation');
|
|
|
+ translatePiece('.toolbar .tb-connect', 'attribute', {'name': 'title'}, 'toolbar.connect');
|
|
|
+ translatePiece('.toolbar .tb-connect', 'attribute', {'name': 'alt'}, 'toolbar.connect');
|
|
|
+ translatePiece('.toolbar .tb-information', 'attribute', {'name': 'title'}, 'toolbar.information');
|
|
|
+ translatePiece('.toolbar .tb-information', 'attribute', {'name': 'alt'}, 'toolbar.information');
|
|
|
+ translatePiece('.toolbar .tb-mute', 'attribute', {'name': 'title'}, 'toolbar.mute');
|
|
|
+ translatePiece('.toolbar .tb-mute', 'attribute', {'name': 'alt'}, 'toolbar.mute');
|
|
|
+ translatePiece('.toolbar .tb-unmute', 'attribute', {'name': 'title'}, 'toolbar.unmute');
|
|
|
+ translatePiece('.toolbar .tb-unmute', 'attribute', {'name': 'alt'}, 'toolbar.unmute');
|
|
|
+ translatePiece('.toolbar .tb-deaf', 'attribute', {'name': 'title'}, 'toolbar.deaf');
|
|
|
+ translatePiece('.toolbar .tb-deaf', 'attribute', {'name': 'alt'}, 'toolbar.deaf');
|
|
|
+ translatePiece('.toolbar .tb-undeaf', 'attribute', {'name': 'title'}, 'toolbar.undeaf');
|
|
|
+ translatePiece('.toolbar .tb-undeaf', 'attribute', {'name': 'alt'}, 'toolbar.undeaf');
|
|
|
+ translatePiece('.toolbar .tb-record', 'attribute', {'name': 'title'}, 'toolbar.record');
|
|
|
+ translatePiece('.toolbar .tb-record', 'attribute', {'name': 'alt'}, 'toolbar.record');
|
|
|
+ translatePiece('.toolbar .tb-comment', 'attribute', {'name': 'title'}, 'toolbar.comment');
|
|
|
+ translatePiece('.toolbar .tb-comment', 'attribute', {'name': 'alt'}, 'toolbar.comment');
|
|
|
+ translatePiece('.toolbar .tb-settings', 'attribute', {'name': 'title'}, 'toolbar.settings');
|
|
|
+ translatePiece('.toolbar .tb-settings', 'attribute', {'name': 'alt'}, 'toolbar.settings');
|
|
|
+ translatePiece('.toolbar .tb-sourcecode', 'attribute', {'name': 'title'}, 'toolbar.sourcecode');
|
|
|
+ translatePiece('.toolbar .tb-sourcecode', 'attribute', {'name': 'alt'}, 'toolbar.sourcecode');
|
|
|
}
|
|
|
|
|
|
async function main() {
|