New support: realtime update of admin and user views

This commit is contained in:
yflory 2024-02-20 14:00:09 +01:00
parent 43047ab326
commit 503bba974c
7 changed files with 251 additions and 83 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();
}); });

View File

@ -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
}); });
}); });

View File

@ -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;

View File

@ -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);