mirror of https://github.com/xwiki-labs/cryptpad
New support: realtime update of admin and user views
This commit is contained in:
parent
43047ab326
commit
503bba974c
|
@ -59,10 +59,29 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.cp-support-ispremium {
|
||||||
|
padding: 0 5px;
|
||||||
|
background-color: @cp_admin-premium-bg;
|
||||||
|
}
|
||||||
.cp-support-list-message {
|
.cp-support-list-message {
|
||||||
background-color: @msg-bg;
|
background-color: @msg-bg;
|
||||||
padding: 5px 5px;
|
padding: 5px 5px;
|
||||||
border-radius: @variables_radius;
|
border-radius: @variables_radius;
|
||||||
|
&.cp-support-fromadmin {
|
||||||
|
background-color: @cp_admin-isadmin-bg !important;
|
||||||
|
.cp-support-message-from, .cp-support-showdata {
|
||||||
|
color: @cryptpad_text_col;
|
||||||
|
background-color: fade(@cp_admin-isadmin-bg, 10%) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
&.cp-support-frompremium {
|
||||||
|
background-color: @cp_admin-premium-bg;
|
||||||
|
.cp-support-showdata {
|
||||||
|
background-color: fade(@cp_admin-premium-bg, 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.cp-support-fromme {
|
.cp-support-fromme {
|
||||||
background-color: @fromme-bg;
|
background-color: @fromme-bg;
|
||||||
}
|
}
|
||||||
|
@ -86,6 +105,9 @@
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.cp-support-list-actions {
|
.cp-support-list-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -861,17 +861,20 @@ define([
|
||||||
var content = msg.content;
|
var content = msg.content;
|
||||||
content.time = data.time;
|
content.time = data.time;
|
||||||
|
|
||||||
if (!content.isAdmin) { // A user replied to an admin
|
|
||||||
// Update admin chainpad
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
let handle = function () {
|
let handle = function () {
|
||||||
var support = Util.find(ctx, ['store', 'modules', 'support']);
|
var support = Util.find(ctx, ['store', 'modules', 'support']);
|
||||||
if (!support && i++ < 100) { setTimeout(handle, 600); }
|
if (!support && i++ < 100) { setTimeout(handle, 600); }
|
||||||
if (!support) { return; }
|
if (!support) { return; }
|
||||||
|
if (!content.isAdmin) { // A user replied to an admin
|
||||||
|
// Update admin chainpad
|
||||||
support.updateAdminTicket(content);
|
support.updateAdminTicket(content);
|
||||||
|
} else {
|
||||||
|
// Trigger realtime update of user support
|
||||||
|
support.updateUserTicket(content);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
handle();
|
handle();
|
||||||
}
|
|
||||||
|
|
||||||
if (supportNotif) { return void cb(true); }
|
if (supportNotif) { return void cb(true); }
|
||||||
supportNotif = content.channel;
|
supportNotif = content.channel;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
define([
|
define([
|
||||||
|
'/api/config',
|
||||||
'/common/common-util.js',
|
'/common/common-util.js',
|
||||||
'/common/common-hash.js',
|
'/common/common-hash.js',
|
||||||
'/common/common-realtime.js',
|
'/common/common-realtime.js',
|
||||||
|
@ -11,7 +12,7 @@ define([
|
||||||
'chainpad-listmap',
|
'chainpad-listmap',
|
||||||
'/components/chainpad/chainpad.dist.js',
|
'/components/chainpad/chainpad.dist.js',
|
||||||
'chainpad-netflux'
|
'chainpad-netflux'
|
||||||
], function (Util, Hash, Realtime, nThen, Crypto, Listmap, ChainPad, CpNetflux) {
|
], function (ApiConfig, Util, Hash, Realtime, nThen, Crypto, Listmap, ChainPad, CpNetflux) {
|
||||||
var Support = {};
|
var Support = {};
|
||||||
|
|
||||||
// UTILS
|
// UTILS
|
||||||
|
@ -20,6 +21,8 @@ define([
|
||||||
var cb = Util.mkAsync(_cb);
|
var cb = Util.mkAsync(_cb);
|
||||||
if (isAdmin && !ctx.adminRdyEvt) { return void cb('EFORBIDDEN'); }
|
if (isAdmin && !ctx.adminRdyEvt) { return void cb('EFORBIDDEN'); }
|
||||||
require(['/api/config?' + (+new Date())], function (NewConfig) {
|
require(['/api/config?' + (+new Date())], function (NewConfig) {
|
||||||
|
ctx.adminKeys = NewConfig.adminKeys; // Update admin keys // XXX MODERATOR
|
||||||
|
|
||||||
var supportKey = NewConfig.newSupportMailbox;
|
var supportKey = NewConfig.newSupportMailbox;
|
||||||
if (!supportKey) { return void cb('E_NOT_INIT'); }
|
if (!supportKey) { return void cb('E_NOT_INIT'); }
|
||||||
|
|
||||||
|
@ -222,6 +225,11 @@ define([
|
||||||
var getMyTickets = function (ctx, data, cId, cb) {
|
var getMyTickets = function (ctx, data, cId, cb) {
|
||||||
var all = [];
|
var all = [];
|
||||||
var n = nThen;
|
var n = nThen;
|
||||||
|
if (!ctx.clients[cId]) {
|
||||||
|
ctx.clients[cId] = {
|
||||||
|
admin: false
|
||||||
|
};
|
||||||
|
}
|
||||||
Object.keys(ctx.supportData).forEach(function (ticket) {
|
Object.keys(ctx.supportData).forEach(function (ticket) {
|
||||||
n = n((waitFor) => {
|
n = n((waitFor) => {
|
||||||
var t = Util.clone(ctx.supportData[ticket]);
|
var t = Util.clone(ctx.supportData[ticket]);
|
||||||
|
@ -250,6 +258,11 @@ define([
|
||||||
|
|
||||||
var listTicketsAdmin = function (ctx, data, cId, cb) {
|
var listTicketsAdmin = function (ctx, data, cId, cb) {
|
||||||
if (!ctx.adminRdyEvt) { return void cb({ error: 'EFORBIDDEN' }); }
|
if (!ctx.adminRdyEvt) { return void cb({ error: 'EFORBIDDEN' }); }
|
||||||
|
if (!ctx.clients[cId]) {
|
||||||
|
ctx.clients[cId] = {
|
||||||
|
admin: true
|
||||||
|
};
|
||||||
|
}
|
||||||
ctx.adminRdyEvt.reg(() => {
|
ctx.adminRdyEvt.reg(() => {
|
||||||
var doc = ctx.adminDoc.proxy;
|
var doc = ctx.adminDoc.proxy;
|
||||||
cb(Util.clone(doc.tickets.active));
|
cb(Util.clone(doc.tickets.active));
|
||||||
|
@ -264,8 +277,22 @@ define([
|
||||||
if (Array.isArray(res) && res.length) {
|
if (Array.isArray(res) && res.length) {
|
||||||
res.sort((t1, t2) => { return t1.time - t2.time; });
|
res.sort((t1, t2) => { return t1.time - t2.time; });
|
||||||
let last = res[res.length - 1];
|
let last = res[res.length - 1];
|
||||||
|
let premium = res.some((msg) => {
|
||||||
|
let curve = Util.find(msg, ['sender', 'curvePublic']);
|
||||||
|
if (data.curvePublic !== curve) { return; }
|
||||||
|
return Util.find(msg, ['sender', 'quota', 'plan']);
|
||||||
|
});
|
||||||
|
var senderKey = last.sender && last.sender.edPublic;
|
||||||
|
|
||||||
var entry = doc.tickets.active[data.channel];
|
var entry = doc.tickets.active[data.channel];
|
||||||
if (entry) { entry.time = last.time; }
|
if (entry) {
|
||||||
|
entry.time = last.time;
|
||||||
|
entry.premium = premium;
|
||||||
|
|
||||||
|
if (senderKey) {
|
||||||
|
entry.lastAdmin = ctx.adminKeys.indexOf(senderKey) !== -1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cb(res);
|
cb(res);
|
||||||
});
|
});
|
||||||
|
@ -288,6 +315,14 @@ define([
|
||||||
|
|
||||||
// Mailbox events
|
// Mailbox events
|
||||||
|
|
||||||
|
var notifyClient = function (ctx, admin, type, channel) {
|
||||||
|
let notifyList = Object.keys(ctx.clients).filter((cId) => {
|
||||||
|
return Boolean(ctx.clients[cId].admin) === admin;
|
||||||
|
});
|
||||||
|
if (!notifyList.length) { return; }
|
||||||
|
ctx.emit(type, { channel }, [notifyList]);
|
||||||
|
};
|
||||||
|
|
||||||
var addAdminTicket = function (ctx, data, cb) {
|
var addAdminTicket = function (ctx, data, cb) {
|
||||||
// Wait for the chainpad to be ready before adding the data
|
// Wait for the chainpad to be ready before adding the data
|
||||||
if (!ctx.adminRdyEvt) { return void cb(false); } // XXX not an admin, delete mailbox?
|
if (!ctx.adminRdyEvt) { return void cb(false); } // XXX not an admin, delete mailbox?
|
||||||
|
@ -310,6 +345,7 @@ define([
|
||||||
Realtime.whenRealtimeSyncs(ctx.adminDoc.realtime, function () {
|
Realtime.whenRealtimeSyncs(ctx.adminDoc.realtime, function () {
|
||||||
cb(false);
|
cb(false);
|
||||||
});
|
});
|
||||||
|
notifyClient(ctx, true, 'NEW_TICKET', data.channel);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -325,10 +361,15 @@ define([
|
||||||
if (!doc.tickets.active[data.channel] && !doc.tickets.pending[data.channel]) {
|
if (!doc.tickets.active[data.channel] && !doc.tickets.pending[data.channel]) {
|
||||||
return; }
|
return; }
|
||||||
let t = doc.tickets.active[data.channel] || doc.tickets.pending[data.channel];
|
let t = doc.tickets.active[data.channel] || doc.tickets.pending[data.channel];
|
||||||
t.time = data.time;
|
if (data.time > (t.time + 2000)) { t.time = data.time; }
|
||||||
|
t.lastAdmin = false;
|
||||||
|
notifyClient(ctx, true, 'UPDATE_TICKET', data.channel);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
var updateUserTicket = function (ctx, data) {
|
||||||
|
notifyClient(ctx, false, 'UPDATE_TICKET', data.channel);
|
||||||
|
};
|
||||||
|
|
||||||
// INITIALIZE ADMIN
|
// INITIALIZE ADMIN
|
||||||
|
|
||||||
|
@ -354,6 +395,7 @@ define([
|
||||||
doc.tickets = doc.tickets || {};
|
doc.tickets = doc.tickets || {};
|
||||||
doc.tickets.active = doc.tickets.active || {};
|
doc.tickets.active = doc.tickets.active || {};
|
||||||
doc.tickets.closed = doc.tickets.closed || {};
|
doc.tickets.closed = doc.tickets.closed || {};
|
||||||
|
doc.tickets.pending = doc.tickets.pending || {};
|
||||||
ctx.adminRdyEvt.fire();
|
ctx.adminRdyEvt.fire();
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
|
@ -405,11 +447,11 @@ define([
|
||||||
var proxy = store.proxy.support = store.proxy.support || {};
|
var proxy = store.proxy.support = store.proxy.support || {};
|
||||||
|
|
||||||
var ctx = {
|
var ctx = {
|
||||||
|
adminKeys: ApiConfig.adminKeys,
|
||||||
supportData: proxy,
|
supportData: proxy,
|
||||||
store: cfg.store,
|
store: cfg.store,
|
||||||
Store: cfg.Store,
|
Store: cfg.Store,
|
||||||
emit: emit,
|
emit: emit,
|
||||||
channels: {},
|
|
||||||
clients: {}
|
clients: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -419,7 +461,7 @@ define([
|
||||||
|
|
||||||
support.ctx = ctx;
|
support.ctx = ctx;
|
||||||
support.removeClient = function (clientId) {
|
support.removeClient = function (clientId) {
|
||||||
// XXX TODO
|
delete ctx.clients[clientId];
|
||||||
};
|
};
|
||||||
support.leavePad = function (padChan) {
|
support.leavePad = function (padChan) {
|
||||||
// XXX TODO
|
// XXX TODO
|
||||||
|
@ -430,6 +472,9 @@ define([
|
||||||
support.updateAdminTicket = function (content) {
|
support.updateAdminTicket = function (content) {
|
||||||
updateAdminTicket(ctx, content);
|
updateAdminTicket(ctx, content);
|
||||||
};
|
};
|
||||||
|
support.updateUserTicket = function (content) {
|
||||||
|
updateUserTicket(ctx, content);
|
||||||
|
};
|
||||||
support.execCommand = function (clientId, obj, cb) {
|
support.execCommand = function (clientId, obj, cb) {
|
||||||
var cmd = obj.cmd;
|
var cmd = obj.cmd;
|
||||||
var data = obj.data;
|
var data = obj.data;
|
||||||
|
|
|
@ -52,16 +52,27 @@ define([
|
||||||
var Nacl = window.nacl;
|
var Nacl = window.nacl;
|
||||||
var common;
|
var common;
|
||||||
var sFrameChan;
|
var sFrameChan;
|
||||||
|
var events = {
|
||||||
|
'NEW_TICKET': Util.mkEvent(),
|
||||||
|
'UPDATE_TICKET': Util.mkEvent()
|
||||||
|
};
|
||||||
|
|
||||||
var andThen = function () {
|
var andThen = function (linkedTicket) {
|
||||||
var $body = $('#cp-content-container');
|
var $body = $('#cp-content-container');
|
||||||
var button = h('button.btn.btn-primary', 'refresh'); // XXX
|
var button = h('button.btn.btn-primary', 'refresh'); // XXX
|
||||||
$body.append(h('div', button));
|
$body.append(h('div', button));
|
||||||
var $container = $(h('div.cp-support-container')).appendTo($body);
|
var $container = $(h('div.cp-support-container')).appendTo($body);
|
||||||
|
|
||||||
|
|
||||||
var refresh = () => {
|
let open = [];
|
||||||
|
let refresh = () => {
|
||||||
APP.module.execCommand('LIST_TICKETS_ADMIN', {}, (tickets) => {
|
APP.module.execCommand('LIST_TICKETS_ADMIN', {}, (tickets) => {
|
||||||
|
let activeForms = {};
|
||||||
|
$container.find('.cp-support-form-container').each((i, el) => {
|
||||||
|
let id = $(el).attr('data-id');
|
||||||
|
if (!id) { return; }
|
||||||
|
activeForms[id] = el;
|
||||||
|
});
|
||||||
$container.empty();
|
$container.empty();
|
||||||
var col1 = h('div.cp-support-column', h('h1', [
|
var col1 = h('div.cp-support-column', h('h1', [
|
||||||
h('span', Messages.admin_support_premium),
|
h('span', Messages.admin_support_premium),
|
||||||
|
@ -80,24 +91,33 @@ define([
|
||||||
return tickets[c2].time - tickets[c1].time;
|
return tickets[c2].time - tickets[c1].time;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onLoad = function (ticket, channel, data) {
|
const onShow = function (ticket, channel, data, done) {
|
||||||
APP.module.execCommand('LOAD_TICKET_ADMIN', {
|
APP.module.execCommand('LOAD_TICKET_ADMIN', {
|
||||||
channel: channel,
|
channel: channel,
|
||||||
curvePublic: data.authorKey
|
curvePublic: data.authorKey
|
||||||
}, function (obj) {
|
}, function (obj) {
|
||||||
if (!Array.isArray(obj)) {
|
if (!Array.isArray(obj)) {
|
||||||
console.error(obj && obj.error);
|
console.error(obj && obj.error);
|
||||||
|
done();
|
||||||
return void UI.warn(Messages.error);
|
return void UI.warn(Messages.error);
|
||||||
}
|
}
|
||||||
obj.forEach(function (msg) {
|
obj.forEach(function (msg) {
|
||||||
console.error(msg);
|
|
||||||
if (!data.notifications) {
|
if (!data.notifications) {
|
||||||
data.notifications = Util.find(msg, ['sender', 'notifications']);
|
data.notifications = Util.find(msg, ['sender', 'notifications']);
|
||||||
}
|
}
|
||||||
$(ticket).append(APP.support.makeMessage(msg));
|
$(ticket).append(APP.support.makeMessage(msg));
|
||||||
});
|
});
|
||||||
|
if (!open.includes(channel)) { open.push(channel); }
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
const onHide = function (ticket, channel, data, done) {
|
||||||
|
$(ticket).find('.cp-support-list-message').remove();
|
||||||
|
open = open.filter((chan) => {
|
||||||
|
return chan !== channel;
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
};
|
||||||
const onClose = function (ticket, channel, data) {
|
const onClose = function (ticket, channel, data) {
|
||||||
APP.module.execCommand('CLOSE_TICKET_ADMIN', {
|
APP.module.execCommand('CLOSE_TICKET_ADMIN', {
|
||||||
channel: channel,
|
channel: channel,
|
||||||
|
@ -120,26 +140,43 @@ define([
|
||||||
return void UI.warn(Messages.error);
|
return void UI.warn(Messages.error);
|
||||||
}
|
}
|
||||||
$(ticket).find('.cp-support-list-message').remove();
|
$(ticket).find('.cp-support-list-message').remove();
|
||||||
refresh(); // XXX RE-open this ticket and scroll to?
|
$(ticket).find('.cp-support-form-container').remove();
|
||||||
|
refresh();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Object.keys(tickets).sort(sortTicket).forEach(function (channel) {
|
Object.keys(tickets).sort(sortTicket).forEach(function (channel) {
|
||||||
var d = tickets[channel];
|
var d = tickets[channel];
|
||||||
var ticket = APP.support.makeTicket(channel, d, onLoad, onClose, onReply);
|
var ticket = APP.support.makeTicket({
|
||||||
|
id: channel,
|
||||||
|
content: d,
|
||||||
|
form: activeForms[channel],
|
||||||
|
onShow, onHide, onClose, onReply
|
||||||
|
});
|
||||||
|
|
||||||
var container;
|
var container;
|
||||||
if (d.lastAdmin) { container = col3; }
|
if (d.lastAdmin) { container = col3; }
|
||||||
else if (d.premium) { container = col1; }
|
else if (d.premium) { container = col1; }
|
||||||
else { container = col2; }
|
else { container = col2; }
|
||||||
$(container).append(ticket);
|
$(container).append(ticket);
|
||||||
|
|
||||||
|
if (open.includes(channel)) { return void ticket.open(); }
|
||||||
|
if (linkedTicket === channel) {
|
||||||
|
linkedTicket = undefined;
|
||||||
|
ticket.open();
|
||||||
|
ticket.scrollIntoView();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
open = [];
|
||||||
console.log(tickets);
|
console.log(tickets);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
let _refresh = Util.throttle(refresh, 500);
|
||||||
Util.onClickEnter($(button), function () {
|
Util.onClickEnter($(button), function () {
|
||||||
refresh();
|
refresh();
|
||||||
});
|
});
|
||||||
|
events.NEW_TICKET.reg(_refresh);
|
||||||
|
events.UPDATE_TICKET.reg(_refresh); // XXX dont refresh all?
|
||||||
refresh();
|
refresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -177,10 +214,24 @@ define([
|
||||||
APP.privateKey = privateData.supportPrivateKey;
|
APP.privateKey = privateData.supportPrivateKey;
|
||||||
APP.origin = privateData.origin;
|
APP.origin = privateData.origin;
|
||||||
APP.readOnly = privateData.readOnly;
|
APP.readOnly = privateData.readOnly;
|
||||||
APP.module = common.makeUniversal('support');
|
APP.module = common.makeUniversal('support', {
|
||||||
|
onEvent: (obj) => {
|
||||||
|
let cmd = obj.ev;
|
||||||
|
let data = obj.data;
|
||||||
|
if (!events[cmd]) { return; }
|
||||||
|
events[cmd].fire(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
APP.support = Support.create(common, true);
|
APP.support = Support.create(common, true);
|
||||||
|
|
||||||
andThen();
|
let active = privateData.category || 'active';
|
||||||
|
let linkedTicket;
|
||||||
|
if (active.indexOf('-') !== -1) {
|
||||||
|
linkedTicket = active.split('-')[1];
|
||||||
|
active = active.split('-')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
andThen(linkedTicket);
|
||||||
UI.removeLoadingScreen();
|
UI.removeLoadingScreen();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,20 +16,6 @@ define([
|
||||||
}).nThen(function (waitFor) {
|
}).nThen(function (waitFor) {
|
||||||
SFCommonO.initIframe(waitFor);
|
SFCommonO.initIframe(waitFor);
|
||||||
}).nThen(function (/*waitFor*/) {
|
}).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);
|
|
||||||
});
|
|
||||||
sframeChan.on('Q_UPDATE_LIMIT', function (data, cb) {
|
|
||||||
Cryptpad.updatePinLimit(function (e) {
|
|
||||||
cb({error: e});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var category;
|
var category;
|
||||||
if (window.location.hash) {
|
if (window.location.hash) {
|
||||||
category = window.location.hash.slice(1);
|
category = window.location.hash.slice(1);
|
||||||
|
@ -40,7 +26,6 @@ define([
|
||||||
};
|
};
|
||||||
SFCommonO.start({
|
SFCommonO.start({
|
||||||
noRealtime: true,
|
noRealtime: true,
|
||||||
addRpc: addRpc,
|
|
||||||
addData: addData
|
addData: addData
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -152,6 +152,9 @@ define([
|
||||||
return $div;
|
return $div;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var events = {
|
||||||
|
'UPDATE_TICKET': Util.mkEvent()
|
||||||
|
};
|
||||||
create['listnew'] = function () {
|
create['listnew'] = function () {
|
||||||
var key = 'listnew';
|
var key = 'listnew';
|
||||||
var $div = makeBlock(key); // Msg.support_listHint, .support_listTitle
|
var $div = makeBlock(key); // Msg.support_listHint, .support_listTitle
|
||||||
|
@ -159,6 +162,8 @@ define([
|
||||||
var $list = $(list);
|
var $list = $(list);
|
||||||
|
|
||||||
|
|
||||||
|
let activeForm = {}; // .channel and .form
|
||||||
|
|
||||||
let refresh = function () {
|
let refresh = function () {
|
||||||
const onClose = function (ticket, channel, data) {
|
const onClose = function (ticket, channel, data) {
|
||||||
APP.supportModule.execCommand('CLOSE_TICKET', {
|
APP.supportModule.execCommand('CLOSE_TICKET', {
|
||||||
|
@ -176,7 +181,8 @@ define([
|
||||||
ticket: formData
|
ticket: formData
|
||||||
}, function (obj) {
|
}, function (obj) {
|
||||||
if (obj && obj.error) { return void UI.warn(Messages.error); }
|
if (obj && obj.error) { return void UI.warn(Messages.error); }
|
||||||
refresh(); // XXX RE-open this ticket and scroll to?
|
$(ticket).find('.cp-support-form-container').remove();
|
||||||
|
refresh();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -185,6 +191,15 @@ define([
|
||||||
return void UI.warn(Messages.error);
|
return void UI.warn(Messages.error);
|
||||||
}
|
}
|
||||||
if (!Array.isArray(obj.tickets)) { return void UI.warn(Messages.error); }
|
if (!Array.isArray(obj.tickets)) { return void UI.warn(Messages.error); }
|
||||||
|
|
||||||
|
// Recover forms
|
||||||
|
let activeForms = {};
|
||||||
|
$list.find('.cp-support-form-container').each((i, el) => {
|
||||||
|
let id = $(el).attr('data-id');
|
||||||
|
if (!id) { return; }
|
||||||
|
activeForms[id] = el;
|
||||||
|
});
|
||||||
|
|
||||||
$list.empty();
|
$list.empty();
|
||||||
obj.tickets.forEach((data) => {
|
obj.tickets.forEach((data) => {
|
||||||
var title = data.title;
|
var title = data.title;
|
||||||
|
@ -192,7 +207,12 @@ define([
|
||||||
var messages = data.messages;
|
var messages = data.messages;
|
||||||
var first = messages[0];
|
var first = messages[0];
|
||||||
first.id = data.id;
|
first.id = data.id;
|
||||||
var ticket = APP.support.makeTicket(data.id, data, null, onClose, onReply);
|
var ticket = APP.support.makeTicket({
|
||||||
|
id: data.id,
|
||||||
|
content: data,
|
||||||
|
form: activeForms[data.id],
|
||||||
|
onClose, onReply
|
||||||
|
});
|
||||||
$list.append(ticket);
|
$list.append(ticket);
|
||||||
messages.forEach(msg => {
|
messages.forEach(msg => {
|
||||||
$(ticket).append(APP.support.makeMessage(msg));
|
$(ticket).append(APP.support.makeMessage(msg));
|
||||||
|
@ -205,6 +225,8 @@ define([
|
||||||
Util.onClickEnter($(button), function () {
|
Util.onClickEnter($(button), function () {
|
||||||
refresh();
|
refresh();
|
||||||
});
|
});
|
||||||
|
let _refresh = Util.throttle(refresh, 500);;
|
||||||
|
events.UPDATE_TICKET.reg(_refresh);
|
||||||
refresh();
|
refresh();
|
||||||
$div.append([
|
$div.append([
|
||||||
button,
|
button,
|
||||||
|
@ -409,7 +431,14 @@ define([
|
||||||
APP.origin = privateData.origin;
|
APP.origin = privateData.origin;
|
||||||
APP.readOnly = privateData.readOnly;
|
APP.readOnly = privateData.readOnly;
|
||||||
APP.support = Support.create(common, false, APP.pinUsage, APP.teamsUsage);
|
APP.support = Support.create(common, false, APP.pinUsage, APP.teamsUsage);
|
||||||
APP.supportModule = common.makeUniversal('support');
|
APP.supportModule = common.makeUniversal('support', {
|
||||||
|
onEvent: (obj) => {
|
||||||
|
let cmd = obj.ev;
|
||||||
|
let data = obj.data;
|
||||||
|
if (!events[cmd]) { return; }
|
||||||
|
events[cmd].fire(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
var $rightside = APP.$rightside;
|
var $rightside = APP.$rightside;
|
||||||
|
|
|
@ -187,7 +187,7 @@ define([
|
||||||
abuse: Pages.customURLs.terms,
|
abuse: Pages.customURLs.terms,
|
||||||
};
|
};
|
||||||
|
|
||||||
var makeForm = function (ctx, cb, title, hideNotice) {
|
var makeForm = function (ctx, oldData, cb, title, hideNotice) {
|
||||||
var button;
|
var button;
|
||||||
|
|
||||||
if (typeof(cb) === "function") {
|
if (typeof(cb) === "function") {
|
||||||
|
@ -253,7 +253,7 @@ define([
|
||||||
cb ? undefined : h('br'),
|
cb ? undefined : h('br'),
|
||||||
h('textarea.cp-support-form-msg', {
|
h('textarea.cp-support-form-msg', {
|
||||||
placeholder: Messages.support_formMessage
|
placeholder: Messages.support_formMessage
|
||||||
}),
|
}, (oldData && oldData.message) || ''),
|
||||||
h('label', Messages.support_attachments),
|
h('label', Messages.support_attachments),
|
||||||
attachments = h('div.cp-support-attachments'),
|
attachments = h('div.cp-support-attachments'),
|
||||||
addAttachment = h('button.btn', Messages.support_addAttachment),
|
addAttachment = h('button.btn', Messages.support_addAttachment),
|
||||||
|
@ -262,6 +262,32 @@ define([
|
||||||
cancel
|
cancel
|
||||||
];
|
];
|
||||||
|
|
||||||
|
var _addAttachment = (name, href) => {
|
||||||
|
var x, a;
|
||||||
|
var span = h('span', {
|
||||||
|
'data-name': name,
|
||||||
|
'data-href': href
|
||||||
|
}, [
|
||||||
|
x = h('i.fa.fa-times'),
|
||||||
|
a = h('a', {
|
||||||
|
href: '#'
|
||||||
|
}, name)
|
||||||
|
]);
|
||||||
|
$(x).click(function () {
|
||||||
|
$(span).remove();
|
||||||
|
});
|
||||||
|
$(a).click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
ctx.common.openURL(href);
|
||||||
|
});
|
||||||
|
|
||||||
|
$(attachments).append(span);
|
||||||
|
};
|
||||||
|
if (oldData && Array.isArray(oldData.attachments)) {
|
||||||
|
oldData.attachments.forEach((data) => {
|
||||||
|
_addAttachment(data.name, data.href);
|
||||||
|
});
|
||||||
|
}
|
||||||
$(addAttachment).click(function () {
|
$(addAttachment).click(function () {
|
||||||
var $input = $('<input>', {
|
var $input = $('<input>', {
|
||||||
'type': 'file',
|
'type': 'file',
|
||||||
|
@ -273,25 +299,7 @@ define([
|
||||||
files.forEach(function (file) {
|
files.forEach(function (file) {
|
||||||
var ev = {};
|
var ev = {};
|
||||||
ev.callback = function (data) {
|
ev.callback = function (data) {
|
||||||
var x, a;
|
_addAttachment(data.name, data.url);
|
||||||
var span = h('span', {
|
|
||||||
'data-name': data.name,
|
|
||||||
'data-href': data.url
|
|
||||||
}, [
|
|
||||||
x = h('i.fa.fa-times'),
|
|
||||||
a = h('a', {
|
|
||||||
href: '#'
|
|
||||||
}, data.name)
|
|
||||||
]);
|
|
||||||
$(x).click(function () {
|
|
||||||
$(span).remove();
|
|
||||||
});
|
|
||||||
$(a).click(function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
ctx.common.openURL(data.url);
|
|
||||||
});
|
|
||||||
|
|
||||||
$(attachments).append(span);
|
|
||||||
};
|
};
|
||||||
// The empty object allows us to bypass the file upload modal
|
// The empty object allows us to bypass the file upload modal
|
||||||
ctx.FM.handleFile(file, ev, {});
|
ctx.FM.handleFile(file, ev, {});
|
||||||
|
@ -310,7 +318,8 @@ define([
|
||||||
return form;
|
return form;
|
||||||
};
|
};
|
||||||
|
|
||||||
var makeTicket = function (ctx, id, content, onShow, onClose, onReply) {
|
var makeTicket = function (ctx, opts) {
|
||||||
|
let { id, content, form, onShow, onHide, onClose, onReply, onForm } = opts;
|
||||||
var common = ctx.common;
|
var common = ctx.common;
|
||||||
var metadataMgr = common.getMetadataMgr();
|
var metadataMgr = common.getMetadataMgr();
|
||||||
var privateData = metadataMgr.getPrivateData();
|
var privateData = metadataMgr.getPrivateData();
|
||||||
|
@ -321,10 +330,10 @@ define([
|
||||||
|
|
||||||
var adminActions;
|
var adminActions;
|
||||||
var adminClasses = '';
|
var adminClasses = '';
|
||||||
|
var adminOpen;
|
||||||
if (ctx.isAdmin) {
|
if (ctx.isAdmin) {
|
||||||
// Admin custom style
|
// Admin custom style
|
||||||
let isPremium = content.premium ? '.cp-support-premium' : '';
|
adminClasses = `.cp-not-loaded`;
|
||||||
adminClasses = `.cp-not-loaded${isPremium}`;
|
|
||||||
// Admin actions
|
// Admin actions
|
||||||
let show = h('button.btn.btn-primary.cp-support-expand', Messages.admin_support_open);
|
let show = h('button.btn.btn-primary.cp-support-expand', Messages.admin_support_open);
|
||||||
let $show = $(show);
|
let $show = $(show);
|
||||||
|
@ -335,31 +344,49 @@ define([
|
||||||
]);
|
]);
|
||||||
$(url).click(function (e) {
|
$(url).click(function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
var link = privateData.origin + privateData.pathname + '#' + 'support-' + id;
|
var link = privateData.origin + privateData.pathname + '#' + 'active-' + id;
|
||||||
Clipboard.copy(link, (err) => {
|
Clipboard.copy(link, (err) => {
|
||||||
if (!err) { UI.log(Messages.shareSuccess); }
|
if (!err) { UI.log(Messages.shareSuccess); }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Util.onClickEnter($show, function () {
|
|
||||||
$ticket.removeClass('cp-not-loaded');
|
|
||||||
$show.remove();
|
let visible = false;
|
||||||
onShow(ticket, id, content);
|
adminOpen = function (force) {
|
||||||
|
$show.prop('disabled', 'disabled');
|
||||||
|
if (visible && !force) {
|
||||||
|
$ticket.toggleClass('cp-not-loaded', true);
|
||||||
|
return onHide(ticket, id, content, function () {
|
||||||
|
visible = false;
|
||||||
|
$show.text(Messages.admin_support_open);
|
||||||
|
$show.prop('disabled', '');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
$ticket.toggleClass('cp-not-loaded', false);
|
||||||
|
onShow(ticket, id, content, function () {
|
||||||
|
visible = true;
|
||||||
|
$show.text(Messages.admin_support_collapse);
|
||||||
|
$show.prop('disabled', '');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Util.onClickEnter($show, adminOpen);
|
||||||
adminActions = h('span.cp-support-title-buttons', [ url, show ])
|
adminActions = h('span.cp-support-title-buttons', [ url, show ])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isPremium = content.premium ? '.cp-support-ispremium' : '';
|
||||||
var name = Util.fixHTML(content.author) || Messages.anonymous;
|
var name = Util.fixHTML(content.author) || Messages.anonymous;
|
||||||
var ticket = h(`div.cp-support-list-ticket${adminClasses}`, {
|
var ticket = h(`div.cp-support-list-ticket${adminClasses}`, {
|
||||||
'data-id': id
|
'data-id': id
|
||||||
}, [
|
}, [
|
||||||
h('div.cp-support-ticket-header', [
|
h('div.cp-support-ticket-header', [
|
||||||
h('span', content.title),
|
h('span', content.title),
|
||||||
ctx.isAdmin ? UI.setHTML(h('span'), Messages._getKey('support_from', [name])) : '',
|
ctx.isAdmin ? UI.setHTML(h(`span${isPremium}`), Messages._getKey('support_from', [name])) : '',
|
||||||
h('span', new Date(content.time).toLocaleString()),
|
h('span', new Date(content.time).toLocaleString()),
|
||||||
adminActions,
|
adminActions,
|
||||||
]),
|
]),
|
||||||
actions
|
actions
|
||||||
]);
|
]);
|
||||||
|
ticket.open = adminOpen;
|
||||||
|
|
||||||
// Add button handlers
|
// Add button handlers
|
||||||
var $ticket = $(ticket);
|
var $ticket = $(ticket);
|
||||||
|
@ -369,17 +396,23 @@ define([
|
||||||
$(close).remove();
|
$(close).remove();
|
||||||
onClose(ticket, id, content);
|
onClose(ticket, id, content);
|
||||||
});
|
});
|
||||||
$(answer).click(function () {
|
|
||||||
|
var addForm = function () {
|
||||||
$ticket.find('.cp-support-form-container').remove();
|
$ticket.find('.cp-support-form-container').remove();
|
||||||
$(actions).hide();
|
$(actions).hide();
|
||||||
var form = makeForm(ctx, function () {
|
|
||||||
onReply(ticket, id, content, form, function () {
|
var oldData = form ? getFormData(ctx, form) : {};
|
||||||
|
form = undefined;
|
||||||
|
var newForm = makeForm(ctx, oldData, function () {
|
||||||
|
onReply(ticket, id, content, newForm, function () {
|
||||||
$(actions).css('display', '');
|
$(actions).css('display', '');
|
||||||
$(form).remove();
|
|
||||||
});
|
});
|
||||||
}, content.title, true);
|
}, content.title, true);
|
||||||
$ticket.append(form);
|
$(newForm).attr('data-id', id);
|
||||||
});
|
$ticket.append(newForm);
|
||||||
|
};
|
||||||
|
if (form) { addForm(); }
|
||||||
|
$(answer).click(addForm);
|
||||||
|
|
||||||
return ticket;
|
return ticket;
|
||||||
};
|
};
|
||||||
|
@ -446,7 +479,7 @@ define([
|
||||||
$pre.text(displayed);
|
$pre.text(displayed);
|
||||||
|
|
||||||
var adminClass = (fromAdmin? '.cp-support-fromadmin': '');
|
var adminClass = (fromAdmin? '.cp-support-fromadmin': '');
|
||||||
var premiumClass = (fromPremium && !fromAdmin? '.cp-support-frompremium': '');
|
var premiumClass = (ctx.isAdmin && fromPremium && !fromAdmin? '.cp-support-frompremium': '');
|
||||||
var name = Util.fixHTML(content.sender.name) || Messages.anonymous;
|
var name = Util.fixHTML(content.sender.name) || Messages.anonymous;
|
||||||
return h('div.cp-support-list-message' + adminClass + premiumClass, {
|
return h('div.cp-support-list-message' + adminClass + premiumClass, {
|
||||||
'data-hash': hash
|
'data-hash': hash
|
||||||
|
@ -507,13 +540,13 @@ define([
|
||||||
return getFormData(ctx, form);
|
return getFormData(ctx, form);
|
||||||
};
|
};
|
||||||
ui.makeForm = function (cb, title, hideNotice) {
|
ui.makeForm = function (cb, title, hideNotice) {
|
||||||
return makeForm(ctx, cb, title, hideNotice);
|
return makeForm(ctx, {}, cb, title, hideNotice);
|
||||||
};
|
};
|
||||||
ui.makeCategoryDropdown = function (container, onChange, all) {
|
ui.makeCategoryDropdown = function (container, onChange, all) {
|
||||||
return makeCategoryDropdown(ctx, container, onChange, all);
|
return makeCategoryDropdown(ctx, container, onChange, all);
|
||||||
};
|
};
|
||||||
ui.makeTicket = function (id, content, onShow, onClose, onReply) {
|
ui.makeTicket = function (opts) {
|
||||||
return makeTicket(ctx, id, content, onShow, onClose, onReply);
|
return makeTicket(ctx, opts);
|
||||||
};
|
};
|
||||||
ui.makeMessage = function (content, hash) {
|
ui.makeMessage = function (content, hash) {
|
||||||
return makeMessage(ctx, content, hash);
|
return makeMessage(ctx, content, hash);
|
||||||
|
|
Loading…
Reference in New Issue