New support: moderator view
This commit is contained in:
parent
6493f3a42e
commit
8e24b8b3f2
8 changed files with 390 additions and 140 deletions
|
@ -49,6 +49,16 @@
|
|||
max-width: 90%;
|
||||
margin: 5px auto;
|
||||
border-radius: @variables_radius;
|
||||
.cp-support-ticket-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
&.cp-not-loaded {
|
||||
.cp-support-list-actions {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.cp-support-list-message {
|
||||
background-color: @msg-bg;
|
||||
padding: 5px 5px;
|
||||
|
|
|
@ -308,7 +308,7 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var account = {};
|
||||
var account = store.account = {};
|
||||
|
||||
Store.getPinnedUsage = function (clientId, data, cb) {
|
||||
var s = getStore(data && data.teamId);
|
||||
|
@ -2837,6 +2837,7 @@ define([
|
|||
loadUniversal(Calendar, 'calendar', waitFor);
|
||||
if (store.modules['team']) { store.modules['team'].onReady(waitFor); }
|
||||
loadUniversal(History, 'history', waitFor);
|
||||
loadUniversal(Support, 'support', waitFor);
|
||||
}).nThen(function () {
|
||||
var requestLogin = function () {
|
||||
broadcast([], "REQUEST_LOGIN");
|
||||
|
@ -2936,7 +2937,6 @@ define([
|
|||
broadcast([], "UPDATE_TOKEN", { token: proxy[Constants.tokenKey] });
|
||||
});
|
||||
|
||||
loadUniversal(Support, 'support');
|
||||
loadMailbox();
|
||||
|
||||
onReadyEvt.fire();
|
||||
|
|
|
@ -845,7 +845,9 @@ define([
|
|||
handlers['NEW_TICKET'] = function (ctx, box, data, cb) {
|
||||
var msg = data.msg;
|
||||
var content = msg.content;
|
||||
content.time = data.time;
|
||||
var i = 0;
|
||||
console.error(msg);
|
||||
var handle = function () {
|
||||
var support = Util.find(ctx, ['store', 'modules', 'support']);
|
||||
if (!support && i++ < 100) { setTimeout(handle, 600); }
|
||||
|
|
|
@ -374,7 +374,8 @@ proxy.mailboxes = {
|
|||
// Message should be displayed
|
||||
var message = {
|
||||
msg: msg,
|
||||
hash: hash
|
||||
hash: hash,
|
||||
time: time
|
||||
};
|
||||
var notify = box.ready;
|
||||
Handlers.add(ctx, box, message, function (dismissed, toDismiss, invalid) {
|
||||
|
|
|
@ -14,35 +14,53 @@ define([
|
|||
], function (Util, Hash, Realtime, nThen, Crypto, Listmap, ChainPad, CpNetflux) {
|
||||
var Support = {};
|
||||
|
||||
var getKeys = function (ctx, cb) {
|
||||
// UTILS
|
||||
|
||||
var getKeys = function (ctx, isAdmin, data, _cb) {
|
||||
var cb = Util.mkAsync(_cb);
|
||||
if (isAdmin && !ctx.adminRdyEvt) { return void cb('EFORBIDDEN'); }
|
||||
require(['/api/config?' + (+new Date())], function (NewConfig) {
|
||||
var supportKey = NewConfig.newSupportMailbox;
|
||||
if (!supportKey) { return void cb('E_NOT_INIT'); }
|
||||
var myCurve = ctx.store.proxy.curvePrivate;
|
||||
|
||||
// If admin, check key
|
||||
if (isAdmin && Util.find(ctx.store.proxy, [
|
||||
'mailboxes', 'support2', 'keys', 'curvePublic']) !== supportKey) {
|
||||
return void cb('EFORBIDDEN');
|
||||
}
|
||||
|
||||
if (isAdmin) {
|
||||
return ctx.adminRdyEvt.reg(() => {
|
||||
cb(null, {
|
||||
myCurve: data.adminCurvePrivate || Util.find(ctx.store.proxy, [
|
||||
'mailboxes', 'support2', 'keys', 'curvePrivate']),
|
||||
theirPublic: data.curvePublic
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
cb(null, {
|
||||
supportKey,
|
||||
myCurve
|
||||
theirPublic: supportKey,
|
||||
myCurve: ctx.store.proxy.curvePrivate
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Get the content of a mailbox and close it
|
||||
// Used for support tickets
|
||||
var getContent = function (ctx, data, _cb) {
|
||||
// Get the content of a ticket mailbox and close it
|
||||
var getContent = function (ctx, data, isAdmin, _cb) {
|
||||
var cb = Util.once(Util.both(close, Util.mkAsync(_cb)));
|
||||
var supportKey, myCurve;
|
||||
var theirPublic, myCurve;
|
||||
nThen((waitFor) => {
|
||||
// Send ticket to the admins and call back
|
||||
getKeys(ctx, waitFor((err, obj) => {
|
||||
getKeys(ctx, isAdmin, data, waitFor((err, obj) => {
|
||||
if (err) {
|
||||
waitFor.abort();
|
||||
return void cb({error: err});
|
||||
}
|
||||
supportKey = obj.supportKey;
|
||||
theirPublic = obj.theirPublic;
|
||||
myCurve = obj.myCurve;
|
||||
}));
|
||||
}).nThen((waitFor) => {
|
||||
var keys = Crypto.Curve.deriveKeys(supportKey, myCurve);
|
||||
var keys = Crypto.Curve.deriveKeys(theirPublic, myCurve);
|
||||
var crypto = Crypto.Curve.createEncryptor(keys);
|
||||
var cfg = {
|
||||
network: ctx.store.network,
|
||||
|
@ -65,8 +83,8 @@ define([
|
|||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
msg.time = time;
|
||||
if (author) { msg.author = author; }
|
||||
console.log(msg);
|
||||
all.push(msg);
|
||||
};
|
||||
cfg.onError = cb;
|
||||
|
@ -78,107 +96,6 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var loadAdminDoc = function (ctx, hash, cb) {
|
||||
var secret = Hash.getSecrets('support', hash);
|
||||
var listmapConfig = {
|
||||
data: {},
|
||||
channel: secret.channel,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
userName: 'support',
|
||||
ChainPad: ChainPad,
|
||||
classic: true,
|
||||
network: ctx.store.network,
|
||||
//Cache: Cache, // XXX XXX XXX
|
||||
metadata: {
|
||||
validateKey: secret.keys.validateKey || undefined,
|
||||
},
|
||||
};
|
||||
var rt = ctx.adminDoc = Listmap.create(listmapConfig);
|
||||
// XXX on change, tell current user that support has changed?
|
||||
rt.onReady = Util.mkEvent(true);
|
||||
rt.proxy.on('ready', function () {
|
||||
var doc = rt.proxy;
|
||||
doc.tickets = doc.tickets || {};
|
||||
doc.tickets.active = doc.tickets.active || {};
|
||||
doc.tickets.closed = doc.tickets.closed || {};
|
||||
rt.onReady.fire();
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
{
|
||||
tickets: {
|
||||
active: {},
|
||||
closed: {}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
var addAdminTicket = function (ctx, data, cb) {
|
||||
console.error(data);
|
||||
if (!ctx.adminDoc) {
|
||||
if (ctx.admin) {
|
||||
return void setTimeout(() => { addAdminTicket(ctx, data, cb); }, 200);
|
||||
}
|
||||
// XXX You have an admin mailbox but wrong keys ==> delete the mailbox?
|
||||
return void cb(false);
|
||||
}
|
||||
// Wait for the chainpad to be ready before adding the data
|
||||
ctx.adminDoc.onReady.reg(() => {
|
||||
// random timeout to avoid duplication wiht multiple admins
|
||||
var rdmTo = Math.floor(Math.random() * 2000); // Between 0 and 2000ms
|
||||
console.warn(rdmTo);
|
||||
setTimeout(() => {
|
||||
var doc = ctx.adminDoc.proxy;
|
||||
console.warn(data.channel, doc.tickets.active);
|
||||
if (doc.tickets.active[data.channel] || doc.tickets.closed[data.channel]) {
|
||||
console.warn('already there');
|
||||
return void cb(true); }
|
||||
doc.tickets.active[data.channel] = {
|
||||
title: data.title,
|
||||
author: data.user && data.user.curvePublic
|
||||
};
|
||||
Realtime.whenRealtimeSyncs(ctx.adminDoc.realtime, function () {
|
||||
console.warn('synced');
|
||||
cb(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
var initializeSupportAdmin = function (ctx) {
|
||||
let proxy = ctx.store.proxy;
|
||||
let supportKey = Util.find(proxy, ['mailboxes', 'support2', 'keys', 'curvePublic']);
|
||||
let privateKey = Util.find(proxy, ['mailboxes', 'support2', 'keys', 'curvePrivate']);
|
||||
nThen((waitFor) => {
|
||||
getKeys(ctx, waitFor((err, obj) => {
|
||||
if (err) { return void waitFor.abort(); }
|
||||
if (obj.supportKey !== supportKey) {
|
||||
// Deprecated support key: no longer an admin!
|
||||
ctx.admin = false;
|
||||
// XXX delete the mailbox?
|
||||
return void waitFor.abort();
|
||||
}
|
||||
}));
|
||||
}).nThen((waitFor) => {
|
||||
ctx.admin = true;
|
||||
let seed = privateKey.slice(0,24); // XXX better way to get seed?
|
||||
let hash = Hash.getEditHashFromKeys({
|
||||
version: 2,
|
||||
type: 'support',
|
||||
keys: {
|
||||
editKeyStr: seed
|
||||
}
|
||||
});
|
||||
loadAdminDoc(ctx, hash, waitFor());
|
||||
}).nThen(() => {
|
||||
console.log(ctx.store.mailbox)
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
var makeTicket = function (ctx, data, cId, cb) {
|
||||
var mailbox = Util.find(ctx, [ 'store', 'mailbox' ]);
|
||||
var anonRpc = Util.find(ctx, [ 'store', 'anon_rpc' ]);
|
||||
|
@ -191,12 +108,12 @@ define([
|
|||
var supportKey, myCurve;
|
||||
nThen((waitFor) => {
|
||||
// Send ticket to the admins and call back
|
||||
getKeys(ctx, waitFor((err, obj) => {
|
||||
getKeys(ctx, false, data, waitFor((err, obj) => {
|
||||
if (err) {
|
||||
waitFor.abort();
|
||||
return void cb({error: err});
|
||||
}
|
||||
supportKey = obj.supportKey;
|
||||
supportKey = obj.theirPublic;
|
||||
myCurve = obj.myCurve;
|
||||
}));
|
||||
}).nThen((waitFor) => {
|
||||
|
@ -216,7 +133,6 @@ define([
|
|||
}));
|
||||
}).nThen((waitFor) => {
|
||||
// Store in our worker
|
||||
console.error(channel);
|
||||
ctx.supportData[channel] = {
|
||||
time: +new Date(),
|
||||
title: title,
|
||||
|
@ -225,10 +141,10 @@ define([
|
|||
ctx.Store.onSync(null, waitFor());
|
||||
}).nThen(() => {
|
||||
var supportChannel = Hash.getChannelIdFromKey(supportKey);
|
||||
console.error(supportChannel);
|
||||
mailbox.sendTo('NEW_TICKET', {
|
||||
title: title,
|
||||
channel: channel
|
||||
channel: channel,
|
||||
premium: Util.find(ctx, ['store', 'account', 'plan'])
|
||||
}, {
|
||||
channel: supportChannel,
|
||||
curvePublic: supportKey
|
||||
|
@ -240,6 +156,41 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var replyTicket = function (ctx, data, isAdmin, cb) {
|
||||
var mailbox = Util.find(ctx, [ 'store', 'mailbox' ]);
|
||||
var anonRpc = Util.find(ctx, [ 'store', 'anon_rpc' ]);
|
||||
if (!mailbox) { return void cb('E_NOT_READY'); }
|
||||
if (!anonRpc) { return void cb("anonymous rpc session not ready"); }
|
||||
var theirPublic, myCurve;
|
||||
nThen((waitFor) => {
|
||||
getKeys(ctx, isAdmin, data, waitFor((err, obj) => {
|
||||
if (err) {
|
||||
waitFor.abort();
|
||||
return void cb({error: err});
|
||||
}
|
||||
theirPublic = obj.theirPublic;
|
||||
myCurve = obj.myCurve;
|
||||
}));
|
||||
}).nThen(() => {
|
||||
var keys = Crypto.Curve.deriveKeys(theirPublic, myCurve);
|
||||
var crypto = Crypto.Curve.createEncryptor(keys);
|
||||
var text = JSON.stringify(data.ticket);
|
||||
var ciphertext = crypto.encrypt(text);
|
||||
anonRpc.send("WRITE_PRIVATE_MESSAGE", [
|
||||
data.channel,
|
||||
ciphertext
|
||||
], (err) => {
|
||||
if (err) {
|
||||
waitFor.abort();
|
||||
return void cb(err);
|
||||
}
|
||||
cb();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// USER COMMANDS
|
||||
|
||||
var getMyTickets = function (ctx, data, cId, cb) {
|
||||
var all = [];
|
||||
var n = nThen;
|
||||
|
@ -248,7 +199,7 @@ define([
|
|||
var t = Util.clone(ctx.supportData[ticket]);
|
||||
getContent(ctx, {
|
||||
channel: ticket,
|
||||
}, waitFor((err, messages) => {
|
||||
}, false, waitFor((err, messages) => {
|
||||
if (err) { t.error = err; }
|
||||
else { t.messages = messages; }
|
||||
t.id = ticket;
|
||||
|
@ -261,6 +212,135 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
// ADMIN COMMANDS
|
||||
|
||||
var listTicketsAdmin = function (ctx, data, cId, cb) {
|
||||
if (!ctx.adminRdyEvt) { return void cb({ error: 'EFORBIDDEN' }); }
|
||||
ctx.adminRdyEvt.reg(() => {
|
||||
var doc = ctx.adminDoc.proxy;
|
||||
cb(Util.clone(doc.tickets.active));
|
||||
});
|
||||
};
|
||||
|
||||
var loadTicketAdmin = function (ctx, data, cId, cb) {
|
||||
getContent(ctx, data, true, function (err, res) {
|
||||
if (err) { return void cb({error: err}); }
|
||||
ctx.adminRdyEvt.reg(() => {
|
||||
var doc = ctx.adminDoc.proxy;
|
||||
if (Array.isArray(res) && res.length) {
|
||||
res.sort((t1, t2) => { return t1.time - t2.time; });
|
||||
let last = res[res.length - 1];
|
||||
var entry = doc.tickets.active[data.channel];
|
||||
if (entry) { entry.time = last.time; }
|
||||
}
|
||||
cb(res);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var replyTicketAdmin = function (ctx, data, cId, cb) {
|
||||
if (!ctx.adminRdyEvt) { return void cb({ error: 'EFORBIDDEN' }); }
|
||||
replyTicket(ctx, data, true, (err) => {
|
||||
if (err) { return void cb({error: err}); }
|
||||
ctx.adminRdyEvt.reg(() => {
|
||||
var doc = ctx.adminDoc.proxy;
|
||||
var entry = doc.tickets.active[data.channel] || doc.tickets.pending[data.channel];
|
||||
entry.time = +new Date();
|
||||
entry.lastAdmin = true;
|
||||
});
|
||||
cb({sent: true});
|
||||
});
|
||||
};
|
||||
|
||||
var addAdminTicket = function (ctx, data, cb) {
|
||||
// Wait for the chainpad to be ready before adding the data
|
||||
if (!ctx.adminRdyEvt) { return void cb(false); } // XXX not an admin, delete mailbox?
|
||||
|
||||
ctx.adminRdyEvt.reg(() => {
|
||||
// random timeout to avoid duplication wiht multiple admins
|
||||
var rdmTo = Math.floor(Math.random() * 2000); // Between 0 and 2000ms
|
||||
setTimeout(() => {
|
||||
var doc = ctx.adminDoc.proxy;
|
||||
if (doc.tickets.active[data.channel] || doc.tickets.closed[data.channel]) {
|
||||
return void cb(true); }
|
||||
doc.tickets.active[data.channel] = {
|
||||
title: data.title,
|
||||
premium: data.premium,
|
||||
time: data.time,
|
||||
author: data.user && data.user.displayName,
|
||||
authorKey: data.user && data.user.curvePublic
|
||||
};
|
||||
Realtime.whenRealtimeSyncs(ctx.adminDoc.realtime, function () {
|
||||
console.warn('synced');
|
||||
cb(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// INITIALIZE ADMIN
|
||||
|
||||
var loadAdminDoc = function (ctx, hash, cb) {
|
||||
var secret = Hash.getSecrets('support', hash);
|
||||
var listmapConfig = {
|
||||
data: {},
|
||||
channel: secret.channel,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
userName: 'support',
|
||||
ChainPad: ChainPad,
|
||||
classic: true,
|
||||
network: ctx.store.network,
|
||||
//Cache: Cache, // XXX XXX XXX
|
||||
metadata: {
|
||||
validateKey: secret.keys.validateKey || undefined,
|
||||
},
|
||||
};
|
||||
var rt = ctx.adminDoc = Listmap.create(listmapConfig);
|
||||
// XXX on change, tell current user that support has changed?
|
||||
rt.proxy.on('ready', function () {
|
||||
var doc = rt.proxy;
|
||||
doc.tickets = doc.tickets || {};
|
||||
doc.tickets.active = doc.tickets.active || {};
|
||||
doc.tickets.closed = doc.tickets.closed || {};
|
||||
ctx.adminRdyEvt.fire();
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
var initializeSupportAdmin = function (ctx, waitFor) {
|
||||
let unlock = waitFor();
|
||||
let proxy = ctx.store.proxy;
|
||||
let supportKey = Util.find(proxy, ['mailboxes', 'support2', 'keys', 'curvePublic']);
|
||||
let privateKey = Util.find(proxy, ['mailboxes', 'support2', 'keys', 'curvePrivate']);
|
||||
ctx.adminRdyEvt = Util.mkEvent(true);
|
||||
nThen((waitFor) => {
|
||||
getKeys(ctx, false, {}, waitFor((err, obj) => {
|
||||
setTimeout(unlock); // Unlock loading process
|
||||
if (err) { return void waitFor.abort(); }
|
||||
if (obj.theirPublic !== supportKey) {
|
||||
// Deprecated support key: no longer an admin!
|
||||
// XXX delete the mailbox?
|
||||
return void waitFor.abort();
|
||||
}
|
||||
}));
|
||||
}).nThen((waitFor) => {
|
||||
let seed = privateKey.slice(0,24); // XXX better way to get seed?
|
||||
let hash = Hash.getEditHashFromKeys({
|
||||
version: 2,
|
||||
type: 'support',
|
||||
keys: {
|
||||
editKeyStr: seed
|
||||
}
|
||||
});
|
||||
loadAdminDoc(ctx, hash, waitFor());
|
||||
}).nThen(() => {
|
||||
console.log('Support admin loaded')
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
Support.init = function (cfg, waitFor, emit) {
|
||||
var support = {};
|
||||
|
||||
|
@ -282,8 +362,7 @@ define([
|
|||
};
|
||||
|
||||
if (Util.find(store, ['proxy', 'mailboxes', 'support2'])) {
|
||||
initializeSupportAdmin(ctx);
|
||||
|
||||
initializeSupportAdmin(ctx, waitFor);
|
||||
}
|
||||
|
||||
support.ctx = ctx;
|
||||
|
@ -302,6 +381,15 @@ define([
|
|||
if (cmd === 'MAKE_TICKET') {
|
||||
return void makeTicket(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'LIST_TICKETS_ADMIN') {
|
||||
return void listTicketsAdmin(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'LOAD_TICKET_ADMIN') {
|
||||
return void loadTicketAdmin(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'REPLY_TICKET_ADMIN') {
|
||||
return void replyTicketAdmin(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'GET_MY_TICKETS') {
|
||||
return void getMyTickets(ctx, data, clientId, cb);
|
||||
}
|
||||
|
|
|
@ -26,17 +26,30 @@
|
|||
display: flex;
|
||||
flex-flow: column;
|
||||
|
||||
.cp-sidebarlayout-element {
|
||||
label:not(.cp-admin-label) {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
input {
|
||||
max-width: 25rem;
|
||||
}
|
||||
nav {
|
||||
display: flex;
|
||||
margin-top: 0.5rem;
|
||||
background: @cp_sidebar-right-bg;
|
||||
color: @cp_sidebar-right-fg;
|
||||
|
||||
#cp-content-container {
|
||||
overflow: auto;
|
||||
}
|
||||
.cp-support-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
.cp-support-column {
|
||||
min-width: 700px;
|
||||
flex: 1 0 50%;
|
||||
h1 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
button {
|
||||
margin-left: 50px !important;
|
||||
}
|
||||
}
|
||||
.cp-support-count {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ define([
|
|||
Keys,
|
||||
Support,
|
||||
Clipboard,
|
||||
Sortify,
|
||||
Sortify
|
||||
)
|
||||
{
|
||||
var APP = {
|
||||
|
@ -53,11 +53,84 @@ define([
|
|||
var common;
|
||||
var sFrameChan;
|
||||
|
||||
// XXX
|
||||
|
||||
var andThen = function () {
|
||||
var $body = $('#cp-content-container');
|
||||
var $container = $(h('div.cp-support-container')).appendTo($body);
|
||||
|
||||
|
||||
var refresh = () => {
|
||||
APP.module.execCommand('LIST_TICKETS_ADMIN', {}, (tickets) => {
|
||||
$container.empty();
|
||||
var col1 = h('div.cp-support-column', h('h1', [
|
||||
h('span', Messages.admin_support_premium),
|
||||
h('span.cp-support-count'),
|
||||
]));
|
||||
var col2 = h('div.cp-support-column', h('h1', [
|
||||
h('span', Messages.admin_support_normal),
|
||||
h('span.cp-support-count'),
|
||||
]));
|
||||
var col3 = h('div.cp-support-column', h('h1', [
|
||||
h('span', Messages.admin_support_answered),
|
||||
h('span.cp-support-count'),
|
||||
]));
|
||||
$container.append([col1, col2, col3]);
|
||||
var sortTicket = function (c1, c2) {
|
||||
return tickets[c2].time - tickets[c1].time;
|
||||
};
|
||||
|
||||
const onLoad = function (ticket, channel, data) {
|
||||
APP.module.execCommand('LOAD_TICKET_ADMIN', {
|
||||
channel: channel,
|
||||
curvePublic: data.authorKey
|
||||
}, function (obj) {
|
||||
if (!Array.isArray(obj)) {
|
||||
console.error(obj && obj.error);
|
||||
return void UI.warn(Messages.error);
|
||||
}
|
||||
obj.forEach(function (msg) {
|
||||
$(ticket).append(APP.support.makeMessage(msg));
|
||||
});
|
||||
});
|
||||
};
|
||||
const onClose = function (ticket, channel, data) {
|
||||
APP.module.execCommand('CLOSE_TICKET_ADMIN', {
|
||||
channel: channel,
|
||||
curvePublic: data.authorKey
|
||||
}, function (obj) {
|
||||
// XXX TODO
|
||||
});
|
||||
};
|
||||
const onReply = function (ticket, channel, data, form, cb) {
|
||||
// XXX TODO
|
||||
var formData = APP.support.getFormData(form);
|
||||
APP.module.execCommand('REPLY_TICKET_ADMIN', {
|
||||
channel: channel,
|
||||
curvePublic: data.authorKey,
|
||||
ticket: formData
|
||||
}, function (obj) {
|
||||
if (obj && obj.error) {
|
||||
console.error(obj && obj.error);
|
||||
return void UI.warn(Messages.error);
|
||||
}
|
||||
$(ticket).find('.cp-support-list-message').remove();
|
||||
refresh(); // XXX RE-open this ticket and scroll to?
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Object.keys(tickets).sort(sortTicket).forEach(function (channel) {
|
||||
var d = tickets[channel];
|
||||
var ticket = APP.support.makeTicketAdmin(channel, d, onLoad, onClose, onReply);
|
||||
var container;
|
||||
if (d.lastAdmin) { container = col3; }
|
||||
else if (d.premium) { container = col1; }
|
||||
else { container = col2; }
|
||||
$(container).append(ticket);
|
||||
});
|
||||
console.log(tickets);
|
||||
});
|
||||
};
|
||||
refresh();
|
||||
};
|
||||
|
||||
var createToolbar = function () {
|
||||
|
@ -81,8 +154,6 @@ define([
|
|||
APP.$toolbar = $('#cp-toolbar');
|
||||
sFrameChan = common.getSframeChannel();
|
||||
sFrameChan.onReady(waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
if (!common.isAdmin()) { return; } // XXX moderator
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
createToolbar();
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
|
@ -96,8 +167,10 @@ define([
|
|||
APP.privateKey = privateData.supportPrivateKey;
|
||||
APP.origin = privateData.origin;
|
||||
APP.readOnly = privateData.readOnly;
|
||||
APP.module = common.makeUniversal('support');
|
||||
APP.support = Support.create(common, true);
|
||||
|
||||
andThen();
|
||||
UI.removeLoadingScreen();
|
||||
|
||||
});
|
||||
|
|
|
@ -516,6 +516,66 @@ define([
|
|||
]);
|
||||
};
|
||||
|
||||
var makeTicketAdmin = function (ctx, id, content, onShow, onClose, onReply) {
|
||||
var show = h('button.btn.btn-primary.cp-support-expand', Messages.admin_support_open);
|
||||
|
||||
var answer = h('button.btn.btn-primary.cp-support-answer', Messages.support_answer);
|
||||
var close = h('button.btn.btn-danger.cp-support-close', Messages.support_close);
|
||||
var actions = h('div.cp-support-list-actions', [ answer, close ]);
|
||||
|
||||
var isPremium = content.premium ? '.cp-support-premium' : '';
|
||||
var name = Util.fixHTML(content.author) || Messages.anonymous;
|
||||
var ticket = h('div.cp-support-list-ticket.cp-not-loaded'+isPremium, {
|
||||
'data-id': id
|
||||
}, [
|
||||
h('div.cp-support-ticket-header', [
|
||||
h('span', content.title),
|
||||
UI.setHTML(h('span'), Messages._getKey('support_from', [name])),
|
||||
h('span', new Date(content.time).toLocaleString()),
|
||||
h('span', show),
|
||||
]),
|
||||
actions
|
||||
]);
|
||||
|
||||
// Add button handlers
|
||||
|
||||
UI.confirmButton(close, {
|
||||
classes: 'btn-danger'
|
||||
}, function() {
|
||||
$(close).remove();
|
||||
onClose(ticket, id, content);
|
||||
});
|
||||
|
||||
var $ticket = $(ticket);
|
||||
$(answer).click(function () {
|
||||
$ticket.find('.cp-support-form-container').remove();
|
||||
$(actions).hide();
|
||||
var form = makeForm(ctx, function () {
|
||||
console.error(form);
|
||||
onReply(ticket, id, content, form, function () {
|
||||
$(actions).css('display', '');
|
||||
$(form).remove();
|
||||
});
|
||||
/*
|
||||
var sent = sendForm(ctx, content.id, form, content.sender);
|
||||
if (sent) {
|
||||
$(actions).css('display', '');
|
||||
$(form).remove();
|
||||
}
|
||||
*/
|
||||
}, content.title, true);
|
||||
$ticket.append(form);
|
||||
});
|
||||
|
||||
var $show = $(show);
|
||||
Util.onClickEnter($show, function () {
|
||||
$ticket.removeClass('cp-not-loaded');
|
||||
$show.remove();
|
||||
onShow(ticket, id, content);
|
||||
});
|
||||
return ticket;
|
||||
};
|
||||
|
||||
var create = function (common, isAdmin, pinUsage, teamsUsage) {
|
||||
var ui = {};
|
||||
var ctx = {
|
||||
|
@ -551,6 +611,9 @@ define([
|
|||
ui.makeTicket = function ($div, content, onHide) {
|
||||
return makeTicket(ctx, $div, content, onHide);
|
||||
};
|
||||
ui.makeTicketAdmin = function (id, content, onShow, onClose, onReply) {
|
||||
return makeTicketAdmin(ctx, id, content, onShow, onClose, onReply);
|
||||
};
|
||||
ui.makeMessage = function (content, hash) {
|
||||
return makeMessage(ctx, content, hash);
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue