Merge branch 'mailbox' into staging
This commit is contained in:
commit
7666a008d7
43
customize.dist/src/less2/include/notifications.less
Normal file
43
customize.dist/src/less2/include/notifications.less
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
@import (reference) "./colortheme-all.less";
|
||||||
|
|
||||||
|
.notifications_main() {
|
||||||
|
--LessLoader_require: LessLoader_currentFile();
|
||||||
|
}
|
||||||
|
& {
|
||||||
|
@notif-height: 50px;
|
||||||
|
.cp-notifications-container {
|
||||||
|
max-width: 300px;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
.cp-notification {
|
||||||
|
height: @notif-height;
|
||||||
|
display: flex;
|
||||||
|
.cp-notification-content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
p {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
&.cp-clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.cp-notification-dismiss {
|
||||||
|
color: black;
|
||||||
|
width: 25px;
|
||||||
|
height: 100%;
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
span {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -9,6 +9,7 @@
|
|||||||
@import (reference) "./icons.less";
|
@import (reference) "./icons.less";
|
||||||
@import (reference) "./modal.less";
|
@import (reference) "./modal.less";
|
||||||
@import (reference) "./help.less";
|
@import (reference) "./help.less";
|
||||||
|
@import (reference) "./notifications.less";
|
||||||
|
|
||||||
.toolbar_vars (
|
.toolbar_vars (
|
||||||
@color: @colortheme_default-color, // Color of the text for the toolbar
|
@color: @colortheme_default-color, // Color of the text for the toolbar
|
||||||
@ -66,6 +67,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.help_main(@color, @bg-color);
|
.help_main(@color, @bg-color);
|
||||||
|
.notifications_main();
|
||||||
.dropdown_main();
|
.dropdown_main();
|
||||||
.history_main();
|
.history_main();
|
||||||
.iconColors_main();
|
.iconColors_main();
|
||||||
@ -567,6 +569,22 @@
|
|||||||
}
|
}
|
||||||
.cp-toolbar-user {
|
.cp-toolbar-user {
|
||||||
height: @toolbar_line-height;
|
height: @toolbar_line-height;
|
||||||
|
.cp-toolbar-notifications {
|
||||||
|
height: @toolbar_line-height;
|
||||||
|
width: @toolbar_line-height;
|
||||||
|
margin-left: 0;
|
||||||
|
button {
|
||||||
|
height: @toolbar_line-height;
|
||||||
|
width: @toolbar_line-height;
|
||||||
|
font-size: 15px;
|
||||||
|
margin-top: -1px;
|
||||||
|
.cp-dropdown-button-title {
|
||||||
|
transform: scale(0.5);
|
||||||
|
bottom: -5px;
|
||||||
|
right: -5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.cp-toolbar-new {
|
.cp-toolbar-new {
|
||||||
height: @toolbar_line-height;
|
height: @toolbar_line-height;
|
||||||
width: @toolbar_line-height;
|
width: @toolbar_line-height;
|
||||||
@ -834,7 +852,7 @@
|
|||||||
line-height: 28px; // padding + border
|
line-height: 28px; // padding + border
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.cp-toolbar-link, .cp-toolbar-new {
|
.cp-toolbar-link, .cp-toolbar-new, .cp-toolbar-notifications {
|
||||||
font-size: 48px;
|
font-size: 48px;
|
||||||
line-height: 64px;
|
line-height: 64px;
|
||||||
width: @toolbar_top-height;
|
width: @toolbar_top-height;
|
||||||
@ -849,14 +867,13 @@
|
|||||||
}
|
}
|
||||||
transition: all 0.15s;
|
transition: all 0.15s;
|
||||||
}
|
}
|
||||||
.cp-toolbar-new {
|
.cp-toolbar-notifications, .cp-toolbar-new {
|
||||||
background-color: rgba(0,0,0,0.2);
|
background-color: rgba(0,0,0,0.2);
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(0,0,0,0.3);
|
background-color: rgba(0,0,0,0.3);
|
||||||
}
|
}
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
margin-left: 10px;
|
|
||||||
&> button {
|
&> button {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -884,6 +901,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.cp-toolbar-notifications {
|
||||||
|
margin-left: 10px;
|
||||||
|
.cp-notifications-empty {
|
||||||
|
color: black;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
position: relative;
|
||||||
|
&.fa-bell-o {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.cp-dropdown-button-title {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 16px;
|
||||||
|
&.cp-notifications-small {
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 17px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.cp-toolbar-link {
|
.cp-toolbar-link {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -917,6 +962,7 @@
|
|||||||
order: 6;
|
order: 6;
|
||||||
line-height: @toolbar_top-height;
|
line-height: @toolbar_top-height;
|
||||||
color: white;
|
color: white;
|
||||||
|
.cp-toolbar-notifications { order: 1; }
|
||||||
.cp-toolbar-new { order: 2; }
|
.cp-toolbar-new { order: 2; }
|
||||||
.cp-toolbar-user-dropdown { order: 3; }
|
.cp-toolbar-user-dropdown { order: 3; }
|
||||||
.cp-toolbar-backup { order: 4; } // TODO drive migration to secure iframe
|
.cp-toolbar-backup { order: 4; } // TODO drive migration to secure iframe
|
||||||
|
|||||||
@ -7,14 +7,7 @@ define([
|
|||||||
|
|
||||||
'/common/common-realtime.js',
|
'/common/common-realtime.js',
|
||||||
], function (Crypto, Hash, Util, Constants, Messages, Realtime) {
|
], function (Crypto, Hash, Util, Constants, Messages, Realtime) {
|
||||||
var Msg = {
|
var Msg = {};
|
||||||
inputs: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
// - mute a channel (hide notifications or don't open it?)
|
|
||||||
var pending = {};
|
|
||||||
var pendingRequests = [];
|
|
||||||
|
|
||||||
var createData = Msg.createData = function (proxy, hash) {
|
var createData = Msg.createData = function (proxy, hash) {
|
||||||
return {
|
return {
|
||||||
@ -23,11 +16,13 @@ define([
|
|||||||
profile: proxy.profile && proxy.profile.view,
|
profile: proxy.profile && proxy.profile.view,
|
||||||
edPublic: proxy.edPublic,
|
edPublic: proxy.edPublic,
|
||||||
curvePublic: proxy.curvePublic,
|
curvePublic: proxy.curvePublic,
|
||||||
|
notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']),
|
||||||
avatar: proxy.profile && proxy.profile.avatar
|
avatar: proxy.profile && proxy.profile.avatar
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
var getFriend = function (proxy, pubkey) {
|
var getFriend = Msg.getFriend = function (proxy, pubkey) {
|
||||||
|
if (!pubkey) { return; }
|
||||||
if (pubkey === proxy.curvePublic) {
|
if (pubkey === proxy.curvePublic) {
|
||||||
var data = createData(proxy);
|
var data = createData(proxy);
|
||||||
delete data.channel;
|
delete data.channel;
|
||||||
@ -56,21 +51,17 @@ define([
|
|||||||
return list;
|
return list;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO make this internal to the messenger
|
Msg.acceptFriendRequest = function (store, data, cb) {
|
||||||
var channels = Msg.channels = {};
|
var friend = getFriend(store.proxy, data.curvePublic) || {};
|
||||||
|
var myData = createData(store.proxy, friend.channel || data.channel);
|
||||||
Msg.getLatestMessages = function () {
|
store.mailbox.sendTo('ACCEPT_FRIEND_REQUEST', myData, {
|
||||||
Object.keys(channels).forEach(function (id) {
|
channel: data.notifications,
|
||||||
if (id === 'me') { return; }
|
curvePublic: data.curvePublic
|
||||||
var friend = channels[id];
|
}, function (obj) {
|
||||||
friend.getMessagesSinceDisconnect();
|
cb(obj);
|
||||||
friend.refresh();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
Msg.addToFriendList = function (cfg, data, cb) {
|
||||||
// Invitation
|
|
||||||
// FIXME there are too many functions with this name
|
|
||||||
var addToFriendList = Msg.addToFriendList = function (cfg, data, cb) {
|
|
||||||
var proxy = cfg.proxy;
|
var proxy = cfg.proxy;
|
||||||
var friends = getFriendList(proxy);
|
var friends = getFriendList(proxy);
|
||||||
var pubKey = data.curvePublic; // todo validata data
|
var pubKey = data.curvePublic; // todo validata data
|
||||||
@ -85,135 +76,6 @@ define([
|
|||||||
if (res.error) { console.error(res.error); }
|
if (res.error) { console.error(res.error); }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
cfg.updateMetadata();
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Used to accept friend requests within apps other than /contacts/ */
|
|
||||||
Msg.addDirectMessageHandler = function (cfg, href) {
|
|
||||||
var network = cfg.network;
|
|
||||||
var proxy = cfg.proxy;
|
|
||||||
if (!network) { return void console.error('Network not ready'); }
|
|
||||||
network.on('message', function (message, sender) {
|
|
||||||
var msg;
|
|
||||||
if (sender === network.historyKeeper) { return; }
|
|
||||||
try {
|
|
||||||
var parsed = Hash.parsePadUrl(href);
|
|
||||||
var secret = Hash.getSecrets(parsed.type, parsed.hash);
|
|
||||||
if (!parsed.hashData) { return; }
|
|
||||||
var chan = secret.channel;
|
|
||||||
// Decrypt
|
|
||||||
var key = secret.keys ? secret.keys.cryptKey : Hash.decodeBase64(secret.key);
|
|
||||||
var decryptMsg;
|
|
||||||
try {
|
|
||||||
decryptMsg = Crypto.decrypt(message, key);
|
|
||||||
} catch (e) {
|
|
||||||
// If we can't decrypt, it means it is not a friend request message
|
|
||||||
}
|
|
||||||
if (!decryptMsg) { return; }
|
|
||||||
// Parse
|
|
||||||
msg = JSON.parse(decryptMsg);
|
|
||||||
if (msg[1] !== chan) { return; }
|
|
||||||
var msgData = msg[2];
|
|
||||||
var msgStr;
|
|
||||||
if (msg[0] === "FRIEND_REQ") {
|
|
||||||
msg = ["FRIEND_REQ_NOK", chan];
|
|
||||||
var todo = function (yes) {
|
|
||||||
if (yes) {
|
|
||||||
pending[sender] = msgData;
|
|
||||||
msg = ["FRIEND_REQ_OK", chan, createData(proxy, msgData.channel)];
|
|
||||||
}
|
|
||||||
msgStr = Crypto.encrypt(JSON.stringify(msg), key);
|
|
||||||
network.sendto(sender, msgStr);
|
|
||||||
};
|
|
||||||
var existing = getFriend(proxy, msgData.curvePublic);
|
|
||||||
if (existing) {
|
|
||||||
todo(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var confirmMsg = Messages._getKey('contacts_request', [
|
|
||||||
Util.fixHTML(msgData.displayName)
|
|
||||||
]);
|
|
||||||
cfg.friendRequest(confirmMsg, todo);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (msg[0] === "FRIEND_REQ_OK") {
|
|
||||||
var idx = pendingRequests.indexOf(sender);
|
|
||||||
if (idx !== -1) { pendingRequests.splice(idx, 1); }
|
|
||||||
|
|
||||||
// FIXME clarify this function's name
|
|
||||||
addToFriendList(cfg, msgData, function (err) {
|
|
||||||
if (err) {
|
|
||||||
return void cfg.friendComplete({
|
|
||||||
logText: Messages.contacts_addError,
|
|
||||||
netfluxId: sender
|
|
||||||
});
|
|
||||||
}
|
|
||||||
cfg.friendComplete({
|
|
||||||
logText: Messages.contacts_added,
|
|
||||||
netfluxId: sender,
|
|
||||||
friend: msgData
|
|
||||||
});
|
|
||||||
var msg = ["FRIEND_REQ_ACK", chan];
|
|
||||||
var msgStr = Crypto.encrypt(JSON.stringify(msg), key);
|
|
||||||
network.sendto(sender, msgStr);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (msg[0] === "FRIEND_REQ_NOK") {
|
|
||||||
var i = pendingRequests.indexOf(sender);
|
|
||||||
if (i !== -1) { pendingRequests.splice(i, 1); }
|
|
||||||
cfg.friendComplete({
|
|
||||||
logText: Messages.contacts_rejected,
|
|
||||||
netfluxId: sender,
|
|
||||||
});
|
|
||||||
cfg.updateMetadata();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (msg[0] === "FRIEND_REQ_ACK") {
|
|
||||||
var data = pending[sender];
|
|
||||||
if (!data) { return; }
|
|
||||||
addToFriendList(cfg, data, function (err) {
|
|
||||||
if (err) {
|
|
||||||
return void cfg.friendComplete({
|
|
||||||
logText: Messages.contacts_addError,
|
|
||||||
netfluxId: sender
|
|
||||||
});
|
|
||||||
}
|
|
||||||
cfg.friendComplete({
|
|
||||||
logText: Messages.contacts_added,
|
|
||||||
netfluxId: sender,
|
|
||||||
friend: data
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO: timeout ACK: warn the user
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Cannot parse direct message", msg || message, "from", sender, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Msg.inviteFromUserlist = function (cfg, data, cb) {
|
|
||||||
var network = cfg.network;
|
|
||||||
var netfluxId = data.netfluxId;
|
|
||||||
var parsed = Hash.parsePadUrl(data.href);
|
|
||||||
var secret = Hash.getSecrets(parsed.type, parsed.hash);
|
|
||||||
if (!parsed.hashData) { return; }
|
|
||||||
// Message
|
|
||||||
var chan = secret.channel;
|
|
||||||
var myData = createData(cfg.proxy);
|
|
||||||
var msg = ["FRIEND_REQ", chan, myData];
|
|
||||||
// Encryption
|
|
||||||
var key = secret.keys ? secret.keys.cryptKey : Hash.decodeBase64(secret.key);
|
|
||||||
var msgStr = Crypto.encrypt(JSON.stringify(msg), key);
|
|
||||||
// Send encrypted message
|
|
||||||
if (pendingRequests.indexOf(netfluxId) === -1) {
|
|
||||||
pendingRequests.push(netfluxId);
|
|
||||||
cfg.updateMetadata(); // redraws the userlist in pad
|
|
||||||
}
|
|
||||||
network.sendto(netfluxId, msgStr);
|
|
||||||
cb();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return Msg;
|
return Msg;
|
||||||
|
|||||||
@ -68,7 +68,7 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Msg.messenger = function (store) {
|
Msg.messenger = function (store, updateMetadata) {
|
||||||
var messenger = {
|
var messenger = {
|
||||||
handlers: {
|
handlers: {
|
||||||
event: []
|
event: []
|
||||||
@ -97,6 +97,7 @@ define([
|
|||||||
stack.push(f);
|
stack.push(f);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var allowFriendsChannels = false;
|
||||||
var channels = messenger.channels = {};
|
var channels = messenger.channels = {};
|
||||||
|
|
||||||
var joining = {};
|
var joining = {};
|
||||||
@ -301,7 +302,10 @@ define([
|
|||||||
if (!proxy.friends) { return; }
|
if (!proxy.friends) { return; }
|
||||||
var friends = proxy.friends;
|
var friends = proxy.friends;
|
||||||
delete friends[curvePublic];
|
delete friends[curvePublic];
|
||||||
Realtime.whenRealtimeSyncs(realtime, cb);
|
Realtime.whenRealtimeSyncs(realtime, function () {
|
||||||
|
updateMetadata();
|
||||||
|
cb();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var pushMsg = function (channel, cryptMsg) {
|
var pushMsg = function (channel, cryptMsg) {
|
||||||
@ -771,45 +775,9 @@ define([
|
|||||||
openChannel(data);
|
openChannel(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Detect friends changes made in another worker
|
|
||||||
proxy.on('change', ['friends'], function (o, n, p) {
|
|
||||||
var curvePublic;
|
|
||||||
if (o === undefined) {
|
|
||||||
// new friend added
|
|
||||||
curvePublic = p.slice(-1)[0];
|
|
||||||
|
|
||||||
// Load channel
|
|
||||||
var friend = friends[curvePublic];
|
|
||||||
if (typeof(friend) !== 'object') { return; }
|
|
||||||
var channel = friend.channel;
|
|
||||||
if (!channel) { return; }
|
|
||||||
loadFriend(friend, function () {
|
|
||||||
emit('FRIEND', {
|
|
||||||
curvePublic: curvePublic,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof(n) === 'undefined') {
|
|
||||||
// Handled by .on('remove')
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}).on('remove', ['friends'], function (o, p) {
|
|
||||||
var curvePublic = p[1];
|
|
||||||
if (!curvePublic) { return; }
|
|
||||||
if (p[2] !== 'channel') { return; }
|
|
||||||
var channel = channels[o];
|
|
||||||
channel.wc.leave(Types.unfriend);
|
|
||||||
delete channels[channel.id];
|
|
||||||
emit('UNFRIEND', {
|
|
||||||
curvePublic: curvePublic,
|
|
||||||
fromMe: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Friend added in our contacts in the current worker
|
// Friend added in our contacts in the current worker
|
||||||
messenger.onFriendAdded = function (friendData) {
|
messenger.onFriendAdded = function (friendData) {
|
||||||
|
if (!allowFriendsChannels) { return; }
|
||||||
var friend = friends[friendData.curvePublic];
|
var friend = friends[friendData.curvePublic];
|
||||||
if (typeof(friend) !== 'object') { return; }
|
if (typeof(friend) !== 'object') { return; }
|
||||||
var channel = friend.channel;
|
var channel = friend.channel;
|
||||||
@ -820,10 +788,23 @@ define([
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
messenger.onFriendRemoved = function (curvePublic, chanId) {
|
||||||
|
var channel = channels[chanId];
|
||||||
|
if (!channel) { return; }
|
||||||
|
if (channel.wc) {
|
||||||
|
channel.wc.leave(Types.unfriend);
|
||||||
|
}
|
||||||
|
delete channels[channel.id];
|
||||||
|
emit('UNFRIEND', {
|
||||||
|
curvePublic: curvePublic,
|
||||||
|
fromMe: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var ready = false;
|
var ready = false;
|
||||||
var initialized = false;
|
var initialized = false;
|
||||||
var init = function () {
|
var init = function () {
|
||||||
|
allowFriendsChannels = true;
|
||||||
if (initialized) { return; }
|
if (initialized) { return; }
|
||||||
initialized = true;
|
initialized = true;
|
||||||
var friends = getFriendList(proxy);
|
var friends = getFriendList(proxy);
|
||||||
|
|||||||
@ -1426,7 +1426,7 @@ define([
|
|||||||
return /HTML/.test(Object.prototype.toString.call(o)) &&
|
return /HTML/.test(Object.prototype.toString.call(o)) &&
|
||||||
typeof(o.tagName) === 'string';
|
typeof(o.tagName) === 'string';
|
||||||
};
|
};
|
||||||
var allowedTags = ['a', 'p', 'hr'];
|
var allowedTags = ['a', 'p', 'hr', 'div'];
|
||||||
var isValidOption = function (o) {
|
var isValidOption = function (o) {
|
||||||
if (typeof o !== "object") { return false; }
|
if (typeof o !== "object") { return false; }
|
||||||
if (isElement(o)) { return true; }
|
if (isElement(o)) { return true; }
|
||||||
@ -2608,5 +2608,48 @@ define([
|
|||||||
return m;
|
return m;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UIElements.displayFriendRequestModal = function (common, data) {
|
||||||
|
var msg = data.content.msg;
|
||||||
|
var text = Messages._getKey('contacts_request', [msg.content.displayName]);
|
||||||
|
|
||||||
|
var todo = function (yes) {
|
||||||
|
common.getSframeChannel().query("Q_ANSWER_FRIEND_REQUEST", {
|
||||||
|
data: data,
|
||||||
|
value: yes
|
||||||
|
}, function (err, obj) {
|
||||||
|
var error = err || (obj && obj.error);
|
||||||
|
if (error) {
|
||||||
|
return void UI.warn(error);
|
||||||
|
}
|
||||||
|
UI.log(Messages.contacts_added);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var content = h('div.cp-share-modal', [
|
||||||
|
setHTML(h('p'), text)
|
||||||
|
]);
|
||||||
|
var buttons = [{
|
||||||
|
name: Messages.friendRequest_later,
|
||||||
|
onClick: function () {},
|
||||||
|
keys: [27]
|
||||||
|
}, {
|
||||||
|
className: 'primary',
|
||||||
|
name: Messages.friendRequest_accept,
|
||||||
|
onClick: function () {
|
||||||
|
todo(true);
|
||||||
|
},
|
||||||
|
keys: [13]
|
||||||
|
}, {
|
||||||
|
className: 'primary',
|
||||||
|
name: Messages.friendRequest_decline,
|
||||||
|
onClick: function () {
|
||||||
|
todo(false);
|
||||||
|
},
|
||||||
|
keys: [[13, 'ctrl']]
|
||||||
|
}];
|
||||||
|
var modal = UI.dialog.customModal(content, {buttons: buttons});
|
||||||
|
UI.openCustomModal(modal);
|
||||||
|
};
|
||||||
|
|
||||||
return UIElements;
|
return UIElements;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -611,16 +611,6 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Messaging (manage friends from the userlist)
|
|
||||||
common.inviteFromUserlist = function (netfluxId, cb) {
|
|
||||||
postMessage("INVITE_FROM_USERLIST", {
|
|
||||||
netfluxId: netfluxId,
|
|
||||||
href: window.location.href
|
|
||||||
}, function (obj) {
|
|
||||||
if (obj && obj.error) { return void cb(obj.error); }
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Admin
|
// Admin
|
||||||
common.adminRpc = function (data, cb) {
|
common.adminRpc = function (data, cb) {
|
||||||
@ -632,14 +622,13 @@ define([
|
|||||||
common.onNetworkReconnect = Util.mkEvent();
|
common.onNetworkReconnect = Util.mkEvent();
|
||||||
common.onNewVersionReconnect = Util.mkEvent();
|
common.onNewVersionReconnect = Util.mkEvent();
|
||||||
|
|
||||||
// Messaging
|
// Messaging (friend requests)
|
||||||
var messaging = common.messaging = {};
|
var messaging = common.messaging = {};
|
||||||
messaging.onFriendRequest = Util.mkEvent();
|
messaging.answerFriendRequest = function (data, cb) {
|
||||||
messaging.onFriendComplete = Util.mkEvent();
|
postMessage("ANSWER_FRIEND_REQUEST", data, cb);
|
||||||
messaging.addHandlers = function (href) {
|
};
|
||||||
postMessage("ADD_DIRECT_MESSAGE_HANDLERS", {
|
messaging.sendFriendRequest = function (data, cb) {
|
||||||
href: href
|
postMessage("SEND_FRIEND_REQUEST", data, cb);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Onlyoffice
|
// Onlyoffice
|
||||||
@ -1088,9 +1077,6 @@ define([
|
|||||||
var localToken = tryParsing(localStorage.getItem(Constants.tokenKey));
|
var localToken = tryParsing(localStorage.getItem(Constants.tokenKey));
|
||||||
if (localToken !== data.token) { requestLogin(); }
|
if (localToken !== data.token) { requestLogin(); }
|
||||||
},
|
},
|
||||||
// Messaging
|
|
||||||
Q_FRIEND_REQUEST: common.messaging.onFriendRequest.fire,
|
|
||||||
EV_FRIEND_COMPLETE: common.messaging.onFriendComplete.fire,
|
|
||||||
// Network
|
// Network
|
||||||
NETWORK_DISCONNECT: common.onNetworkDisconnect.fire,
|
NETWORK_DISCONNECT: common.onNetworkDisconnect.fire,
|
||||||
NETWORK_RECONNECT: function (data) {
|
NETWORK_RECONNECT: function (data) {
|
||||||
|
|||||||
58
www/common/notifications.js
Normal file
58
www/common/notifications.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'/common/hyperscript.js',
|
||||||
|
'/common/common-ui-elements.js',
|
||||||
|
'/customize/messages.js',
|
||||||
|
], function ($, h, UIElements, Messages) {
|
||||||
|
|
||||||
|
var handlers = {};
|
||||||
|
|
||||||
|
handlers['FRIEND_REQUEST'] = function (common, data, el) {
|
||||||
|
var content = data.content;
|
||||||
|
var msg = content.msg;
|
||||||
|
|
||||||
|
// Check authenticity
|
||||||
|
if (msg.author !== msg.content.curvePublic) { return; }
|
||||||
|
|
||||||
|
common.addFriendRequest(data);
|
||||||
|
|
||||||
|
// Display the notification
|
||||||
|
$(el).find('.cp-notification-content').addClass("cp-clickable");
|
||||||
|
$(el).find('.cp-notification-content p')
|
||||||
|
.html(Messages._getKey('friendRequest_notification', [msg.content.displayName])
|
||||||
|
.click(function () {
|
||||||
|
UIElements.displayFriendRequestModal(common, data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handlers['ACCEPT_FRIEND_REQUEST'] = function (common, data, el) {
|
||||||
|
var content = data.content;
|
||||||
|
var msg = content.msg;
|
||||||
|
$(el).find('.cp-notification-content p')
|
||||||
|
.html(Messages._getKey('friendRequest_accepted', [msg.content.displayName])
|
||||||
|
$(el).find('.cp-notification-dismiss').css('display', 'flex');
|
||||||
|
};
|
||||||
|
|
||||||
|
handlers['DECLINE_FRIEND_REQUEST'] = function (common, data, el) {
|
||||||
|
var content = data.content;
|
||||||
|
var msg = content.msg;
|
||||||
|
$(el).find('.cp-notification-content p')
|
||||||
|
.html(Messages._getKey('friendRequest_declined', [msg.content.displayName])
|
||||||
|
$(el).find('.cp-notification-dismiss').css('display', 'flex');
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
add: function (common, data, el) {
|
||||||
|
var type = data.content.msg.type;
|
||||||
|
|
||||||
|
if (handlers[type]) {
|
||||||
|
handlers[type](common, data, el);
|
||||||
|
} else {
|
||||||
|
$(el).find('.cp-notification-dismiss').css('display', 'flex');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remove: function (common, data) {
|
||||||
|
common.removeFriendRequest(data.hash);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
@ -123,6 +123,13 @@ define([
|
|||||||
list = list.concat(fList);
|
list = list.concat(fList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (store.proxy.mailboxes) {
|
||||||
|
var mList = Object.keys(store.proxy.mailboxes).map(function (m) {
|
||||||
|
return store.proxy.mailboxes[m].channel;
|
||||||
|
});
|
||||||
|
list = list.concat(mList);
|
||||||
|
}
|
||||||
|
|
||||||
list.push(userChannel);
|
list.push(userChannel);
|
||||||
list.sort();
|
list.sort();
|
||||||
|
|
||||||
@ -451,6 +458,7 @@ define([
|
|||||||
avatar: Util.find(store.proxy, ['profile', 'avatar']),
|
avatar: Util.find(store.proxy, ['profile', 'avatar']),
|
||||||
profile: Util.find(store.proxy, ['profile', 'view']),
|
profile: Util.find(store.proxy, ['profile', 'view']),
|
||||||
color: getUserColor(),
|
color: getUserColor(),
|
||||||
|
notifications: Util.find(store.proxy, ['mailboxes', 'notifications', 'channel']),
|
||||||
curvePublic: store.proxy.curvePublic,
|
curvePublic: store.proxy.curvePublic,
|
||||||
},
|
},
|
||||||
// "priv" is not shared with other users but is needed by the apps
|
// "priv" is not shared with other users but is needed by the apps
|
||||||
@ -460,7 +468,8 @@ define([
|
|||||||
friends: store.proxy.friends || {},
|
friends: store.proxy.friends || {},
|
||||||
settings: store.proxy.settings,
|
settings: store.proxy.settings,
|
||||||
thumbnails: disableThumbnails === false,
|
thumbnails: disableThumbnails === false,
|
||||||
isDriveOwned: Boolean(Util.find(store, ['driveMetadata', 'owners']))
|
isDriveOwned: Boolean(Util.find(store, ['driveMetadata', 'owners'])),
|
||||||
|
pendingFriends: store.proxy.friends_pending || {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cb(JSON.parse(JSON.stringify(metadata)));
|
cb(JSON.parse(JSON.stringify(metadata)));
|
||||||
@ -626,6 +635,27 @@ define([
|
|||||||
|
|
||||||
// Set the display name (username) in the proxy
|
// Set the display name (username) in the proxy
|
||||||
Store.setDisplayName = function (clientId, value, cb) {
|
Store.setDisplayName = function (clientId, value, cb) {
|
||||||
|
if (store.mailbox && store.proxy.friends) {
|
||||||
|
// XXX test mailbox, should be removed in prod
|
||||||
|
/*store.mailbox.post('notifications', 'NAME_CHANGED', {
|
||||||
|
old: store.proxy[Constants.displayNameKey],
|
||||||
|
new: value
|
||||||
|
});
|
||||||
|
Object.keys(store.proxy.friends).forEach(function (curve) {
|
||||||
|
var f = store.proxy.friends[curve];
|
||||||
|
if (!f.notifications) { return; }
|
||||||
|
store.mailbox.sendTo('NAME_CHANGED', {
|
||||||
|
old: store.proxy[Constants.displayNameKey],
|
||||||
|
new: value
|
||||||
|
}, {
|
||||||
|
channel: f.notifications,
|
||||||
|
curvePublic: curve
|
||||||
|
}, function (obj) {
|
||||||
|
if (obj && obj.error) { return void console.error(obj.error); }
|
||||||
|
console.log('notif sent to '+f);
|
||||||
|
});
|
||||||
|
});*/
|
||||||
|
}
|
||||||
store.proxy[Constants.displayNameKey] = value;
|
store.proxy[Constants.displayNameKey] = value;
|
||||||
broadcast([clientId], "UPDATE_METADATA");
|
broadcast([clientId], "UPDATE_METADATA");
|
||||||
if (store.messenger) { store.messenger.updateMyData(); }
|
if (store.messenger) { store.messenger.updateMyData(); }
|
||||||
@ -881,36 +911,73 @@ define([
|
|||||||
|
|
||||||
|
|
||||||
// Messaging (manage friends from the userlist)
|
// Messaging (manage friends from the userlist)
|
||||||
var getMessagingCfg = function (clientId) {
|
Store.answerFriendRequest = function (clientId, obj, cb) {
|
||||||
return {
|
var value = obj.value;
|
||||||
proxy: store.proxy,
|
var data = obj.data;
|
||||||
realtime: store.realtime,
|
if (data.type !== 'notifications') { return void cb ({error: 'EINVAL'}); }
|
||||||
network: store.network,
|
var hash = data.content.hash;
|
||||||
updateMetadata: function () {
|
var msg = data.content.msg;
|
||||||
postMessage(clientId, "UPDATE_METADATA");
|
|
||||||
},
|
|
||||||
pinPads: function (data, cb) { Store.pinPads(null, data, cb); },
|
|
||||||
friendComplete: function (data) {
|
|
||||||
if (data.friend && store.messenger && store.messenger.onFriendAdded) {
|
|
||||||
store.messenger.onFriendAdded(data.friend);
|
|
||||||
}
|
|
||||||
postMessage(clientId, "EV_FRIEND_COMPLETE", data);
|
|
||||||
},
|
|
||||||
friendRequest: function (data, cb) {
|
|
||||||
postMessage(clientId, "Q_FRIEND_REQUEST", data, cb);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
Store.inviteFromUserlist = function (clientId, data, cb) {
|
|
||||||
var messagingCfg = getMessagingCfg(clientId);
|
|
||||||
Messaging.inviteFromUserlist(messagingCfg, data, cb);
|
|
||||||
};
|
|
||||||
Store.addDirectMessageHandlers = function (clientId, data) {
|
|
||||||
var messagingCfg = getMessagingCfg(clientId);
|
|
||||||
Messaging.addDirectMessageHandler(messagingCfg, data.href);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Messenger
|
var dismiss = function (cb) {
|
||||||
|
cb = cb || function () {};
|
||||||
|
store.mailbox.dismiss({
|
||||||
|
hash: hash,
|
||||||
|
type: 'notifications'
|
||||||
|
}, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we accept the request, add the friend to the list
|
||||||
|
if (value) {
|
||||||
|
Messaging.acceptFriendRequest(store, msg.content, function (obj) {
|
||||||
|
if (obj && obj.error) { return void cb(obj); }
|
||||||
|
Messaging.addToFriendList({
|
||||||
|
proxy: store.proxy,
|
||||||
|
realtime: store.realtime,
|
||||||
|
pinPads: function (data, cb) { Store.pinPads(null, data, cb); },
|
||||||
|
}, msg.content, function (err) {
|
||||||
|
if (store.messenger) {
|
||||||
|
store.messenger.onFriendAdded(msg.content);
|
||||||
|
}
|
||||||
|
broadcast([], "UPDATE_METADATA");
|
||||||
|
if (err) { return void cb({error: err}); }
|
||||||
|
dismiss(cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Otherwise, just remove the notification
|
||||||
|
store.mailbox.sendTo('DECLINE_FRIEND_REQUEST', {}, {
|
||||||
|
channel: msg.content.notifications,
|
||||||
|
curvePublic: msg.content.curvePublic
|
||||||
|
}, function (obj) {
|
||||||
|
cb(obj);
|
||||||
|
});
|
||||||
|
dismiss();
|
||||||
|
};
|
||||||
|
Store.sendFriendRequest = function (clientId, data, cb) {
|
||||||
|
var friend = Messaging.getFriend(store.proxy, data.curvePublic);
|
||||||
|
if (friend) { return void cb({error: 'ALREADY_FRIEND'}); }
|
||||||
|
if (!data.notifications || !data.curvePublic) { return void cb({error: 'INVALID_USER'}); }
|
||||||
|
|
||||||
|
store.proxy.friends_pending = store.proxy.friends_pending || {};
|
||||||
|
|
||||||
|
var twoDaysAgo = +new Date() - (2 * 24 * 3600 * 1000);
|
||||||
|
if (store.proxy.friends_pending[data.curvePublic] &&
|
||||||
|
store.proxy.friends_pending[data.curvePublic] > twoDaysAgo) {
|
||||||
|
return void cb({error: 'TIMEOUT'});
|
||||||
|
}
|
||||||
|
|
||||||
|
store.proxy.friends_pending[data.curvePublic] = +new Date();
|
||||||
|
broadcast([], "UPDATE_METADATA");
|
||||||
|
|
||||||
|
var myData = Messaging.createData(store.proxy);
|
||||||
|
store.mailbox.sendTo('FRIEND_REQUEST', myData, {
|
||||||
|
channel: data.notifications,
|
||||||
|
curvePublic: data.curvePublic
|
||||||
|
}, function (obj) {
|
||||||
|
cb(obj);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Get hashes for the share button
|
// Get hashes for the share button
|
||||||
Store.getStrongerHash = function (clientId, data, cb) {
|
Store.getStrongerHash = function (clientId, data, cb) {
|
||||||
@ -925,6 +992,7 @@ define([
|
|||||||
cb();
|
cb();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Messenger
|
||||||
Store.messenger = {
|
Store.messenger = {
|
||||||
execCommand: function (clientId, data, cb) {
|
execCommand: function (clientId, data, cb) {
|
||||||
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
|
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
|
||||||
@ -951,6 +1019,7 @@ define([
|
|||||||
// Mailbox
|
// Mailbox
|
||||||
Store.mailbox = {
|
Store.mailbox = {
|
||||||
execCommand: function (clientId, data, cb) {
|
execCommand: function (clientId, data, cb) {
|
||||||
|
if (!store.loggedIn) { return void cb(); }
|
||||||
if (!store.mailbox) { return void cb ({error: 'Mailbox is disabled'}); }
|
if (!store.mailbox) { return void cb ({error: 'Mailbox is disabled'}); }
|
||||||
store.mailbox.execCommand(clientId, data, cb);
|
store.mailbox.execCommand(clientId, data, cb);
|
||||||
}
|
}
|
||||||
@ -1314,13 +1383,15 @@ define([
|
|||||||
if (messengerIdx !== -1) {
|
if (messengerIdx !== -1) {
|
||||||
messengerEventClients.splice(messengerIdx, 1);
|
messengerEventClients.splice(messengerIdx, 1);
|
||||||
}
|
}
|
||||||
// TODO mailbox events
|
|
||||||
try {
|
try {
|
||||||
store.cursor.removeClient(clientId);
|
store.cursor.removeClient(clientId);
|
||||||
} catch (e) { console.error(e); }
|
} catch (e) { console.error(e); }
|
||||||
try {
|
try {
|
||||||
store.onlyoffice.removeClient(clientId);
|
store.onlyoffice.removeClient(clientId);
|
||||||
} catch (e) { console.error(e); }
|
} catch (e) { console.error(e); }
|
||||||
|
try {
|
||||||
|
store.mailbox.removeClient(clientId);
|
||||||
|
} catch (e) { console.error(e); }
|
||||||
|
|
||||||
Object.keys(Store.channels).forEach(function (chanId) {
|
Object.keys(Store.channels).forEach(function (chanId) {
|
||||||
var chanIdx = Store.channels[chanId].clients.indexOf(clientId);
|
var chanIdx = Store.channels[chanId].clients.indexOf(clientId);
|
||||||
@ -1385,7 +1456,9 @@ define([
|
|||||||
};
|
};
|
||||||
var loadMessenger = function () {
|
var loadMessenger = function () {
|
||||||
if (AppConfig.availablePadTypes.indexOf('contacts') === -1) { return; }
|
if (AppConfig.availablePadTypes.indexOf('contacts') === -1) { return; }
|
||||||
var messenger = store.messenger = Messenger.messenger(store);
|
var messenger = store.messenger = Messenger.messenger(store, function () {
|
||||||
|
broadcast([], "UPDATE_METADATA");
|
||||||
|
});
|
||||||
messenger.on('event', function (ev, data) {
|
messenger.on('event', function (ev, data) {
|
||||||
sendMessengerEvent('CHAT_EVENT', {
|
sendMessengerEvent('CHAT_EVENT', {
|
||||||
ev: ev,
|
ev: ev,
|
||||||
@ -1417,7 +1490,16 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
var loadMailbox = function (waitFor) {
|
var loadMailbox = function (waitFor) {
|
||||||
store.mailbox = Mailbox.init(store, waitFor, function (ev, data, clients) {
|
if (!store.loggedIn || !store.proxy.edPublic) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
store.mailbox = Mailbox.init({
|
||||||
|
store: store,
|
||||||
|
updateMetadata: function () {
|
||||||
|
broadcast([], "UPDATE_METADATA");
|
||||||
|
},
|
||||||
|
pinPads: function (data, cb) { Store.pinPads(null, data, cb); },
|
||||||
|
}, waitFor, function (ev, data, clients) {
|
||||||
clients.forEach(function (cId) {
|
clients.forEach(function (cId) {
|
||||||
postMessage(cId, 'MAILBOX_EVENT', {
|
postMessage(cId, 'MAILBOX_EVENT', {
|
||||||
ev: ev,
|
ev: ev,
|
||||||
@ -1427,6 +1509,18 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var cleanFriendRequests = function () {
|
||||||
|
try {
|
||||||
|
if (!store.proxy.friends_pending) { return; }
|
||||||
|
var twoDaysAgo = +new Date() - (2 * 24 * 3600 * 1000);
|
||||||
|
Object.keys(store.proxy.friends_pending).forEach(function (curve) {
|
||||||
|
if (store.proxy.friends_pending[curve] < twoDaysAgo) {
|
||||||
|
delete store.proxy.friends_pending[curve];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {}
|
||||||
|
};
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
/////////////////////// Init /////////////////////////////////////
|
/////////////////////// Init /////////////////////////////////////
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
@ -1522,6 +1616,7 @@ define([
|
|||||||
loadCursor();
|
loadCursor();
|
||||||
loadOnlyOffice();
|
loadOnlyOffice();
|
||||||
loadMailbox(waitFor);
|
loadMailbox(waitFor);
|
||||||
|
cleanFriendRequests();
|
||||||
}).nThen(function () {
|
}).nThen(function () {
|
||||||
var requestLogin = function () {
|
var requestLogin = function () {
|
||||||
broadcast([], "REQUEST_LOGIN");
|
broadcast([], "REQUEST_LOGIN");
|
||||||
@ -1575,9 +1670,31 @@ define([
|
|||||||
// Trigger userlist update when the avatar has changed
|
// Trigger userlist update when the avatar has changed
|
||||||
broadcast([], "UPDATE_METADATA");
|
broadcast([], "UPDATE_METADATA");
|
||||||
});
|
});
|
||||||
proxy.on('change', ['friends'], function () {
|
proxy.on('change', ['friends'], function (o, n, p) {
|
||||||
// Trigger userlist update when the friendlist has changed
|
// Trigger userlist update when the friendlist has changed
|
||||||
broadcast([], "UPDATE_METADATA");
|
broadcast([], "UPDATE_METADATA");
|
||||||
|
|
||||||
|
if (!store.messenger) { return; }
|
||||||
|
if (o !== undefined) { return; }
|
||||||
|
var curvePublic = p.slice(-1)[0];
|
||||||
|
var friend = proxy.friends && proxy.friends[curvePublic];
|
||||||
|
store.messenger.onFriendAdded(friend);
|
||||||
|
});
|
||||||
|
proxy.on('remove', ['friends'], function (o, p) {
|
||||||
|
broadcast([], "UPDATE_METADATA");
|
||||||
|
|
||||||
|
if (!store.messenger) { return; }
|
||||||
|
var curvePublic = p[1];
|
||||||
|
if (!curvePublic) { return; }
|
||||||
|
if (p[2] !== 'channel') { return; }
|
||||||
|
store.messenger.onFriendRemoved(curvePublic, o);
|
||||||
|
});
|
||||||
|
proxy.on('change', ['friends_pending'], function () {
|
||||||
|
// Trigger userlist update when the friendlist has changed
|
||||||
|
broadcast([], "UPDATE_METADATA");
|
||||||
|
});
|
||||||
|
proxy.on('remove', ['friends_pending'], function () {
|
||||||
|
broadcast([], "UPDATE_METADATA");
|
||||||
});
|
});
|
||||||
proxy.on('change', ['settings'], function () {
|
proxy.on('change', ['settings'], function () {
|
||||||
broadcast([], "UPDATE_METADATA");
|
broadcast([], "UPDATE_METADATA");
|
||||||
|
|||||||
59
www/common/outer/mailbox-handlers.js
Normal file
59
www/common/outer/mailbox-handlers.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
define([
|
||||||
|
'/common/common-messaging.js',
|
||||||
|
], function (Messaging) {
|
||||||
|
|
||||||
|
var handlers = {};
|
||||||
|
|
||||||
|
handlers['FRIEND_REQUEST'] = function (ctx, data, cb) {
|
||||||
|
if (data.msg.author === data.msg.content.curvePublic &&
|
||||||
|
Messaging.getFriend(ctx.store.proxy, data.msg.author)) {
|
||||||
|
Messaging.acceptFriendRequest(ctx.store, data.msg.content, function (obj) {
|
||||||
|
if (obj && obj.error) { return void cb(); }
|
||||||
|
cb(true);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
handlers['DECLINE_FRIEND_REQUEST'] = function (ctx, box, data, cb) {
|
||||||
|
// Our friend request was declined.
|
||||||
|
if (!ctx.store.proxy.friends_pending[data.msg.author]) { return void cb(true); }
|
||||||
|
delete ctx.store.proxy.friends_pending[data.msg.author];
|
||||||
|
ctx.updateMetadata();
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
handlers['ACCEPT_FRIEND_REQUEST'] = function (ctx, box, data, cb) {
|
||||||
|
// Our friend request was accepted.
|
||||||
|
// Make sure we really sent it
|
||||||
|
if (!ctx.store.proxy.friends_pending[data.msg.author]) { return void cb(); }
|
||||||
|
// And add the friend
|
||||||
|
Messaging.addToFriendList({
|
||||||
|
proxy: ctx.store.proxy,
|
||||||
|
realtime: ctx.store.realtime,
|
||||||
|
pinPads: ctx.pinPads
|
||||||
|
}, data.msg.content, function (err) {
|
||||||
|
if (err) { console.error(err); }
|
||||||
|
delete ctx.store.proxy.friends_pending[data.msg.author];
|
||||||
|
if (ctx.store.messenger) {
|
||||||
|
ctx.store.messenger.onFriendAdded(data.msg.content);
|
||||||
|
}
|
||||||
|
ctx.updateMetadata();
|
||||||
|
});
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
|
||||||
|
return function (ctx, box, data, cb) {
|
||||||
|
var type = data.msg.type;
|
||||||
|
|
||||||
|
if (handlers[type]) {
|
||||||
|
try {
|
||||||
|
handlers[type](ctx, box, data, cb);
|
||||||
|
} catch (e) {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
@ -1,31 +1,395 @@
|
|||||||
// jshint ignore: start
|
|
||||||
define([
|
define([
|
||||||
'/common/common-util.js',
|
'/common/common-util.js',
|
||||||
'/common/common-constants.js',
|
'/common/common-hash.js',
|
||||||
'/customize/messages.js',
|
'/common/common-realtime.js',
|
||||||
|
'/common/outer/mailbox-handlers.js',
|
||||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||||
'/bower_components/chainpad-crypto/crypto.js',
|
'/bower_components/chainpad-crypto/crypto.js',
|
||||||
], function (Util, Constants, Messages, CpNetflux, Crypto) {
|
], function (Util, Hash, Realtime, Handlers, CpNetflux, Crypto) {
|
||||||
var Mailbox = {};
|
var Mailbox = {};
|
||||||
|
|
||||||
Mailbox.init = function (store, waitFor, emit) {
|
var TYPES = [
|
||||||
|
'notifications',
|
||||||
|
'test'
|
||||||
|
];
|
||||||
|
var BLOCKING_TYPES = [
|
||||||
|
];
|
||||||
|
|
||||||
|
var initializeMailboxes = function (ctx, mailboxes) {
|
||||||
|
if (!mailboxes['notifications']) {
|
||||||
|
mailboxes.notifications = {
|
||||||
|
channel: Hash.createChannelId(),
|
||||||
|
lastKnownHash: '',
|
||||||
|
viewed: []
|
||||||
|
};
|
||||||
|
ctx.pinPads([mailboxes.notifications.channel], function (res) {
|
||||||
|
if (res.error) { console.error(res); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
proxy.mailboxes = {
|
||||||
|
friends: {
|
||||||
|
channel: '',
|
||||||
|
lastKnownHash: '',
|
||||||
|
viewed: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
var isMessageNew = function (hash, m) {
|
||||||
|
return (m.viewed || []).indexOf(hash) === -1 && hash !== m.lastKnownHash;
|
||||||
|
};
|
||||||
|
|
||||||
|
var showMessage = function (ctx, type, msg, cId) {
|
||||||
|
ctx.emit('MESSAGE', {
|
||||||
|
type: type,
|
||||||
|
content: msg
|
||||||
|
}, cId ? [cId] : ctx.clients);
|
||||||
|
};
|
||||||
|
|
||||||
|
var getMyKeys = function (ctx) {
|
||||||
|
var proxy = ctx.store && ctx.store.proxy;
|
||||||
|
if (!proxy.curvePrivate || !proxy.curvePublic) { return; }
|
||||||
|
return {
|
||||||
|
curvePrivate: proxy.curvePrivate,
|
||||||
|
curvePublic: proxy.curvePublic
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send a message to someone else
|
||||||
|
var sendTo = function (ctx, type, msg, user, cb) {
|
||||||
|
if (!Crypto.Mailbox) {
|
||||||
|
return void cb({error: "chainpad-crypto is outdated and doesn't support mailboxes."});
|
||||||
|
}
|
||||||
|
var keys = getMyKeys(ctx);
|
||||||
|
if (!keys) { return void cb({error: "missing asymmetric encryption keys"}); }
|
||||||
|
if (!user || !user.channel || !user.curvePublic) { return void cb({error: "no notification channel"}); }
|
||||||
|
|
||||||
|
var crypto = Crypto.Mailbox.createEncryptor(keys);
|
||||||
|
var network = ctx.store.network;
|
||||||
|
|
||||||
|
var ciphertext = crypto.encrypt(JSON.stringify({
|
||||||
|
type: type,
|
||||||
|
content: msg
|
||||||
|
}), user.curvePublic);
|
||||||
|
|
||||||
|
network.join(user.channel).then(function (wc) {
|
||||||
|
wc.bcast(ciphertext).then(function () {
|
||||||
|
cb();
|
||||||
|
wc.leave();
|
||||||
|
});
|
||||||
|
}, function (err) {
|
||||||
|
cb({error: err});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var updateLastKnownHash = function (ctx, type) {
|
||||||
|
var m = Util.find(ctx, ['store', 'proxy', 'mailboxes', type]);
|
||||||
|
if (!m) { return; }
|
||||||
|
var box = ctx.boxes[type];
|
||||||
|
if (!box) { return; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mark a message as read
|
||||||
|
var dismiss = function (ctx, data, cId, cb) {
|
||||||
|
var type = data.type;
|
||||||
|
var hash = data.hash;
|
||||||
|
var m = Util.find(ctx, ['store', 'proxy', 'mailboxes', type]);
|
||||||
|
if (!m) { return void cb({error: 'NOT_FOUND'}); }
|
||||||
|
var box = ctx.boxes[type];
|
||||||
|
if (!box) { return void cb({error: 'NOT_LOADED'}); }
|
||||||
|
|
||||||
|
// If the hash in in our history, get the index from the history:
|
||||||
|
// - if the index is 0, we can change our lastKnownHash
|
||||||
|
// - otherwise, just push to view
|
||||||
|
var idx;
|
||||||
|
if (box.history.some(function (el, i) {
|
||||||
|
if (hash === el) {
|
||||||
|
idx = i;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})) {
|
||||||
|
if (idx === 0) {
|
||||||
|
m.lastKnownHash = hash;
|
||||||
|
box.history.shift();
|
||||||
|
delete box.content[hash];
|
||||||
|
} else if (m.viewed.indexOf(hash) === -1) {
|
||||||
|
m.viewed.push(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear data in memory if needed
|
||||||
|
// Check the "viewed" array to see if we're able to bump lastKnownhash more
|
||||||
|
var sliceIdx;
|
||||||
|
var lastKnownHash;
|
||||||
|
box.history.some(function (hash, i) {
|
||||||
|
var isViewed = m.viewed.indexOf(hash);
|
||||||
|
if (isViewed !== -1) {
|
||||||
|
sliceIdx = i + 1;
|
||||||
|
m.viewed.splice(isViewed, 1);
|
||||||
|
lastKnownHash = hash;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sliceIdx) {
|
||||||
|
box.history = box.history.slice(sliceIdx);
|
||||||
|
m.lastKnownHash = lastKnownHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we remove data about dismissed messages
|
||||||
|
Object.keys(box.content).forEach(function (h) {
|
||||||
|
if (box.history.indexOf(h) === -1 || m.viewed.indexOf(h) !== -1) {
|
||||||
|
delete box.content[h];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Realtime.whenRealtimeSyncs(ctx.store.realtime, function () {
|
||||||
|
cb();
|
||||||
|
ctx.emit('VIEWED', {
|
||||||
|
type: type,
|
||||||
|
hash: hash
|
||||||
|
}, ctx.clients.filter(function (clientId) {
|
||||||
|
return clientId !== cId;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var openChannel = function (ctx, type, m, onReady) {
|
||||||
|
var box = ctx.boxes[type] = {
|
||||||
|
queue: [], // Store the messages to send when the channel is ready
|
||||||
|
history: [], // All the hashes loaded from the server in corretc order
|
||||||
|
content: {}, // Content of the messages that should be displayed
|
||||||
|
sendMessage: function (msg) { // To send a message to our box
|
||||||
|
try {
|
||||||
|
msg = JSON.stringify(msg);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
box.queue.push(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Crypto = Crypto;
|
||||||
|
if (!Crypto.Mailbox) {
|
||||||
|
return void console.error("chainpad-crypto is outdated and doesn't support mailboxes.");
|
||||||
|
}
|
||||||
|
var keys = getMyKeys(ctx);
|
||||||
|
if (!keys) { return void console.error("missing asymmetric encryption keys"); }
|
||||||
|
var crypto = Crypto.Mailbox.createEncryptor(keys);
|
||||||
|
// XXX remove 'test'
|
||||||
|
if (type === 'test') {
|
||||||
|
crypto = {
|
||||||
|
encrypt: function (x) { return x; },
|
||||||
|
decrypt: function (x) { return x; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var cfg = {
|
||||||
|
network: ctx.store.network,
|
||||||
|
channel: m.channel,
|
||||||
|
noChainPad: true,
|
||||||
|
crypto: crypto,
|
||||||
|
owners: [ctx.store.proxy.edPublic],
|
||||||
|
lastKnownHash: m.lastKnownHash
|
||||||
|
};
|
||||||
|
cfg.onConnect = function (wc, sendMessage) {
|
||||||
|
// Send a message to our box?
|
||||||
|
// NOTE: we use our own curvePublic so that we can decrypt our own message :)
|
||||||
|
box.sendMessage = function (msg) {
|
||||||
|
try {
|
||||||
|
msg = JSON.stringify(msg);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
sendMessage(msg, function (err, hash) {
|
||||||
|
if (m.viewed.indexOf(hash) === -1) {
|
||||||
|
m.viewed.push(hash);
|
||||||
|
}
|
||||||
|
}, keys.curvePublic);
|
||||||
|
};
|
||||||
|
box.queue.forEach(function (msg) {
|
||||||
|
box.sendMessage(msg);
|
||||||
|
});
|
||||||
|
box.queue = [];
|
||||||
|
};
|
||||||
|
cfg.onMessage = function (msg, user, vKey, isCp, hash, author) {
|
||||||
|
if (hash === m.lastKnownHash) { return; }
|
||||||
|
try {
|
||||||
|
msg = JSON.parse(msg);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
if (author) { msg.author = author; }
|
||||||
|
box.history.push(hash);
|
||||||
|
if (isMessageNew(hash, m)) {
|
||||||
|
// Message should be displayed
|
||||||
|
var message = {
|
||||||
|
msg: msg,
|
||||||
|
hash: hash
|
||||||
|
};
|
||||||
|
Handlers(ctx, box, message, function (toDismiss) {
|
||||||
|
if (toDismiss) {
|
||||||
|
dismiss(ctx, {
|
||||||
|
type: type,
|
||||||
|
hash: hash
|
||||||
|
}, '', function () {
|
||||||
|
console.log('Notification handled automatically');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
box.content[hash] = msg;
|
||||||
|
showMessage(ctx, type, message);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Message has already been viewed by the user
|
||||||
|
if (Object.keys(box.content).length === 0) {
|
||||||
|
// If nothing is displayed yet, we can bump our lastKnownHash and remove this hash
|
||||||
|
// from our "viewed" array
|
||||||
|
m.lastKnownHash = hash;
|
||||||
|
box.history = [];
|
||||||
|
var idxViewed = m.viewed.indexOf(hash);
|
||||||
|
if (idxViewed !== -1) { m.viewed.splice(idxViewed, 1); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cfg.onReady = function () {
|
||||||
|
// Clean the "viewed" array: make sure all the "viewed" hashes are
|
||||||
|
// in history
|
||||||
|
var toClean = [];
|
||||||
|
m.viewed.forEach(function (h, i) {
|
||||||
|
if (box.history.indexOf(h) === -1) {
|
||||||
|
toClean.push(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (var i = toClean.length-1; i>=0; i--) {
|
||||||
|
m.viewed.splice(toClean[i], 1);
|
||||||
|
}
|
||||||
|
// Listen for changes in the "viewed" and lastKnownHash values
|
||||||
|
var view = function (h) {
|
||||||
|
delete box.content[h];
|
||||||
|
ctx.emit('VIEWED', {
|
||||||
|
type: type,
|
||||||
|
hash: h
|
||||||
|
}, ctx.clients);
|
||||||
|
};
|
||||||
|
ctx.store.proxy.on('change', ['mailboxes', type], function (o, n, p) {
|
||||||
|
if (p[2] === 'lastKnownHash') {
|
||||||
|
// Hide everything up to this hash
|
||||||
|
var sliceIdx;
|
||||||
|
box.history.some(function (h, i) {
|
||||||
|
sliceIdx = i + 1;
|
||||||
|
view(h);
|
||||||
|
if (h === n) { return true; }
|
||||||
|
});
|
||||||
|
box.history = box.history.slice(sliceIdx);
|
||||||
|
}
|
||||||
|
if (p[2] === 'viewed') {
|
||||||
|
// Hide this message
|
||||||
|
view(n);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Continue
|
||||||
|
onReady();
|
||||||
|
};
|
||||||
|
CpNetflux.start(cfg);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var subscribe = function (ctx, data, cId, cb) {
|
||||||
|
// Get existing notifications
|
||||||
|
Object.keys(ctx.boxes).forEach(function (type) {
|
||||||
|
Object.keys(ctx.boxes[type].content).forEach(function (h) {
|
||||||
|
var message = {
|
||||||
|
msg: ctx.boxes[type].content[h],
|
||||||
|
hash: h
|
||||||
|
};
|
||||||
|
showMessage(ctx, type, message, cId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// Subscribe to new notifications
|
||||||
|
var idx = ctx.clients.indexOf(cId);
|
||||||
|
if (idx === -1) {
|
||||||
|
ctx.clients.push(cId);
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
|
||||||
|
var removeClient = function (ctx, cId) {
|
||||||
|
var idx = ctx.clients.indexOf(cId);
|
||||||
|
ctx.clients.splice(idx, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
Mailbox.init = function (cfg, waitFor, emit) {
|
||||||
var mailbox = {};
|
var mailbox = {};
|
||||||
|
var store = cfg.store;
|
||||||
var ctx = {
|
var ctx = {
|
||||||
store: store,
|
store: store,
|
||||||
|
pinPads: cfg.pinPads,
|
||||||
|
updateMetadata: cfg.updateMetadata,
|
||||||
emit: emit,
|
emit: emit,
|
||||||
|
clients: [],
|
||||||
|
boxes: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
var mailboxes = store.proxy.mailboxes = store.proxy.mailboxes || {};
|
||||||
|
|
||||||
|
initializeMailboxes(ctx, mailboxes);
|
||||||
|
|
||||||
|
Object.keys(mailboxes).forEach(function (key) {
|
||||||
|
if (TYPES.indexOf(key) === -1) { return; }
|
||||||
|
var m = mailboxes[key];
|
||||||
|
|
||||||
|
if (BLOCKING_TYPES.indexOf(key) === -1) {
|
||||||
|
openChannel(ctx, key, m, function () {
|
||||||
|
updateLastKnownHash(ctx, key);
|
||||||
|
//console.log(key + ' mailbox is ready');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
openChannel(ctx, key, m, waitFor(function () {
|
||||||
|
//console.log(key + ' mailbox is ready');
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// XXX test function used to populate a mailbox, should be removed in prod
|
||||||
|
mailbox.post = function (box, type, content) {
|
||||||
|
var b = ctx.boxes[box];
|
||||||
|
if (!b) { return; }
|
||||||
|
b.sendMessage({
|
||||||
|
type: type,
|
||||||
|
content: content,
|
||||||
|
sender: store.proxy.curvePublic
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
mailbox.dismiss = function (data, cb) {
|
||||||
|
dismiss(ctx, data, '', cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
mailbox.sendTo = function (type, msg, user, cb) {
|
||||||
|
sendTo(ctx, type, msg, user, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
mailbox.removeClient = function (clientId) {
|
mailbox.removeClient = function (clientId) {
|
||||||
// TODO
|
removeClient(ctx, clientId);
|
||||||
//removeClient(ctx, clientId);
|
|
||||||
};
|
|
||||||
mailbox.leavePad = function (padChan) {
|
|
||||||
// TODO
|
|
||||||
//leaveChannel(ctx, padChan);
|
|
||||||
};
|
};
|
||||||
mailbox.execCommand = function (clientId, obj, cb) {
|
mailbox.execCommand = function (clientId, obj, cb) {
|
||||||
var cmd = obj.cmd;
|
var cmd = obj.cmd;
|
||||||
var data = obj.data;
|
var data = obj.data;
|
||||||
|
if (cmd === 'SUBSCRIBE') {
|
||||||
|
return void subscribe(ctx, data, clientId, cb);
|
||||||
|
}
|
||||||
|
if (cmd === 'DISMISS') {
|
||||||
|
return void dismiss(ctx, data, clientId, cb);
|
||||||
|
}
|
||||||
|
if (cmd === 'SENDTO') {
|
||||||
|
return void sendTo(ctx, data.type, data.msg, data.user, cb);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return mailbox;
|
return mailbox;
|
||||||
|
|||||||
@ -58,8 +58,8 @@ define([
|
|||||||
ADD_SHARED_FOLDER: Store.addSharedFolder,
|
ADD_SHARED_FOLDER: Store.addSharedFolder,
|
||||||
LOAD_SHARED_FOLDER: Store.loadSharedFolderAnon,
|
LOAD_SHARED_FOLDER: Store.loadSharedFolderAnon,
|
||||||
// Messaging
|
// Messaging
|
||||||
INVITE_FROM_USERLIST: Store.inviteFromUserlist,
|
ANSWER_FRIEND_REQUEST: Store.answerFriendRequest,
|
||||||
ADD_DIRECT_MESSAGE_HANDLERS: Store.addDirectMessageHandlers,
|
SEND_FRIEND_REQUEST: Store.sendFriendRequest,
|
||||||
// Chat
|
// Chat
|
||||||
CHAT_COMMAND: Store.messenger.execCommand,
|
CHAT_COMMAND: Store.messenger.execCommand,
|
||||||
// OnlyOffice
|
// OnlyOffice
|
||||||
|
|||||||
@ -605,7 +605,8 @@ define([
|
|||||||
'newpad',
|
'newpad',
|
||||||
'share',
|
'share',
|
||||||
'limit',
|
'limit',
|
||||||
'unpinnedWarning'
|
'unpinnedWarning',
|
||||||
|
'notifications'
|
||||||
],
|
],
|
||||||
title: title.getTitleConfig(),
|
title: title.getTitleConfig(),
|
||||||
metadataMgr: cpNfInner.metadataMgr,
|
metadataMgr: cpNfInner.metadataMgr,
|
||||||
|
|||||||
179
www/common/sframe-common-mailbox.js
Normal file
179
www/common/sframe-common-mailbox.js
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'/common/common-util.js',
|
||||||
|
'/common/common-interface.js',
|
||||||
|
'/common/common-ui-elements.js',
|
||||||
|
'/common/notifications.js',
|
||||||
|
'/common/hyperscript.js',
|
||||||
|
'/customize/messages.js'
|
||||||
|
], function ($, Util, UI, UIElements, Notifications, h, Messages) {
|
||||||
|
var Mailbox = {};
|
||||||
|
|
||||||
|
Mailbox.create = function (Common) {
|
||||||
|
var mailbox = Common.mailbox;
|
||||||
|
var sframeChan = Common.getSframeChannel();
|
||||||
|
|
||||||
|
var execCommand = function (cmd, data, cb) {
|
||||||
|
sframeChan.query('Q_MAILBOX_COMMAND', {
|
||||||
|
cmd: cmd,
|
||||||
|
data: data
|
||||||
|
}, function (err, obj) {
|
||||||
|
if (err) { return void cb({error: err}); }
|
||||||
|
cb(obj);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var history = {};
|
||||||
|
|
||||||
|
var removeFromHistory = function (type, hash) {
|
||||||
|
history[type] = history[type].filter(function (obj) {
|
||||||
|
return obj.hash !== hash;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
mailbox.sendTo = function (type, content, user) {
|
||||||
|
execCommand('SENDTO', {
|
||||||
|
type: type,
|
||||||
|
msg: content,
|
||||||
|
user: user
|
||||||
|
}, function (err, obj) {
|
||||||
|
if (err || (obj && obj.error)) { return void console.error(err || obj.error); }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// UI
|
||||||
|
|
||||||
|
var formatData = function (data) {
|
||||||
|
return JSON.stringify(data.content.msg.content);
|
||||||
|
};
|
||||||
|
var createElement = function (data) {
|
||||||
|
var notif;
|
||||||
|
var dismiss = h('span.fa.fa-times');
|
||||||
|
dismiss.addEventListener('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
mailbox.dismiss(data, function (err) {
|
||||||
|
if (err) { return void console.error(err); }
|
||||||
|
/*if (notif && notif.parentNode) {
|
||||||
|
try {
|
||||||
|
notif.parentNode.removeChild(notif);
|
||||||
|
} catch (e) { console.error(e); }
|
||||||
|
}*/
|
||||||
|
});
|
||||||
|
});
|
||||||
|
notif = h('div.cp-notification', {
|
||||||
|
'data-hash': data.content.hash
|
||||||
|
}, [
|
||||||
|
h('div.cp-notification-content', h('p', formatData(data))),
|
||||||
|
h('div.cp-notification-dismiss', dismiss)
|
||||||
|
]);
|
||||||
|
return notif;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var onViewedHandlers = [];
|
||||||
|
var onMessageHandlers = [];
|
||||||
|
|
||||||
|
onViewedHandlers.push(function (data) {
|
||||||
|
var hash = data.hash.replace(/"/g, '\\\"');
|
||||||
|
var $notif = $('.cp-notification[data-hash="'+hash+'"]');
|
||||||
|
if ($notif.length) {
|
||||||
|
$notif.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Call the onMessage handlers
|
||||||
|
var pushMessage = function (data, handler) {
|
||||||
|
var todo = function (f) {
|
||||||
|
try {
|
||||||
|
var el = createElement(data);
|
||||||
|
Notifications.add(Common, data, el);
|
||||||
|
f(data, el);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (typeof (handler) === "function") {
|
||||||
|
return void todo(handler);
|
||||||
|
}
|
||||||
|
onMessageHandlers.forEach(todo);
|
||||||
|
};
|
||||||
|
|
||||||
|
var onViewed = function (data) {
|
||||||
|
// data = { type: 'type', hash: 'hash' }
|
||||||
|
onViewedHandlers.forEach(function (f) {
|
||||||
|
try {
|
||||||
|
f(data);
|
||||||
|
Notifications.remove(Common, data);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
removeFromHistory(data.type, data.hash);
|
||||||
|
};
|
||||||
|
|
||||||
|
var onMessage = function (data) {
|
||||||
|
// data = { type: 'type', content: {msg: 'msg', hash: 'hash'} }
|
||||||
|
console.log(data.content);
|
||||||
|
pushMessage(data);
|
||||||
|
if (!history[data.type]) { history[data.type] = []; }
|
||||||
|
history[data.type].push(data.content);
|
||||||
|
};
|
||||||
|
|
||||||
|
mailbox.dismiss = function (data, cb) {
|
||||||
|
var dataObj = {
|
||||||
|
hash: data.content.hash,
|
||||||
|
type: data.type
|
||||||
|
};
|
||||||
|
execCommand('DISMISS', dataObj, function (obj) {
|
||||||
|
if (obj && obj.error) { return void cb(obj.error); }
|
||||||
|
onViewed(dataObj);
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Get all existing notifications + the new ones when they come
|
||||||
|
mailbox.subscribe = function (cfg) {
|
||||||
|
if (typeof(cfg.onViewed) === "function") {
|
||||||
|
onViewedHandlers.push(cfg.onViewed);
|
||||||
|
}
|
||||||
|
if (typeof(cfg.onMessage) === "function") {
|
||||||
|
onMessageHandlers.push(cfg.onMessage);
|
||||||
|
}
|
||||||
|
Object.keys(history).forEach(function (type) {
|
||||||
|
history[type].forEach(function (data) {
|
||||||
|
pushMessage({
|
||||||
|
type: type,
|
||||||
|
content: data
|
||||||
|
}, cfg.onMessage);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// CHANNEL WITH WORKER
|
||||||
|
|
||||||
|
sframeChan.on('EV_MAILBOX_EVENT', function (obj) {
|
||||||
|
// obj = { ev: 'type', data: obj }
|
||||||
|
var ev = obj.ev;
|
||||||
|
var data = obj.data;
|
||||||
|
if (ev === 'MESSAGE') {
|
||||||
|
return void onMessage(data);
|
||||||
|
}
|
||||||
|
if (ev === 'VIEWED') {
|
||||||
|
return void onViewed(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
execCommand('SUBSCRIBE', null, function () {
|
||||||
|
//console.log('subscribed');
|
||||||
|
});
|
||||||
|
|
||||||
|
return mailbox;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Mailbox;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -502,16 +502,11 @@ define([
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Messaging
|
// Messaging
|
||||||
sframeChan.on('Q_SEND_FRIEND_REQUEST', function (netfluxId, cb) {
|
sframeChan.on('Q_SEND_FRIEND_REQUEST', function (data, cb) {
|
||||||
Cryptpad.inviteFromUserlist(netfluxId, cb);
|
Cryptpad.messaging.sendFriendRequest(data, cb);
|
||||||
});
|
});
|
||||||
Cryptpad.messaging.onFriendRequest.reg(function (confirmText, cb) {
|
sframeChan.on('Q_ANSWER_FRIEND_REQUEST', function (data, cb) {
|
||||||
sframeChan.query('Q_INCOMING_FRIEND_REQUEST', confirmText, function (err, data) {
|
Cryptpad.messaging.answerFriendRequest(data, cb);
|
||||||
cb(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Cryptpad.messaging.onFriendComplete.reg(function (data) {
|
|
||||||
sframeChan.event('EV_FRIEND_REQUEST', data);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// History
|
// History
|
||||||
@ -877,6 +872,13 @@ define([
|
|||||||
Cryptpad.cursor.execCommand(data, cb);
|
Cryptpad.cursor.execCommand(data, cb);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cryptpad.mailbox.onEvent.reg(function (data) {
|
||||||
|
sframeChan.event('EV_MAILBOX_EVENT', data);
|
||||||
|
});
|
||||||
|
sframeChan.on('Q_MAILBOX_COMMAND', function (data, cb) {
|
||||||
|
Cryptpad.mailbox.execCommand(data, cb);
|
||||||
|
});
|
||||||
|
|
||||||
Cryptpad.onTimeoutEvent.reg(function () {
|
Cryptpad.onTimeoutEvent.reg(function () {
|
||||||
sframeChan.event('EV_WORKER_TIMEOUT');
|
sframeChan.event('EV_WORKER_TIMEOUT');
|
||||||
});
|
});
|
||||||
@ -947,9 +949,6 @@ define([
|
|||||||
readOnly: readOnly,
|
readOnly: readOnly,
|
||||||
crypto: Crypto.createEncryptor(secret.keys),
|
crypto: Crypto.createEncryptor(secret.keys),
|
||||||
onConnect: function () {
|
onConnect: function () {
|
||||||
var href = parsed.getUrl();
|
|
||||||
// Add friends requests handlers when we have the final href
|
|
||||||
Cryptpad.messaging.addHandlers(href);
|
|
||||||
if (window.location.hash && window.location.hash !== '#') {
|
if (window.location.hash && window.location.hash !== '#') {
|
||||||
/*window.location = parsed.getUrl({
|
/*window.location = parsed.getUrl({
|
||||||
present: parsed.hashData.present,
|
present: parsed.hashData.present,
|
||||||
|
|||||||
@ -69,7 +69,23 @@ define([
|
|||||||
return void UI.alert(Messages.pinLimitNotPinned, null, true);
|
return void UI.alert(Messages.pinLimitNotPinned, null, true);
|
||||||
} else if (err) { return; }
|
} else if (err) { return; }
|
||||||
evTitleChange.fire(title);
|
evTitleChange.fire(title);
|
||||||
if (titleUpdated) { titleUpdated(undefined, title); }
|
if (titleUpdated) {
|
||||||
|
titleUpdated(undefined, title);
|
||||||
|
// XXX Test notifications from inner
|
||||||
|
var users = metadataMgr.getMetadata().users;
|
||||||
|
var me = metadataMgr.getNetfluxId();
|
||||||
|
Object.keys(users).forEach(function (netfluxId) {
|
||||||
|
if (netfluxId === me) { return; }
|
||||||
|
var user = users[netfluxId];
|
||||||
|
if (!user.curvePublic || !user.notifications) { return; }
|
||||||
|
Common.mailbox.sendTo("TEST_NOTIF_TITLE", {
|
||||||
|
new_title: title
|
||||||
|
}, {
|
||||||
|
channel: user.notifications,
|
||||||
|
curvePublic: user.curvePublic
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ define([
|
|||||||
'/common/sframe-common-file.js',
|
'/common/sframe-common-file.js',
|
||||||
'/common/sframe-common-codemirror.js',
|
'/common/sframe-common-codemirror.js',
|
||||||
'/common/sframe-common-cursor.js',
|
'/common/sframe-common-cursor.js',
|
||||||
|
'/common/sframe-common-mailbox.js',
|
||||||
'/common/metadata-manager.js',
|
'/common/metadata-manager.js',
|
||||||
|
|
||||||
'/customize/application_config.js',
|
'/customize/application_config.js',
|
||||||
@ -33,6 +34,7 @@ define([
|
|||||||
File,
|
File,
|
||||||
CodeMirror,
|
CodeMirror,
|
||||||
Cursor,
|
Cursor,
|
||||||
|
Mailbox,
|
||||||
MetadataMgr,
|
MetadataMgr,
|
||||||
AppConfig,
|
AppConfig,
|
||||||
CommonRealtime,
|
CommonRealtime,
|
||||||
@ -378,14 +380,31 @@ define([
|
|||||||
funcs.mergeAnonDrive = function (cb) {
|
funcs.mergeAnonDrive = function (cb) {
|
||||||
ctx.sframeChan.query('Q_MERGE_ANON_DRIVE', null, cb);
|
ctx.sframeChan.query('Q_MERGE_ANON_DRIVE', null, cb);
|
||||||
};
|
};
|
||||||
// Friends
|
|
||||||
var pendingFriends = [];
|
// Create friend request
|
||||||
funcs.getPendingFriends = function () {
|
funcs.getPendingFriends = function () {
|
||||||
return pendingFriends.slice();
|
return ctx.metadataMgr.getPrivateData().pendingFriends;
|
||||||
};
|
};
|
||||||
funcs.sendFriendRequest = function (netfluxId) {
|
funcs.sendFriendRequest = function (data, cb) {
|
||||||
ctx.sframeChan.query('Q_SEND_FRIEND_REQUEST', netfluxId, $.noop);
|
ctx.sframeChan.query('Q_SEND_FRIEND_REQUEST', data, cb);
|
||||||
pendingFriends.push(netfluxId);
|
};
|
||||||
|
// Friend requests received
|
||||||
|
var friendRequests = {};
|
||||||
|
funcs.addFriendRequest = function (data) {
|
||||||
|
var curve = Util.find(data, ['content', 'msg', 'author']);
|
||||||
|
friendRequests[curve] = data;
|
||||||
|
};
|
||||||
|
funcs.removeFriendRequest = function (hash) {
|
||||||
|
Object.keys(friendRequests).some(function (curve) {
|
||||||
|
var h = Util.find(friendRequests[curve], ['content', 'hash']);
|
||||||
|
if (h === hash) {
|
||||||
|
delete friendRequests[curve];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
funcs.getFriendRequests = function () {
|
||||||
|
return JSON.parse(JSON.stringify(friendRequests));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Feedback
|
// Feedback
|
||||||
@ -468,6 +487,8 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
funcs.mailbox = {};
|
||||||
|
|
||||||
Object.freeze(funcs);
|
Object.freeze(funcs);
|
||||||
return { create: function (cb) {
|
return { create: function (cb) {
|
||||||
|
|
||||||
@ -520,15 +541,6 @@ define([
|
|||||||
|
|
||||||
UI.addTooltips();
|
UI.addTooltips();
|
||||||
|
|
||||||
ctx.sframeChan.on('Q_INCOMING_FRIEND_REQUEST', function (confirmMsg, cb) {
|
|
||||||
UI.confirm(confirmMsg, cb, null, true);
|
|
||||||
});
|
|
||||||
ctx.sframeChan.on('EV_FRIEND_REQUEST', function (data) {
|
|
||||||
var i = pendingFriends.indexOf(data.netfluxId);
|
|
||||||
if (i !== -1) { pendingFriends.splice(i, 1); }
|
|
||||||
UI.log(data.logText);
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.sframeChan.on("EV_PAD_PASSWORD", function () {
|
ctx.sframeChan.on("EV_PAD_PASSWORD", function () {
|
||||||
UIElements.displayPasswordPrompt(funcs);
|
UIElements.displayPasswordPrompt(funcs);
|
||||||
});
|
});
|
||||||
@ -630,6 +642,8 @@ define([
|
|||||||
|
|
||||||
ctx.sframeChan.ready();
|
ctx.sframeChan.ready();
|
||||||
cb(funcs);
|
cb(funcs);
|
||||||
|
|
||||||
|
Mailbox.create(funcs);
|
||||||
});
|
});
|
||||||
} };
|
} };
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,9 +7,10 @@ define([
|
|||||||
'/common/common-hash.js',
|
'/common/common-hash.js',
|
||||||
'/common/common-util.js',
|
'/common/common-util.js',
|
||||||
'/common/common-feedback.js',
|
'/common/common-feedback.js',
|
||||||
|
'/common/hyperscript.js',
|
||||||
'/common/messenger-ui.js',
|
'/common/messenger-ui.js',
|
||||||
'/customize/messages.js',
|
'/customize/messages.js',
|
||||||
], function ($, Config, ApiConfig, UIElements, UI, Hash, Util, Feedback,
|
], function ($, Config, ApiConfig, UIElements, UI, Hash, Util, Feedback, h,
|
||||||
MessengerUI, Messages) {
|
MessengerUI, Messages) {
|
||||||
var Common;
|
var Common;
|
||||||
|
|
||||||
@ -37,6 +38,7 @@ MessengerUI, Messages) {
|
|||||||
var TITLE_CLS = Bar.constants.title = "cp-toolbar-title";
|
var TITLE_CLS = Bar.constants.title = "cp-toolbar-title";
|
||||||
var NEWPAD_CLS = Bar.constants.newpad = "cp-toolbar-new";
|
var NEWPAD_CLS = Bar.constants.newpad = "cp-toolbar-new";
|
||||||
var LINK_CLS = Bar.constants.link = "cp-toolbar-link";
|
var LINK_CLS = Bar.constants.link = "cp-toolbar-link";
|
||||||
|
var NOTIFICATIONS_CLS = Bar.constants.user = 'cp-toolbar-notifications';
|
||||||
|
|
||||||
// User admin menu
|
// User admin menu
|
||||||
var USERADMIN_CLS = Bar.constants.user = 'cp-toolbar-user-dropdown';
|
var USERADMIN_CLS = Bar.constants.user = 'cp-toolbar-user-dropdown';
|
||||||
@ -70,6 +72,7 @@ MessengerUI, Messages) {
|
|||||||
'class': USER_CLS
|
'class': USER_CLS
|
||||||
}).appendTo($topContainer);
|
}).appendTo($topContainer);
|
||||||
$('<span>', {'class': LIMIT_CLS}).hide().appendTo($userContainer);
|
$('<span>', {'class': LIMIT_CLS}).hide().appendTo($userContainer);
|
||||||
|
$('<span>', {'class': NOTIFICATIONS_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer);
|
||||||
$('<span>', {'class': NEWPAD_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer);
|
$('<span>', {'class': NEWPAD_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer);
|
||||||
$('<span>', {'class': USERADMIN_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer);
|
$('<span>', {'class': USERADMIN_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer);
|
||||||
|
|
||||||
@ -229,7 +232,9 @@ MessengerUI, Messages) {
|
|||||||
// Display the userlist
|
// Display the userlist
|
||||||
|
|
||||||
// Editors
|
// Editors
|
||||||
var pendingFriends = Common.getPendingFriends();
|
var pendingFriends = Common.getPendingFriends(); // Friend requests sent
|
||||||
|
var friendRequests = Common.getFriendRequests(); // Friend requests received
|
||||||
|
var friendTo = +new Date() - (2 * 24 * 3600 * 1000);
|
||||||
editUsersNames.forEach(function (data) {
|
editUsersNames.forEach(function (data) {
|
||||||
var name = data.name || Messages.anonymous;
|
var name = data.name || Messages.anonymous;
|
||||||
var $span = $('<span>', {'class': 'cp-avatar'});
|
var $span = $('<span>', {'class': 'cp-avatar'});
|
||||||
@ -297,9 +302,18 @@ MessengerUI, Messages) {
|
|||||||
}
|
}
|
||||||
} else if (Common.isLoggedIn() && data.curvePublic && !friends[data.curvePublic]
|
} else if (Common.isLoggedIn() && data.curvePublic && !friends[data.curvePublic]
|
||||||
&& !priv.readOnly) {
|
&& !priv.readOnly) {
|
||||||
if (pendingFriends.indexOf(data.netfluxId) !== -1) {
|
if (pendingFriends[data.curvePublic] && pendingFriends[data.curvePublic] > friendTo) {
|
||||||
$('<span>', {'class': 'cp-toolbar-userlist-friend'}).text(Messages.userlist_pending)
|
$('<span>', {'class': 'cp-toolbar-userlist-friend'}).text(Messages.userlist_pending)
|
||||||
.appendTo($rightCol);
|
.appendTo($rightCol);
|
||||||
|
} else if (friendRequests[data.curvePublic]) {
|
||||||
|
$('<button>', {
|
||||||
|
'class': 'fa fa-bell cp-toolbar-userlist-button',
|
||||||
|
'title': Messages._getKey('friendRequest_received', [name]),
|
||||||
|
}).appendTo($nameSpan).click(function (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
UIElements.displayFriendRequestModal(Common, friendRequests[data.curvePublic]);
|
||||||
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$('<button>', {
|
$('<button>', {
|
||||||
'class': 'fa fa-user-plus cp-toolbar-userlist-button',
|
'class': 'fa fa-user-plus cp-toolbar-userlist-button',
|
||||||
@ -308,7 +322,9 @@ MessengerUI, Messages) {
|
|||||||
])
|
])
|
||||||
}).appendTo($nameSpan).click(function (e) {
|
}).appendTo($nameSpan).click(function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
Common.sendFriendRequest(data.netfluxId);
|
Common.sendFriendRequest(data, function (err, obj) {
|
||||||
|
if (err || (obj && obj.error)) { return void console.error(err || obj.error); }
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (Common.isLoggedIn() && data.curvePublic && friends[data.curvePublic]) {
|
} else if (Common.isLoggedIn() && data.curvePublic && friends[data.curvePublic]) {
|
||||||
@ -927,6 +943,62 @@ MessengerUI, Messages) {
|
|||||||
return $userAdmin;
|
return $userAdmin;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var createNotifications = function (toolbar, config) {
|
||||||
|
var $notif = toolbar.$top.find('.'+NOTIFICATIONS_CLS).show();
|
||||||
|
var div = h('div.cp-notifications-container', [
|
||||||
|
h('div.cp-notifications-empty', Messages.notifications_empty)
|
||||||
|
]);
|
||||||
|
var pads_options = [div];
|
||||||
|
var dropdownConfig = {
|
||||||
|
text: '', // Button initial text
|
||||||
|
options: pads_options, // Entries displayed in the menu
|
||||||
|
container: $notif,
|
||||||
|
left: true,
|
||||||
|
common: Common
|
||||||
|
};
|
||||||
|
var $newPadBlock = UIElements.createDropdown(dropdownConfig);
|
||||||
|
var $button = $newPadBlock.find('button');
|
||||||
|
$button.attr('title', Messages.notifications_empty);
|
||||||
|
$button.addClass('fa fa-bell-o');
|
||||||
|
var $n = $button.find('.cp-dropdown-button-title').hide();
|
||||||
|
var $empty = $(div).find('.cp-notifications-empty');
|
||||||
|
|
||||||
|
var refresh = function () {
|
||||||
|
updateUserList(toolbar, config);
|
||||||
|
var n = $(div).find('.cp-notification').length;
|
||||||
|
$button.removeClass('fa-bell-o').removeClass('fa-bell');
|
||||||
|
$n.removeClass('cp-notifications-small');
|
||||||
|
if (n === 0) {
|
||||||
|
$button.attr('title', Messages.notifications_empty);
|
||||||
|
$empty.show();
|
||||||
|
$n.hide();
|
||||||
|
return void $button.addClass('fa-bell-o');
|
||||||
|
}
|
||||||
|
$button.attr('title', Messages.notifications_title);
|
||||||
|
if (n > 99) {
|
||||||
|
n = '99+';
|
||||||
|
$n.addClass('cp-notifications-small');
|
||||||
|
}
|
||||||
|
$empty.hide();
|
||||||
|
$n.text(n).show();
|
||||||
|
$button.addClass('fa-bell');
|
||||||
|
};
|
||||||
|
|
||||||
|
Common.mailbox.subscribe({
|
||||||
|
onMessage: function (data, el) {
|
||||||
|
if (el) {
|
||||||
|
div.appendChild(el);
|
||||||
|
}
|
||||||
|
refresh();
|
||||||
|
},
|
||||||
|
onViewed: function (data) {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return $newPadBlock;
|
||||||
|
};
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
var initClickEvents = function (toolbar) {
|
var initClickEvents = function (toolbar) {
|
||||||
var removeDropdowns = function () {
|
var removeDropdowns = function () {
|
||||||
@ -1095,6 +1167,7 @@ MessengerUI, Messages) {
|
|||||||
tb['newpad'] = createNewPad;
|
tb['newpad'] = createNewPad;
|
||||||
tb['useradmin'] = createUserAdmin;
|
tb['useradmin'] = createUserAdmin;
|
||||||
tb['unpinnedWarning'] = createUnpinnedWarning;
|
tb['unpinnedWarning'] = createUnpinnedWarning;
|
||||||
|
tb['notifications'] = createNotifications;
|
||||||
|
|
||||||
var addElement = toolbar.addElement = function (arr, additionalCfg, init) {
|
var addElement = toolbar.addElement = function (arr, additionalCfg, init) {
|
||||||
if (typeof additionalCfg === "object") { $.extend(true, config, additionalCfg); }
|
if (typeof additionalCfg === "object") { $.extend(true, config, additionalCfg); }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user