Support mailbox in the support and admin apps
parent
22c9af6961
commit
93b4dac8bb
|
@ -90,12 +90,21 @@
|
|||
button.btn {
|
||||
@button-bg: @colortheme_sidebar-button-bg;
|
||||
@button-red-bg: @colortheme_sidebar-button-red-bg;
|
||||
@button-alt-bg: @colortheme_sidebar-button-alt-bg;
|
||||
background-color: @button-bg;
|
||||
border-color: darken(@button-bg, 10%);
|
||||
color: white;
|
||||
&:hover {
|
||||
background-color: darken(@button-bg, 10%);
|
||||
}
|
||||
&.btn-secondary {
|
||||
background-color: @button-alt-bg;
|
||||
border-color: darken(@button-alt-bg, 10%);
|
||||
color: black;
|
||||
&:hover {
|
||||
background-color: darken(@button-alt-bg, 10%);
|
||||
}
|
||||
}
|
||||
&.btn-danger {
|
||||
background-color: @button-red-bg;
|
||||
border-color: darken(@button-red-bg, 10%);
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
@import (reference) "./colortheme-all.less";
|
||||
.support_main () {
|
||||
@ticket-bg: #F7F7F7;
|
||||
@msg-bg: #eee;
|
||||
@fromme-bg: #ddd;
|
||||
.cp-support-container {
|
||||
.cp-support-list-ticket {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
background-color: @ticket-bg;
|
||||
padding: 10px;
|
||||
width: 1200px;
|
||||
max-width: 90%;
|
||||
margin: 5px auto;
|
||||
.cp-support-list-message {
|
||||
background-color: @msg-bg;
|
||||
margin: 2px;
|
||||
padding: 2px 5px;
|
||||
.cp-support-fromme {
|
||||
background-color: @fromme-bg;
|
||||
}
|
||||
.cp-support-showdata {
|
||||
cursor: pointer;
|
||||
background-color: @fromme-bg;
|
||||
.cp-support-message-data {
|
||||
display: none;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
.cp-support-message-time {
|
||||
float: right;
|
||||
}
|
||||
pre {
|
||||
margin-bottom: 0;
|
||||
&.cp-support-message-content {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-support-list-actions {
|
||||
order: 3;
|
||||
.cp-support-hide {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.cp-support-form-container {
|
||||
order: 2;
|
||||
}
|
||||
&.cp-support-list-closed {
|
||||
.cp-support-list-actions {
|
||||
display: block !important;
|
||||
.cp-support-answer, .cp-support-close {
|
||||
display: none;
|
||||
}
|
||||
.cp-support-hide {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
.cp-support-form-container {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
/* jshint esversion: 6, node: true */
|
||||
|
||||
const Nacl = require('tweetnacl');
|
||||
const Crypto = require('crypto');
|
||||
|
||||
const keyPair = Nacl.box.keyPair();
|
||||
console.log(keyPair);
|
||||
|
||||
console.log("You've just generated a new key pair for your support mailbox.");
|
||||
|
||||
console.log("The public key should first be added to your config.js file ('supportMailboxPublicKey'), then save and restart the server.")
|
||||
console.log("The public key should first be added to your config.js file ('supportMailboxPublicKey'), then save and restart the server.");
|
||||
console.log("Once restarted, administrators (specified with 'adminKeys' in config.js too) will be able to add the private key into their account. This can be done using the administration panel.");
|
||||
console.log("You will have to send the private key to each administrator manually so that they can add it to their account.");
|
||||
console.log();
|
|
@ -1,5 +1,6 @@
|
|||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
@import (reference) '../../customize/src/less2/include/sidebar-layout.less';
|
||||
@import (reference) '../../customize/src/less2/include/support.less';
|
||||
|
||||
&.cp-app-admin {
|
||||
|
||||
|
@ -9,6 +10,11 @@
|
|||
@color: @colortheme_admin-color
|
||||
);
|
||||
.sidebar-layout_main();
|
||||
.support_main();
|
||||
|
||||
.cp-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
|
|
@ -9,6 +9,8 @@ define([
|
|||
'/customize/messages.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/support/ui.js',
|
||||
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
|
@ -23,7 +25,9 @@ define([
|
|||
h,
|
||||
Messages,
|
||||
UI,
|
||||
Util
|
||||
Util,
|
||||
Hash,
|
||||
Support
|
||||
)
|
||||
{
|
||||
var APP = {};
|
||||
|
@ -41,6 +45,10 @@ define([
|
|||
'cp-admin-active-pads',
|
||||
'cp-admin-registered',
|
||||
'cp-admin-disk-usage',
|
||||
],
|
||||
'support': [
|
||||
'cp-admin-support-list',
|
||||
'cp-admin-support-init'
|
||||
]
|
||||
};
|
||||
|
||||
|
@ -94,7 +102,6 @@ define([
|
|||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ACTIVE_SESSIONS',
|
||||
}, function (e, data) {
|
||||
console.log(e, data);
|
||||
var total = data[0];
|
||||
var ips = data[1];
|
||||
$div.append(h('pre', total + ' (' + ips + ')'));
|
||||
|
@ -160,6 +167,111 @@ define([
|
|||
return $div;
|
||||
};
|
||||
|
||||
var supportKey = ApiConfig.supportMailbox;
|
||||
create['support-list'] = function () {
|
||||
if (!supportKey || !APP.privateKey) { return; }
|
||||
var $div = makeBlock('support-list');
|
||||
$div.addClass('cp-support-container');
|
||||
var hashesById = {};
|
||||
|
||||
// Register to the "support" mailbox
|
||||
common.mailbox.subscribe(['supportadmin'], {
|
||||
onMessage: function (data) {
|
||||
/*
|
||||
Get ID of the ticket
|
||||
If we already have a div for this ID
|
||||
Push the message to the end of the ticket
|
||||
If it's a new ticket ID
|
||||
Make a new div for this ID
|
||||
*/
|
||||
var msg = data.content.msg;
|
||||
var hash = data.content.hash;
|
||||
var content = msg.content;
|
||||
var id = content.id;
|
||||
var $ticket = $div.find('.cp-support-list-ticket[data-id="'+id+'"]');
|
||||
|
||||
hashesById[id] = hashesById[id] || [];
|
||||
if (hashesById[id].indexOf(hash) === -1) {
|
||||
hashesById[id].push(data);
|
||||
}
|
||||
|
||||
if (msg.type === 'CLOSE') {
|
||||
// A ticket has been closed by the admins...
|
||||
if (!$ticket.length) { return; }
|
||||
$ticket.addClass('cp-support-list-closed');
|
||||
$ticket.append(Support.makeCloseMessage(common, content, hash));
|
||||
return;
|
||||
}
|
||||
if (msg.type !== 'TICKET') { return; }
|
||||
|
||||
if (!$ticket.length) {
|
||||
$ticket = Support.makeTicket($div, common, content, function () {
|
||||
var error = false;
|
||||
hashesById[id].forEach(function (d) {
|
||||
common.mailbox.dismiss(d, function (err) {
|
||||
if (err) {
|
||||
error = true;
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!error) { $ticket.remove(); }
|
||||
});
|
||||
}
|
||||
$ticket.append(Support.makeMessage(common, content, hash, true));
|
||||
}
|
||||
});
|
||||
return $div;
|
||||
};
|
||||
|
||||
var checkAdminKey = function (priv) {
|
||||
if (!supportKey) { return; }
|
||||
return Hash.checkBoxKeyPair(priv, supportKey);
|
||||
};
|
||||
|
||||
create['support-init'] = function () {
|
||||
var $div = makeBlock('support-init');
|
||||
if (!supportKey) {
|
||||
$div.append(h('p', Messages.admin_supportInitHelp || "Your server is not configured to have a support mailbox. If you want a support mailbox to receive messages from your users, you should ask your server administrator to run the script located in './scripts/generate-admin-keys.js', store the public key in the 'config.js' file, and send you the private key.")); // XXX
|
||||
return $div;
|
||||
}
|
||||
if (!APP.privateKey || !checkAdminKey(APP.privateKey)) {
|
||||
$div.append(h('p', Messages.admin_supportInitPrivate || "Your CryptPad instance is configured to use a support mailbox but your account doesn't have the correct private key to access it. Please use the following form to add or update the private key to your account")); // XXX
|
||||
|
||||
var error = h('div.cp-admin-support-error');
|
||||
var input = h('input.cp-admin-add-private-key');
|
||||
var button = h('button.btn.btn-primary', Messages.admin_supportAddKey || 'add key'); // XXX
|
||||
|
||||
if (APP.privateKey && !checkAdminKey(APP.privateKey)) {
|
||||
$(error).text(Messages.admin_supportAddError || 'invalid'); // XXX
|
||||
}
|
||||
|
||||
$div.append(h('div', [
|
||||
error,
|
||||
input,
|
||||
button
|
||||
]));
|
||||
|
||||
$(button).click(function () {
|
||||
var key = $(input).val();
|
||||
if (!checkAdminKey(key)) {
|
||||
$(input).val('');
|
||||
return void $(error).text(Messages.admin_supportAddError || 'invalid'); // XXX
|
||||
}
|
||||
sFrameChan.query("Q_ADMIN_MAILBOX", key, function () {
|
||||
console.log(key);
|
||||
console.log(arguments);
|
||||
APP.privateKey = key;
|
||||
console.log('ok');
|
||||
$('.cp-admin-support-init').hide();
|
||||
APP.$rightside.append(create['support-list']()); // TODO: check?
|
||||
});
|
||||
});
|
||||
return $div;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
var hideCategories = function () {
|
||||
APP.$rightside.find('> div').hide();
|
||||
};
|
||||
|
@ -180,6 +292,7 @@ define([
|
|||
var $category = $('<div>', {'class': 'cp-sidebarlayout-category'}).appendTo($categories);
|
||||
if (key === 'general') { $category.append($('<span>', {'class': 'fa fa-user-o'})); }
|
||||
if (key === 'stats') { $category.append($('<span>', {'class': 'fa fa-hdd-o'})); }
|
||||
if (key === 'support') { $category.append($('<span>', {'class': 'fa fa-life-ring'})); }
|
||||
|
||||
if (key === active) {
|
||||
$category.addClass('cp-leftside-active');
|
||||
|
@ -236,6 +349,7 @@ define([
|
|||
return void UI.errorLoadingScreen(Messages.admin_authError || '403 Forbidden');
|
||||
}
|
||||
|
||||
APP.privateKey = privateData.supportPrivateKey;
|
||||
APP.origin = privateData.origin;
|
||||
APP.readOnly = privateData.readOnly;
|
||||
|
||||
|
|
|
@ -38,6 +38,9 @@ define([
|
|||
}).nThen(function (/*waitFor*/) {
|
||||
var addRpc = function (sframeChan, Cryptpad/*, Utils*/) {
|
||||
// Adding a new avatar from the profile: pin it and store it in the object
|
||||
sframeChan.on('Q_ADMIN_MAILBOX', function (data, cb) {
|
||||
Cryptpad.addAdminMailbox(data, cb);
|
||||
});
|
||||
sframeChan.on('Q_ADMIN_RPC', function (data, cb) {
|
||||
Cryptpad.adminRpc(data, cb);
|
||||
});
|
||||
|
|
|
@ -89,6 +89,18 @@ define([
|
|||
if (!publicKey) { return; }
|
||||
return uint8ArrayToHex(Hash.decodeBase64(publicKey).subarray(0,16));
|
||||
};
|
||||
Hash.getBoxPublicFromSecret = function (priv) {
|
||||
if (!priv) { return; }
|
||||
var u8_priv = Hash.decodeBase64(priv);
|
||||
var pair = Nacl.box.keyPair.fromSecretKey(u8_priv);
|
||||
return Hash.encodeBase64(pair.publicKey);
|
||||
};
|
||||
Hash.checkBoxKeyPair = function (priv, pub) {
|
||||
if (!pub || !priv) { return false; }
|
||||
var u8_priv = Hash.decodeBase64(priv);
|
||||
var pair = Nacl.box.keyPair.fromSecretKey(u8_priv);
|
||||
return pub === Hash.encodeBase64(pair.publicKey);
|
||||
};
|
||||
|
||||
Hash.createRandomHash = function (type, password) {
|
||||
var cryptor;
|
||||
|
|
|
@ -620,6 +620,9 @@ define([
|
|||
common.adminRpc = function (data, cb) {
|
||||
postMessage("ADMIN_RPC", data, cb);
|
||||
};
|
||||
common.addAdminMailbox = function (data, cb) {
|
||||
postMessage("ADMIN_ADD_MAILBOX", data, cb);
|
||||
};
|
||||
|
||||
// Network
|
||||
common.onNetworkDisconnect = Util.mkEvent();
|
||||
|
|
|
@ -479,7 +479,8 @@ define([
|
|||
thumbnails: disableThumbnails === false,
|
||||
isDriveOwned: Boolean(Util.find(store, ['driveMetadata', 'owners'])),
|
||||
support: Util.find(store.proxy, ['mailboxes', 'support', 'channel']),
|
||||
pendingFriends: store.proxy.friends_pending || {}
|
||||
pendingFriends: store.proxy.friends_pending || {},
|
||||
supportPrivateKey: Util.find(store.proxy, ['mailboxes', 'supportadmin', 'keys', 'curvePrivate'])
|
||||
}
|
||||
};
|
||||
cb(JSON.parse(JSON.stringify(metadata)));
|
||||
|
@ -1060,6 +1061,26 @@ define([
|
|||
cb(res);
|
||||
});
|
||||
};
|
||||
Store.addAdminMailbox = function (clientId, data, cb) {
|
||||
var priv = data;
|
||||
var pub = Hash.getBoxPublicFromSecret(priv);
|
||||
if (!priv || !pub) { return void cb({error: 'EINVAL'}); }
|
||||
var channel = Hash.getChannelIdFromKey(pub);
|
||||
var mailboxes = store.proxy.mailboxes = store.proxy.mailboxes || {};
|
||||
var box = mailboxes.supportadmin = {
|
||||
channel: channel,
|
||||
viewed: [],
|
||||
lastKnownHash: '',
|
||||
keys: {
|
||||
curvePublic: pub,
|
||||
curvePrivate: priv
|
||||
}
|
||||
};
|
||||
store.mailbox.open('supportadmin', box, function () {
|
||||
console.log('ready');
|
||||
});
|
||||
onSync(cb);
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
/////////////////////// PAD //////////////////////////////////////
|
||||
|
|
|
@ -10,6 +10,7 @@ define([
|
|||
|
||||
var TYPES = [
|
||||
'notifications',
|
||||
'supportadmin',
|
||||
'support'
|
||||
];
|
||||
var BLOCKING_TYPES = [
|
||||
|
@ -96,10 +97,10 @@ proxy.mailboxes = {
|
|||
network.join(user.channel).then(function (wc) {
|
||||
wc.bcast(ciphertext).then(function () {
|
||||
cb();
|
||||
wc.leave();
|
||||
|
||||
// If we've just sent a message to one of our mailboxes, we have to trigger the handler manually
|
||||
// (the server won't send back our message to us)
|
||||
// If it isn't one of our mailboxes, we can close it now
|
||||
var box;
|
||||
if (Object.keys(ctx.boxes).some(function (t) {
|
||||
var _box = ctx.boxes[t];
|
||||
|
@ -110,6 +111,8 @@ proxy.mailboxes = {
|
|||
})) {
|
||||
var hash = ciphertext.slice(0, 64);
|
||||
box.onMessage(text, null, null, null, hash, user.curvePublic);
|
||||
} else {
|
||||
wc.leave();
|
||||
}
|
||||
});
|
||||
}, function (err) {
|
||||
|
@ -200,7 +203,7 @@ proxy.mailboxes = {
|
|||
if (!Crypto.Mailbox) {
|
||||
return void console.error("chainpad-crypto is outdated and doesn't support mailboxes.");
|
||||
}
|
||||
var keys = getMyKeys(ctx);
|
||||
var keys = m.keys || getMyKeys(ctx);
|
||||
if (!keys) { return void console.error("missing asymmetric encryption keys"); }
|
||||
var crypto = Crypto.Mailbox.createEncryptor(keys);
|
||||
var cfg = {
|
||||
|
@ -364,6 +367,7 @@ proxy.mailboxes = {
|
|||
Object.keys(mailboxes).forEach(function (key) {
|
||||
if (TYPES.indexOf(key) === -1) { return; }
|
||||
var m = mailboxes[key];
|
||||
console.log(key, m);
|
||||
|
||||
if (BLOCKING_TYPES.indexOf(key) === -1) {
|
||||
openChannel(ctx, key, m, function () {
|
||||
|
@ -386,6 +390,11 @@ proxy.mailboxes = {
|
|||
});
|
||||
};
|
||||
|
||||
mailbox.open = function (key, m, cb) {
|
||||
if (TYPES.indexOf(key) === -1) { return; }
|
||||
openChannel(ctx, key, m, cb);
|
||||
};
|
||||
|
||||
mailbox.dismiss = function (data, cb) {
|
||||
dismiss(ctx, data, '', cb);
|
||||
};
|
||||
|
|
|
@ -84,6 +84,7 @@ define([
|
|||
DELETE_ACCOUNT: Store.deleteAccount,
|
||||
// Admin
|
||||
ADMIN_RPC: Store.adminRpc,
|
||||
ADMIN_ADD_MAILBOX: Store.addAdminMailbox,
|
||||
};
|
||||
|
||||
Rpc.query = function (cmd, data, cb) {
|
||||
|
|
|
@ -123,7 +123,7 @@ define([
|
|||
|
||||
var onMessage = function (data) {
|
||||
// data = { type: 'type', content: {msg: 'msg', hash: 'hash'} }
|
||||
console.log(data.content);
|
||||
console.log(data.type, data.content);
|
||||
pushMessage(data);
|
||||
if (!history[data.type]) { history[data.type] = []; }
|
||||
history[data.type].push(data.content);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
@import (reference) '../../customize/src/less2/include/sidebar-layout.less';
|
||||
@import (reference) '../../customize/src/less2/include/support.less';
|
||||
|
||||
&.cp-app-support {
|
||||
.framework_min_main(
|
||||
|
@ -8,6 +9,7 @@
|
|||
@color: @colortheme_support-color
|
||||
);
|
||||
.sidebar-layout_main();
|
||||
.support_main();
|
||||
|
||||
.cp-hidden {
|
||||
display: none !important;
|
||||
|
|
|
@ -9,8 +9,8 @@ define([
|
|||
'/common/common-hash.js',
|
||||
'/customize/messages.js',
|
||||
'/common/hyperscript.js',
|
||||
'/support/ui.js',
|
||||
'/api/config',
|
||||
'/common/common-feedback.js',
|
||||
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
|
@ -26,17 +26,15 @@ define([
|
|||
Hash,
|
||||
Messages,
|
||||
h,
|
||||
ApiConfig,
|
||||
Feedback
|
||||
Support,
|
||||
ApiConfig
|
||||
)
|
||||
{
|
||||
var saveAs = window.saveAs;
|
||||
var APP = window.APP = {};
|
||||
|
||||
var common;
|
||||
var metadataMgr;
|
||||
var privateData;
|
||||
var sframeChan;
|
||||
|
||||
var categories = {
|
||||
'tickets': [
|
||||
|
@ -47,9 +45,9 @@ define([
|
|||
],
|
||||
};
|
||||
|
||||
var supportKey = ApiConfig.supportMailbox; // XXX curvePublic
|
||||
var supportChannel = Hash.getChannelIdFromKey(supportKey); // XXX
|
||||
if (true || !supportKey || !supportChannel) {
|
||||
var supportKey = ApiConfig.supportMailbox;
|
||||
var supportChannel = Hash.getChannelIdFromKey(supportKey);
|
||||
if (!supportKey || !supportChannel) {
|
||||
categories = {
|
||||
'tickets': [
|
||||
'cp-support-disabled'
|
||||
|
@ -75,138 +73,14 @@ define([
|
|||
return $div;
|
||||
};
|
||||
|
||||
var showError = function (form, msg) {
|
||||
if (!msg) {
|
||||
return void $(form).find('.cp-support-form-error').text('').hide();
|
||||
}
|
||||
$(form).find('.cp-support-form-error').text(msg).show();
|
||||
};
|
||||
|
||||
var makeForm = function (cb, title) {
|
||||
var button;
|
||||
|
||||
if (typeof(cb) === "function") {
|
||||
button = h('button.btn.btn-primary.cp-support-list-send', Messages.support_send || 'Send'); // XXX
|
||||
$(button).click(cb);
|
||||
}
|
||||
|
||||
var content = [
|
||||
h('hr'),
|
||||
h('div.cp-support-form-error'),
|
||||
h('label' + (title ? '.cp-hidden' : ''), Messages.support_formTitle || 'title...'), // XXX
|
||||
h('input.cp-support-form-title' + (title ? '.cp-hidden' : ''), {
|
||||
placeholder: Messages.support_formTitlePlaceholder || 'title here...', // XXX
|
||||
value: title
|
||||
}),
|
||||
cb ? undefined : h('br'),
|
||||
h('label', Messages.support_formMessage || 'content...'), // XXX
|
||||
h('textarea.cp-support-form-msg', {
|
||||
placeholder: Messages.support_formMessagePlaceholder || 'describe your problem here...' // XXX
|
||||
}),
|
||||
h('hr'),
|
||||
button
|
||||
];
|
||||
|
||||
return h('div.cp-support-form-container', content);
|
||||
};
|
||||
|
||||
var sendForm = function (id, form) {
|
||||
var user = metadataMgr.getUserData();
|
||||
privateData = metadataMgr.getPrivateData();
|
||||
|
||||
var $title = $(form).find('.cp-support-form-title');
|
||||
var $content = $(form).find('.cp-support-form-msg');
|
||||
|
||||
var title = $title.val();
|
||||
if (!title) {
|
||||
return void showError(form, Messages.support_formTitleError || 'title error'); // XXX
|
||||
}
|
||||
var content = $content.val();
|
||||
if (!content) {
|
||||
return void showError(form, Messages.support_formContentError || 'content error'); // XXX
|
||||
}
|
||||
// Success: hide any error
|
||||
showError(form, null);
|
||||
$content.val('');
|
||||
$title.val('');
|
||||
|
||||
common.mailbox.sendTo('TICKET', {
|
||||
sender: {
|
||||
name: user.name,
|
||||
channel: privateData.support,
|
||||
curvePublic: user.curvePublic,
|
||||
edPublic: privateData.edPublic
|
||||
},
|
||||
title: title,
|
||||
message: content,
|
||||
id: id
|
||||
}, {
|
||||
channel: supportChannel,
|
||||
curvePublic: supportKey
|
||||
});
|
||||
common.mailbox.sendTo('TICKET', {
|
||||
sender: {
|
||||
name: user.name,
|
||||
channel: privateData.support,
|
||||
curvePublic: user.curvePublic,
|
||||
edPublic: privateData.edPublic
|
||||
},
|
||||
title: title,
|
||||
message: content,
|
||||
id: id
|
||||
}, {
|
||||
channel: privateData.support,
|
||||
curvePublic: user.curvePublic
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// List existing (open?) tickets
|
||||
create['list'] = function () {
|
||||
var key = 'list';
|
||||
var $div = makeBlock(key);
|
||||
|
||||
var makeTicket = function (content) {
|
||||
var ticketTitle = content.id + ' - ' + content.title;
|
||||
var answer = h('button.btn.btn-primary.cp-support-answer', Messages.support_answer || 'Answer'); // XXX
|
||||
|
||||
var $ticket = $(h('div.cp-support-list-ticket', {
|
||||
'data-id': content.id
|
||||
}, [
|
||||
h('h2', ticketTitle),
|
||||
h('div.cp-support-list-actions', answer)
|
||||
]));
|
||||
|
||||
$(answer).click(function () {
|
||||
$div.find('.cp-support-form-container').remove();
|
||||
$div.find('.cp-support-answer').show();
|
||||
$(answer).hide();
|
||||
var form = makeForm(function () {
|
||||
var sent = sendForm(content.id, form);
|
||||
if (sent) {
|
||||
$(answer).show();
|
||||
$(form).remove();
|
||||
}
|
||||
}, content.title);
|
||||
$ticket.append(form);
|
||||
});
|
||||
|
||||
$div.append($ticket);
|
||||
return $ticket;
|
||||
};
|
||||
|
||||
var makeMessage = function (content, hash) {
|
||||
// Check content.sender to see if it comes from us or from an admin
|
||||
// XXX admins should send their personal public key?
|
||||
var fromMe = content.sender && content.sender.edPublic === privateData.edPublic;
|
||||
return h('div.cp-support-list-message', [
|
||||
h('p.cp-support-message-from' + fromMe ? '.cp-support-fromme' : '',
|
||||
//Messages._getKey('support_from', [content.sender.name])), // XXX
|
||||
[h('b', 'From: '), content.sender.name]),
|
||||
h('pre.cp-support-message-content', content.message)
|
||||
]);
|
||||
};
|
||||
$div.addClass('cp-support-container');
|
||||
var hashesById = {};
|
||||
|
||||
// Register to the "support" mailbox
|
||||
common.mailbox.subscribe(['support'], {
|
||||
|
@ -220,23 +94,39 @@ define([
|
|||
*/
|
||||
var msg = data.content.msg;
|
||||
var hash = data.content.hash;
|
||||
if (msg.type === 'CLOSE') {
|
||||
// A ticket has been closed by the admins...
|
||||
// TODO: add a "closed" class to the ticket in the UI
|
||||
}
|
||||
if (msg.type !== 'TICKET') { return; }
|
||||
var content = msg.content;
|
||||
var id = content.id;
|
||||
|
||||
var $ticket = $div.find('.cp-support-list-ticket[data-id="'+id+'"]');
|
||||
if (!$ticket.length) {
|
||||
$ticket = makeTicket(content);
|
||||
|
||||
hashesById[id] = hashesById[id] || [];
|
||||
if (hashesById[id].indexOf(hash) === -1) {
|
||||
hashesById[id].push(data);
|
||||
}
|
||||
$ticket.append(makeMessage(content, hash));
|
||||
},
|
||||
onViewed: function (data) {
|
||||
// Remove the ticket with this hash
|
||||
// If the ticket div is empty, remove the ticket div
|
||||
|
||||
if (msg.type === 'CLOSE') {
|
||||
// A ticket has been closed by the admins...
|
||||
if (!$ticket.length) { return; }
|
||||
$ticket.addClass('cp-support-list-closed');
|
||||
$ticket.append(Support.makeCloseMessage(common, content, hash));
|
||||
return;
|
||||
}
|
||||
if (msg.type !== 'TICKET') { return; }
|
||||
|
||||
if (!$ticket.length) {
|
||||
$ticket = Support.makeTicket($div, common, content, function () {
|
||||
var error = false;
|
||||
hashesById[id].forEach(function (d) {
|
||||
common.mailbox.dismiss(d, function (err) {
|
||||
if (err) {
|
||||
error = true;
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!error) { $ticket.remove(); }
|
||||
});
|
||||
}
|
||||
$ticket.append(Support.makeMessage(common, content, hash, false));
|
||||
}
|
||||
});
|
||||
return $div;
|
||||
|
@ -247,14 +137,20 @@ define([
|
|||
var key = 'form';
|
||||
var $div = makeBlock(key, true);
|
||||
|
||||
var form = makeForm();
|
||||
var form = Support.makeForm();
|
||||
|
||||
$div.find('button').before(form);
|
||||
|
||||
var id = Util.uid();
|
||||
|
||||
$div.find('button').click(function () {
|
||||
var sent = sendForm(id, form);
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
var user = metadataMgr.getUserData();
|
||||
var sent = Support.sendForm(common, id, form, {
|
||||
channel: privateData.support,
|
||||
curvePublic: user.curvePublic
|
||||
});
|
||||
if (sent) {
|
||||
$('.cp-sidebarlayout-category[data-category="tickets"]').click();
|
||||
}
|
||||
|
@ -338,7 +234,7 @@ define([
|
|||
APP.$toolbar = $('#cp-toolbar');
|
||||
APP.$leftside = $('<div>', {id: 'cp-sidebarlayout-leftside'}).appendTo(APP.$container);
|
||||
APP.$rightside = $('<div>', {id: 'cp-sidebarlayout-rightside'}).appendTo(APP.$container);
|
||||
sFrameChan = common.getSframeChannel();
|
||||
var sFrameChan = common.getSframeChannel();
|
||||
sFrameChan.onReady(waitFor());
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
createToolbar();
|
||||
|
|
|
@ -36,8 +36,6 @@ define([
|
|||
};
|
||||
window.addEventListener('message', onMsg);
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
var addRpc = function (sframeChan, Cryptpad, Utils) {
|
||||
};
|
||||
var category;
|
||||
if (window.location.hash) {
|
||||
category = window.location.hash.slice(1);
|
||||
|
@ -48,7 +46,6 @@ define([
|
|||
};
|
||||
SFCommonO.start({
|
||||
noRealtime: true,
|
||||
addRpc: addRpc,
|
||||
addData: addData
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
define([
|
||||
'jquery',
|
||||
'/api/config',
|
||||
'/common/hyperscript.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-util.js',
|
||||
'/customize/messages.js',
|
||||
], function ($, ApiConfig, h, Hash, Util, Messages) {
|
||||
|
||||
var showError = function (form, msg) {
|
||||
if (!msg) {
|
||||
return void $(form).find('.cp-support-form-error').text('').hide();
|
||||
}
|
||||
$(form).find('.cp-support-form-error').text(msg).show();
|
||||
};
|
||||
|
||||
var send = function (common, id, type, data, dest) {
|
||||
var supportKey = ApiConfig.supportMailbox;
|
||||
var supportChannel = Hash.getChannelIdFromKey(supportKey);
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var user = metadataMgr.getUserData();
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
|
||||
data = data || {};
|
||||
|
||||
data.sender = {
|
||||
name: user.name,
|
||||
channel: privateData.support,
|
||||
curvePublic: user.curvePublic,
|
||||
edPublic: privateData.edPublic
|
||||
};
|
||||
data.id = id;
|
||||
data.time = +new Date();
|
||||
|
||||
// Send the message to the admin mailbox and to the user mailbox
|
||||
common.mailbox.sendTo(type, data, {
|
||||
channel: supportChannel,
|
||||
curvePublic: supportKey
|
||||
});
|
||||
common.mailbox.sendTo(type, data, {
|
||||
channel: dest.channel,
|
||||
curvePublic: dest.curvePublic
|
||||
});
|
||||
};
|
||||
|
||||
var sendForm = function (common, id, form, dest) {
|
||||
var $title = $(form).find('.cp-support-form-title');
|
||||
var $content = $(form).find('.cp-support-form-msg');
|
||||
|
||||
var title = $title.val();
|
||||
if (!title) {
|
||||
return void showError(form, Messages.support_formTitleError || 'title error'); // XXX
|
||||
}
|
||||
var content = $content.val();
|
||||
if (!content) {
|
||||
return void showError(form, Messages.support_formContentError || 'content error'); // XXX
|
||||
}
|
||||
// Success: hide any error
|
||||
showError(form, null);
|
||||
$content.val('');
|
||||
$title.val('');
|
||||
|
||||
send(common, id, 'TICKET', {
|
||||
title: title,
|
||||
message: content,
|
||||
}, dest);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
var makeForm = function (cb, title) {
|
||||
var button;
|
||||
|
||||
if (typeof(cb) === "function") {
|
||||
button = h('button.btn.btn-primary.cp-support-list-send', Messages.support_send || 'Send'); // XXX
|
||||
$(button).click(cb);
|
||||
}
|
||||
|
||||
var cancel = title ? h('button.btn.btn-secondary', Messages.cancel) : undefined;
|
||||
|
||||
var content = [
|
||||
h('hr'),
|
||||
h('div.cp-support-form-error'),
|
||||
h('label' + (title ? '.cp-hidden' : ''), Messages.support_formTitle || 'title...'), // XXX
|
||||
h('input.cp-support-form-title' + (title ? '.cp-hidden' : ''), {
|
||||
placeholder: Messages.support_formTitlePlaceholder || 'title here...', // XXX
|
||||
value: title || ''
|
||||
}),
|
||||
cb ? undefined : h('br'),
|
||||
h('label', Messages.support_formMessage || 'content...'), // XXX
|
||||
h('textarea.cp-support-form-msg', {
|
||||
placeholder: Messages.support_formMessagePlaceholder || 'describe your problem here...' // XXX
|
||||
}),
|
||||
h('hr'),
|
||||
button,
|
||||
cancel
|
||||
];
|
||||
|
||||
var form = h('div.cp-support-form-container', content);
|
||||
|
||||
$(cancel).click(function () {
|
||||
$(form).closest('.cp-support-list-ticket').find('.cp-support-list-actions').show();
|
||||
$(form).remove();
|
||||
});
|
||||
|
||||
return form;
|
||||
};
|
||||
|
||||
var makeTicket = function ($div, common, content, onHide) {
|
||||
var ticketTitle = content.id + ' - ' + content.title;
|
||||
var answer = h('button.btn.btn-primary.cp-support-answer', Messages.support_answer || 'Answer'); // XXX
|
||||
var close = h('button.btn.btn-danger.cp-support-close', Messages.support_close || 'Close'); // XXX
|
||||
var hide = h('button.btn.btn-danger.cp-support-hide', Messages.support_remove || 'Remove'); // XXX
|
||||
|
||||
var actions = h('div.cp-support-list-actions', [
|
||||
answer,
|
||||
close,
|
||||
hide
|
||||
]);
|
||||
|
||||
var $ticket = $(h('div.cp-support-list-ticket', {
|
||||
'data-id': content.id
|
||||
}, [
|
||||
h('h2', ticketTitle),
|
||||
actions
|
||||
]));
|
||||
|
||||
$(close).click(function () {
|
||||
send(common, content.id, 'CLOSE', {}, content.sender);
|
||||
});
|
||||
|
||||
$(hide).click(function () {
|
||||
if (typeof(onHide) !== "function") { return; }
|
||||
onHide();
|
||||
});
|
||||
|
||||
$(answer).click(function () {
|
||||
$ticket.find('.cp-support-form-container').remove();
|
||||
$(actions).hide();
|
||||
var form = makeForm(function () {
|
||||
var sent = sendForm(common, content.id, form, content.sender);
|
||||
if (sent) {
|
||||
$(actions).show();
|
||||
$(form).remove();
|
||||
}
|
||||
}, content.title);
|
||||
$ticket.append(form);
|
||||
});
|
||||
|
||||
$div.append($ticket);
|
||||
return $ticket;
|
||||
};
|
||||
|
||||
var makeMessage = function (common, content, hash, isAdmin) {
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
|
||||
// Check content.sender to see if it comes from us or from an admin
|
||||
// XXX admins should send their personal public key?
|
||||
var fromMe = content.sender && content.sender.edPublic === privateData.edPublic;
|
||||
|
||||
var userData = h('div.cp-support-showdata', [
|
||||
Messages.support_showData || 'Show/hide data', // XXX
|
||||
h('pre.cp-support-message-data', JSON.stringify(content.sender, 0, 2))
|
||||
]);
|
||||
$(userData).click(function () {
|
||||
$(userData).find('pre').toggle();
|
||||
});
|
||||
|
||||
return h('div.cp-support-list-message', {
|
||||
'data-hash': hash
|
||||
}, [
|
||||
h('div.cp-support-message-from' + (fromMe ? '.cp-support-fromme' : ''),
|
||||
//Messages._getKey('support_from', [content.sender.name, new Date(content.time)])), // XXX
|
||||
[h('b', 'From: '), content.sender.name, h('span.cp-support-message-time', content.time ? new Date(content.time).toLocaleString() : '')]),
|
||||
h('pre.cp-support-message-content', content.message),
|
||||
isAdmin ? userData : undefined,
|
||||
]);
|
||||
};
|
||||
|
||||
var makeCloseMessage = function (common, content, hash) {
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
var fromMe = content.sender && content.sender.edPublic === privateData.edPublic;
|
||||
|
||||
return h('div.cp-support-list-message', {
|
||||
'data-hash': hash
|
||||
}, [
|
||||
h('div.cp-support-message-from' + (fromMe ? '.cp-support-fromme' : ''),
|
||||
//Messages._getKey('support_from', [content.sender.name, new Date(content.time)])), // XXX
|
||||
[h('b', 'From: '), content.sender.name, h('span.cp-support-message-time', content.time ? new Date(content.time).toLocaleString() : '')]),
|
||||
h('pre.cp-support-message-content', Messages.support_closed || 'Ticket closed...') // XXX
|
||||
]);
|
||||
};
|
||||
|
||||
return {
|
||||
sendForm: sendForm,
|
||||
makeForm: makeForm,
|
||||
makeTicket: makeTicket,
|
||||
makeMessage: makeMessage,
|
||||
makeCloseMessage: makeCloseMessage
|
||||
};
|
||||
});
|
Loading…
Reference in New Issue