Added notification support to mobile application.
Ylian Saint-Hilaire committed
Jan 4, 2021 at 19:14 UTC
9a1eb534c41b26326e0a17801bace95884b4ef00
4 files changed
+303
-13
meshcentral.js
+1
@@ -159,6 +159,7 @@ function CreateMeshCentralServer(config, args) {
159
console.log(' --redirport [number] Creates an additional HTTP server to redirect users to the HTTPS server.');
160
console.log(' --exactports Server must run with correct ports or exit.');
161
console.log(' --noagentupdate Server will not update mesh agent native binaries.');
162
+ console.log(' --nedbtodb Transfer all NeDB records into current database.');
163
console.log(' --listuserids Show a list of a user identifiers in the database.');
164
console.log(' --cert [name], (country), (org) Create a web server certificate with [name] server name.');
165
console.log(' country and organization can optionally be set.');
views/default-mobile.handlebars
+289
-4
@@ -417,10 +417,65 @@
417
margin:10px;
418
z-index:1000;
419
}
420
+
421
+ #notificationCount {
422
+ min-width: 28px;
423
+ font-size: 20px;
424
+ background-color: orange;
425
+ text-align: center;
426
+ cursor: pointer;
427
+ color: black;
428
+ }
429
+
430
+ .notifiyBox {
431
+ font-size: 16px;
432
+ position: absolute;
433
+ z-index: 1000;
434
+ top: 60px;
435
+ right: 76px;
436
+ width: 300px;
437
+ text-align: left;
438
+ background-color: #F0ECCD;
439
+ border: 4px solid #666;
440
+ -webkit-border-radius: 10px;
441
+ -moz-border-radius: 10px;
442
+ border-radius: 10px;
443
+ -webkit-box-shadow: 2px 2px 4px #888;
444
+ -moz-box-shadow: 2px 2px 4px #888;
445
+ box-shadow: 2px 2px 4px #888;
446
+ max-height: 200px;
447
+ }
448
+
449
+ .night .notifiyBox {
450
+ color: black;
451
+ }
452
+
453
+ .notifiyBox:before {
454
+ content: ' ';
455
+ position: absolute;
456
+ width: 0;
457
+ height: 0;
458
+ right: 5px;
459
+ top: -30px;
460
+ border: 15px solid;
461
+ border-color: transparent #666 #666 transparent;
462
+ }
463
+
464
+ .notifiyBox:after {
465
+ content: ' ';
466
+ position: absolute;
467
+ width: 0;
468
+ height: 0;
469
+ right: 7px;
470
+ top: -24px;
471
+ border: 12px solid;
472
+ border-color: transparent #F0ECCD #F0ECCD transparent;
473
+ }
474
</style>
475
</head>
476
<body id="body" onload="if (typeof(startup) !== 'undefined') startup();" style="overflow-y:hidden;margin:0;padding:0;border:0;color:black;font-size:13px;font-family:\'Trebuchet MS\', Arial, Helvetica, sans-serif">
477
<div id=container>
478
+ <div id="notifiyBox" class="notifiyBox" style="display:none"></div>
479
<div id=mastheadx></div>
480
<div id=masthead style="background:url(logo.png) 0px 0px;background-size:341px 50px;background-color:#036;background-repeat:no-repeat;height:50px;width:100%;overflow:hidden">
481
<div style="width:calc(100% - 50px);overflow:hidden">
@@ -431,7 +486,8 @@
486
<strong><font style="font-size:12px;font-family:Arial,Helvetica,sans-serif">{{{title2}}}</font></strong>
487
</div>
488
</div>
434
- <img id="topMenuIcon" class=noselect style="position:absolute;right:0;top:10px;bottom:50px;color:#c8c8c8;font-size:44px;margin-right:8px;cursor:pointer;display:none" onclick=topMenu() src="/images/3bars-30.png" width=30 height=30 />
489
+ <div id=notificationCount onclick="clickNotificationIcon()" class="unselectable" style="position:absolute;right:50px;top:0px;font-size:28px;width:50px;height:50px;cursor:pointer;display:none" title="Click to view current notifications"><div id="notificationCount2" style="padding-top:8px">0</div></div>
490
+ <img id="topMenuIcon" class=noselect style="position:absolute;right:0;top:10px;color:#c8c8c8;font-size:44px;margin-right:8px;cursor:pointer;display:none" onclick=topMenu() src="/images/3bars-30.png" width=30 height=30 />
491
</div>
492
<div id=page_content style="position:absolute;bottom:32px;top:50px;width:100%">
493
<div id=column_l style="width:100%;padding:0;position:absolute;bottom:0px;top:0px">
@@ -879,6 +935,7 @@
935
<div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer" onclick=topMenu(1)>My Account</div>
936
<div id="logoutMenuOption"><a href=/logout><div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer">Logout</div></a></div>
937
</div>
938
+ <audio id="chimes"><source src="sounds/chimes.mp3" type="audio/mp3" /></audio>
939
<iframe name="fileUploadFrame" style=display:none></iframe>
940
<script>
941
'use strict';
@@ -1170,6 +1227,36 @@
1227
}
1228
break;
1229
}
1230
+ case 'msg': {
1231
+ // Check if this is a message from a node
1232
+ if (message.nodeid != null) {
1233
+ var index = -1;
1234
+ if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == message.nodeid) { index = i; break; } } }
1235
+ if (index != -1) {
1236
+ if (message.type == 'notify') { // This is a notification message.
1237
+ var n = getstore('notifications', 0);
1238
+ if (((n & 8) == 0) && (message.amtMessage != null)) { break; } // Intel AMT desktop & terminal messages should be ignored.
1239
+ var n = { text: message.value, title: message.title, icon: message.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
1240
+ if (message.id != null) { n.id = message.id; }
1241
+ if (message.nodeid != null) { n.nodeid = message.nodeid; }
1242
+ if (message.tag != null) { n.tag = message.tag; }
1243
+ if (message.username != null) { n.username = message.username; }
1244
+ if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
1245
+ addNotification(n);
1246
+ }
1247
+ }
1248
+ } else {
1249
+ if (message.type == 'notify') { // This is a notification message.
1250
+ var n = { text: message.value, title: message.title, icon: message.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
1251
+ if (message.id != null) { n.id = message.id; }
1252
+ if (message.tag != null) { n.tag = message.tag; }
1253
+ if (message.username != null) { n.username = message.username; }
1254
+ if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
1255
+ addNotification(n);
1256
+ }
1257
+ }
1258
+ break;
1259
+ }
1260
case 'getnetworkinfo': {
1261
if (currentNode._id != message.nodeid) return;
1262
updateDeviceDetails(getNodeFromId(message.nodeid), null, message);
@@ -1536,9 +1623,11 @@
1623
break;
1624
}
1625
case 'notify': {
1539
- //var n = { text: message.event.value };
1540
- //if (message.event.tag != null) { n.tag = message.event.tag; }
1541
- //addNotification(n);
1626
+ var n = { text: message.event.value, title: message.event.title, icon: message.event.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
1627
+ if (message.id != null) { n.id = message.id; }
1628
+ if (message.event.tag != null) { n.tag = message.event.tag; }
1629
+ if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
1630
+ addNotification(n);
1631
break;
1632
}
1633
case 'sysinfohash': {
@@ -4449,6 +4538,200 @@
4538
function p20deleteUser(e, userid) { haltEvent(e); p20viewuserEx(2, decodeURIComponent(userid)); }
4539
function p20viewuserEx2(button, userid) { meshserver.send({ action: 'removemeshuser', meshid: currentMesh._id, meshname: currentMesh.name, userid: userid }); }
4540
4541
+
4542
+ //
4543
+ // NOTIFICATIONS
4544
+ //
4545
+
4546
+ var notifications = [];
4547
+
4548
+ // Toggle showing notifications
4549
+ function clickNotificationIcon(show) {
4550
+ //addNotification({ icon:0, text:'test' });
4551
+ if (show == true) { QV('notifiyBox', true); } else if (show == false) { QV('notifiyBox', false); } else { QV('notifiyBox', QS('notifiyBox')['display'] == 'none'); }
4552
+ drawNotifications();
4553
+ }
4554
+
4555
+ // Set the notification count on the upper right oft he screen
4556
+ function setNotificationCount(c) {
4557
+ if (parseInt(Q('notificationCount').innerHTML) == c) return; // If the count did not change, exit now.
4558
+ QH('notificationCount2', c);
4559
+ QV('notificationCount', c > 0);
4560
+ }
4561
+
4562
+ // Refresh the notification box
4563
+ function drawNotifications() {
4564
+ var notifySettings = getstore('notifications', 0);
4565
+ var r = '';
4566
+ if (notifications.length == 0) {
4567
+ r = '<div style=margin:5px>' + "There are currently no notifications" + '</div>';
4568
+ } else {
4569
+ for (var i in notifications) {
4570
+ var n = notifications[i], t = '', d = new Date(n.time), icon = 0;
4571
+ if (n.title != null) { t = '<b>' + EscapeHtml(n.title) + '</b>: ' }
4572
+ if (n.nodeid != null) {
4573
+ var node = getNodeFromId(n.nodeid);
4574
+ if (node != null) {
4575
+ icon = node.icon;
4576
+ if (notifySettings & 16) { t = '<b>' + EscapeHtml(meshes[node.meshid].name) + ' / ' + EscapeHtml(node.name) + '</b>: '; } else { t = '<b>' + EscapeHtml(node.name) + '</b>: '; } // Display with or without group name
4577
+ }
4578
+ }
4579
+
4580
+ r += '<div title="' + format("Occured at {0}", printDateTime(d)) + '" id="notifyx' + n.id + '" class=notification style="cursor:pointer;border-top:1px solid ' + ((r == '') ? 'transparent' : 'orange') + '">';
4581
+ if (icon) { r += '<div class=j' + icon + ' onclick="notificationSelected(' + n.id + ')" style=margin:5px;float:left></div>'; }
4582
+ r += '<div onclick="notificationDelete(' + n.id + ')" class=unselectable title="' + "Clear this notification" + '" style=margin:5px;float:right;color:orange><b>X</b></div><div onclick="notificationSelected(' + n.id + ')" style=margin:5px>' + t + EscapeHtml(n.text) + '</div></div>';
4583
+ }
4584
+ }
4585
+ var deleteall = '';
4586
+ if (notifications.length > 1) { deleteall = '<div id="notifyRemoveAll" onclick="deleteAllNotifications()" style="cursor:pointer;border-top:1px solid orange;margin:5px;color:orange;text-align:right;padding-right:3px">' + "Clear all" + '</div>'; }
4587
+ QH('notifiyBox', '<div class=customScroll style="max-height:170px;overflow-y:auto;margin:5px">' + r + '</div>' + deleteall);
4588
+ }
4589
+
4590
+ // A notification was selected
4591
+ function notificationSelected(id, del) {
4592
+ var j = -1;
4593
+ for (var i in notifications) { if (notifications[i].id == id) { j = i; } }
4594
+ if (j != -1) {
4595
+ notificationSelectedEx(notifications[j], id);
4596
+ if (del && notifications[j]) {
4597
+ if (notifications[j].notification) { notifications[j].notification.close(); delete notifications[j].notification; }
4598
+ notificationDelete(id);
4599
+ }
4600
+ }
4601
+ }
4602
+
4603
+ function notificationSelectedEx(n, id) {
4604
+ if (n.nodeid != null) {
4605
+ if (n.tag == 'desktop') gotoDevice(n.nodeid, 12); // Desktop
4606
+ else if (n.tag == 'terminal') gotoDevice(n.nodeid, 11); // Terminal
4607
+ else if (n.tag == 'files') gotoDevice(n.nodeid, 13); // Files
4608
+ else if (n.tag == 'intelamt') gotoDevice(n.nodeid, 14); // Intel AMT
4609
+ else if (n.tag == 'console') gotoDevice(n.nodeid, 15); // Files
4610
+ else gotoDevice(n.nodeid, 10); // General
4611
+ } else {
4612
+ if ((n.tag != null) && n.tag.startsWith('meshmessenger/')) {
4613
+ safeNewWindow('/messenger?id=' + n.tag + '&title=' + encodeURIComponentEx(n.username), n.tag.split('/')[2]);
4614
+ notificationDelete(id);
4615
+ }
4616
+ }
4617
+ }
4618
+
4619
+ // Remove one notification
4620
+ function notificationDelete(id) {
4621
+ var j = -1, e = Q('notifyx' + id);
4622
+ if (e != null) {
4623
+ for (var i in notifications) { if (notifications[i].id == id) { j = i; } }
4624
+ if (j != -1) {
4625
+ meshserver.send({ action: 'intersession', subaction: 'removeNotify', id: id }); // Remove the notification in other sessions of the same user.
4626
+ if (notifications[j].notification) { notifications[j].notification.close(); delete notifications[j].notification; }
4627
+ notifications.splice(j, 1);
4628
+ e.parentNode.removeChild(e);
4629
+ setNotificationCount(notifications.length);
4630
+ if (notifications.length == 0) { QV('notifiyBox', false); }
4631
+ if (notifications.length == 1) { QV('notifyRemoveAll', false); }
4632
+ if ((notifications.length > 0) && (j == 0)) {
4633
+ var n = notifications[0];
4634
+ QS('notifyx' + n.id)['border-top'] = '1px solid transparent';
4635
+ }
4636
+ }
4637
+ }
4638
+ }
4639
+
4640
+ // Add a new notification and play the notification sound
4641
+ function addNotification(n) {
4642
+ // Perform message translation
4643
+ var translatedTitles = [
4644
+ null,
4645
+ "New Account", // 1
4646
+ "Server Limit",
4647
+ "Security Warning",
4648
+ "Account Settings",
4649
+ "Device Group",
4650
+ "Invite Codes"
4651
+ ];
4652
+ var translatedMessages = [
4653
+ null,
4654
+ "Permission denied", // 1
4655
+ "Invalid username",
4656
+ "Invalid password",
4657
+ "Invalid email",
4658
+ "Invalid domain",
4659
+ "Invalid site permissions",
4660
+ "User already exists",
4661
+ "Unable to add user in this mode",
4662
+ "Validation exception",
4663
+ "Account limit reached.", // 10
4664
+ "Chat Request, Click here to accept.",
4665
+ "There has been {0} failed login attempts on this account since the last login.",
4666
+ "Failed to change email address, another account already using: {0}.",
4667
+ "Email sent.",
4668
+ "User {0} not found.",
4669
+ "Users {0} not found.",
4670
+ "Error, unable to change to previously used password.",
4671
+ "Error, unable to change to commonly used password.",
4672
+ "Error, password not changed.",
4673
+ "Password changed.", // 20
4674
+ "Current password not correct.",
4675
+ "Error, invite code \"{0}\" already in use.",
4676
+ "SMS gateway not enabled",
4677
+ "No user management rights",
4678
+ "Invalid SMS message",
4679
+ "No phone number for this user",
4680
+ "SMS succesfuly sent.",
4681
+ "SMS error",
4682
+ "SMS error: {0}"
4683
+ ];
4684
+ if (typeof n.titleid == 'number') { try { n.title = translatedTitles[n.titleid]; } catch (ex) { } }
4685
+ if (typeof n.msgid == 'number') { try { n.text = translatedMessages[n.msgid]; if (Array.isArray(n.args)) { format(n.text, ...n.args); } } catch (ex) { } }
4686
+
4687
+ // Show notification within the web page.
4688
+ if (n.time == null) { n.time = Date.now(); }
4689
+ if (n.id == null) { n.id = Math.random(); }
4690
+ notifications.unshift(n);
4691
+ setNotificationCount(notifications.length);
4692
+ clickNotificationIcon(true);
4693
+ var notifySettings = getstore('notifications', 0);
4694
+ if (notifySettings & 1) { Q('chimes').play(); }
4695
+
4696
+ // If web notifications are granted, use it.
4697
+ var notification = null;
4698
+ if (Notification && (Notification.permission == 'granted')) {
4699
+ var text = n.text.split('®').join('').split('<b>').join('').split('</b>').join('').split('<br />').join('\r\n'); // Clean up any HTML codes
4700
+ if (n.nodeid) {
4701
+ var node = getNodeFromId(n.nodeid);
4702
+ if (node) {
4703
+ if (notifySettings & 16) { // Notify with group name
4704
+ notification = new Notification(decodeURIComponent('{{{extitle}}}') + ' - ' + meshes[node.meshid].name + ' - ' + node.name, { tag: n.tag, body: text, icon: '/images/notify/icons128-' + node.icon + '.png' });
4705
+ } else {
4706
+ notification = new Notification(decodeURIComponent('{{{extitle}}}') + ' - ' + node.name, { tag: n.tag, body: text, icon: '/images/notify/icons128-' + node.icon + '.png' });
4707
+ }
4708
+ }
4709
+ } else {
4710
+ if (n.icon == null) { n.icon = 0; }
4711
+ var title = n.title;
4712
+ if (title == null) { title = ''; } else { title = ' - ' + n.title; }
4713
+ notification = new Notification(decodeURIComponent('{{{extitle}}}') + title, { tag: n.tag, body: text, icon: '/images/notify/icons128-' + n.icon + '.png' });
4714
+ }
4715
+ notification.id = n.id;
4716
+ notification.xtag = n.tag;
4717
+ notification.nodeid = n.nodeid;
4718
+ notification.username = n.username;
4719
+ notification.onclick = function (e) { notificationSelected(e.target.id, true); }
4720
+ n.notification = notification;
4721
+ }
4722
+
4723
+ // If the notification has a max time, setup the timer here.
4724
+ if ((typeof n.maxtime == 'number') && (n.maxtime > 0)) { var trigger = function notifyRemoveTrigger() { notificationDelete(notifyRemoveTrigger.xid); }; trigger.xid = n.id; setTimeout(trigger, n.maxtime * 1000); }
4725
+ }
4726
+
4727
+ // Remove all notifications
4728
+ function deleteAllNotifications() {
4729
+ notifications = [];
4730
+ setNotificationCount(0);
4731
+ drawNotifications();
4732
+ QV('notifiyBox', false);
4733
+ }
4734
+
4735
//
4736
// PANELS
4737
//
@@ -4649,6 +4932,8 @@
4932
function getUserName(userid) { if (users && users[userid] != null) return users[userid].name; return userid.split('/')[2]; }
4933
function addDetailItem(title, value, state) { return '<table style=width:100%><td>' + nobreak(title) + '<td style=text-align:right>' + value + '</table>'; }
4934
function isPrivateIP(a) { return (a.startsWith('10.') || a.startsWith('172.16.') || a.startsWith('192.168.')); }
4935
+ function encodeURIComponentEx(txt) { return encodeURIComponent(txt).replace(/'/g, '%27'); };
4936
+ function safeNewWindow(url, target) { var newWindow = window.open(url, target, 'noopener,noreferrer'); if (newWindow) { newWindow.opener = null; } }
4937
4938
</script>
4939
</body>
views/default.handlebars
+11
-8
@@ -12425,8 +12425,8 @@
12425
var emailLink = '';
12426
if (user.email) { emailLink = ' <a href="mailto:' + EscapeHtml(user.email) + '" \'><img class=hoverButton src="images/link1.png" /></a>'; }
12427
if (((user.siteadmin != 0xFFFFFFFF) || (userinfo.siteadmin == 0xFFFFFFFF))) { // If we are not site admin, we can't change a admin email or real name
12428
- x += addDeviceAttribute("Email", everify + email + emailLink + ' <img class=hoverButton style=cursor:pointer src="images/link5.png" onclick=p30showUserEmailChangeDialog(event,"' + userid + '") />');
12429
- x += addDeviceAttribute("Real Name", realname + ' <img class=hoverButton style=cursor:pointer src="images/link5.png" onclick=p30showUserRealNameChangeDialog(event,"' + userid + '") />');
12428
+ x += addDeviceAttribute("Email", everify + email + emailLink + ' <img class=hoverButton style=cursor:pointer src="images/link5.png" onclick=p30showUserEmailChangeDialog(event,"' + encodeURIComponentEx(user._id) + '") />');
12429
+ x += addDeviceAttribute("Real Name", realname + ' <img class=hoverButton style=cursor:pointer src="images/link5.png" onclick=p30showUserRealNameChangeDialog(event,"' + encodeURIComponentEx(user._id) + '") />');
12430
} else {
12431
x += addDeviceAttribute("Email", everify + email + emailLink);
12432
x += addDeviceAttribute("Real Name", realname);
@@ -12436,7 +12436,7 @@
12436
x += addDeviceAttribute("Phone Number", (user.phone?user.phone:('<i>' + "None" + '</i>')) + ' <img class=hoverButton style=cursor:pointer src="images/link5.png" onclick=p30editPhone() />');
12437
}
12438
12439
- x += addDeviceAttribute("Server Rights", premsg + msg.join(', ') + ' <img style=cursor:pointer class=hoverButton onclick=\'return showUserAdminDialog(event,"' + userid + '")\' src="images/link5.png" />');
12439
+ x += addDeviceAttribute("Server Rights", premsg + msg.join(', ') + ' <img style=cursor:pointer class=hoverButton onclick=\'return showUserAdminDialog(event,"' + encodeURIComponentEx(user._id) + '")\' src="images/link5.png" />');
12440
if (user.quota) x += addDeviceAttribute("Server Quota", EscapeHtml(parseInt(user.quota) / 1024) + ' k');
12441
x += addDeviceAttribute("Creation", printDateTime(new Date(user.creation * 1000)));
12442
if (user.login) x += addDeviceAttribute("Last Login", printDateTime(new Date(user.login * 1000)));
@@ -12455,7 +12455,7 @@
12455
if ((userinfo.siteadmin == 0xFFFFFFFF) || (userinfo.siteadmin & 2)) {
12456
var xuserGroups = '<i>' + "None" + '</i>';
12457
if (user.groups) { xuserGroups = ''; for (var i in user.groups) { xuserGroups += '<span class="tagSpan">' + EscapeHtml(user.groups[i]) + '</span>'; } }
12458
- x += addDeviceAttribute("Admin Realms", addLinkConditional(xuserGroups, 'showUserGroupDialog(event,"' + userid + '")', (userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.groups == null) && (userinfo._id != user._id) && (user.siteadmin != 0xFFFFFFFF))));
12458
+ x += addDeviceAttribute("Admin Realms", addLinkConditional(xuserGroups, 'showUserGroupDialog(event,"' + encodeURIComponentEx(user._id) + '")', (userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.groups == null) && (userinfo._id != user._id) && (user.siteadmin != 0xFFFFFFFF))));
12459
}
12460
12461
// Display device user consent
@@ -12488,10 +12488,13 @@
12488
x += '</table></div><br />';
12489
12490
// Add action buttons
12491
- x += '<input type=button value="' + "Notes" + '" title="' + "View notes about this user" + '" onclick=showNotes(false,"' + encodeURIComponentEx(userid) + '") />';
12492
- if (user.phone && (features & 0x02000000)) { x += '<input type=button value="' + "SMS" + '" title="' + "Send a SMS message to this user" + '" onclick=showSendSMS("' + encodeURIComponentEx(userid) + '") />'; }
12493
- if ((typeof user.email == 'string') && (user.emailVerified === true) && (features & 0x00000040)) { x += '<input type=button value="' + "Email" + '" title="' + "Send a email message to this user" + '" onclick=showSendEmail("' + userid + '") />'; }
12494
- if (!self && (activeSessions > 0)) { x += '<input type=button value="' + "Notify" + '" title="' + "Send user notification" + '" onclick=showUserAlertDialog(event,"' + encodeURIComponentEx(userid) + '") />'; }
12491
+ x += '<input type=button value="' + "Notes" + '" title="' + "View notes about this user" + '" onclick=showNotes(false,"' + encodeURIComponentEx(user._id) + '") />';
12492
+ if (user.phone && (features & 0x02000000)) { x += '<input type=button value="' + "SMS" + '" title="' + "Send a SMS message to this user" + '" onclick=showSendSMS("' + encodeURIComponentEx(user._id) + '") />'; }
12493
+ if ((typeof user.email == 'string') && (user.emailVerified === true) && (features & 0x00000040)) { x += '<input type=button value="' + "Email" + '" title="' + "Send a email message to this user" + '" onclick=showSendEmail("' + encodeURIComponentEx(user._id) + '") />'; }
12494
+ if (!self && (activeSessions > 0)) {
12495
+ x += '<input type=button value="' + "Notify" + '" title="' + "Send user notification" + '" onclick=showUserAlertDialog(event,"' + encodeURIComponentEx(user._id) + '") />';
12496
+ x += '<input type=button value="' + "Chat" + '" title="' + "Chat" + '" onclick=userChat(event,"' + encodeURIComponentEx(user._id) + '","' + encodeURIComponentEx(user.name) + '") />';
12497
+ }
12498
12499
// Setup the panel
12500
QH('p30html', x);
views/messenger.handlebars
+2
-1
@@ -4,6 +4,7 @@
4
<title>{{{title}}} - Messenger</title>
5
<meta http-equiv=X-UA-Compatible content="IE=edge">
6
<meta content="text/html;charset=utf-8" http-equiv=Content-Type>
7
+ <meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0" />
8
<meta name=format-detection content="telephone=no">
9
<meta name="robots" content="noindex,nofollow">
10
<link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
@@ -24,7 +25,7 @@
25
</div>
26
<div style="padding-top:9px;padding-left:6px;font-size:20px;display:inline-block"><b><span id="xtitle">MeshMessenger</span></b></div>
27
</div>
27
- <div id="xmiddle" style="position:absolute;left:0;right:0;top:38px;bottom:30px">
28
+ <div id="xmiddle" style="position:absolute;left:0;right:0;top:38px;bottom:30px;font-size:16px">
29
<div id="xmsgparent" style="position:absolute;left:0;right:0;top:0;bottom:0;overflow-y:scroll">
30
<div id="xmsg" style="padding:5px"></div>
31
</div>