New support: create ticket from admin side
This commit is contained in:
parent
47284dc1ec
commit
0056603486
9 changed files with 242 additions and 51 deletions
|
@ -91,6 +91,12 @@
|
|||
.cp-support-message-data {
|
||||
display: none;
|
||||
cursor: default;
|
||||
position: relative;
|
||||
.cp-support-copydata {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-support-message-time {
|
||||
|
|
|
@ -50,7 +50,7 @@ define([
|
|||
icon ? h('i', { 'class': icon }) : undefined,
|
||||
h('span', text)
|
||||
]);
|
||||
}
|
||||
};
|
||||
blocks.nav = (buttons) => {
|
||||
return h('nav', buttons);
|
||||
};
|
||||
|
@ -221,12 +221,16 @@ define([
|
|||
var isActive = key === active ? '.cp-leftside-active' : '';
|
||||
var item = h('li.cp-sidebarlayout-category'+isActive, {
|
||||
'role': 'menuitem',
|
||||
'tabindex': 0
|
||||
'tabindex': 0,
|
||||
'data-category': key
|
||||
}, [
|
||||
icon,
|
||||
Messages[`${app}_cat_${key}`] || key,
|
||||
]);
|
||||
var $item = $(item).appendTo(container);
|
||||
category.open = function () {
|
||||
$item.click();
|
||||
};
|
||||
|
||||
Util.onClickEnter($item, function () {
|
||||
if (!Array.isArray(category.content) && category.onClick) {
|
||||
|
@ -245,6 +249,10 @@ define([
|
|||
$leftside.append(container);
|
||||
};
|
||||
|
||||
sidebar.openCategory = name => {
|
||||
$(`.cp-sidebarlayout-category[data-category="${name}"]`).click();
|
||||
};
|
||||
|
||||
sidebar.disableItem = (key) => {
|
||||
$(items[key]).remove();
|
||||
delete items[key];
|
||||
|
|
|
@ -846,15 +846,17 @@ define([
|
|||
handlers['NEW_TICKET'] = function (ctx, box, data, cb) {
|
||||
var msg = data.msg;
|
||||
var content = msg.content;
|
||||
content.time = data.time;
|
||||
var i = 0;
|
||||
var handle = function () {
|
||||
var support = Util.find(ctx, ['store', 'modules', 'support']);
|
||||
if (!support && i++ < 100) { setTimeout(handle, 600); }
|
||||
if (!support) { return; }
|
||||
support.addAdminTicket(content, cb);
|
||||
};
|
||||
handle();
|
||||
if (!content.time) { content.time = data.time; }
|
||||
|
||||
var support = Util.find(ctx, ['store', 'modules', 'support']);
|
||||
|
||||
// Admin to user
|
||||
if (content.isAdmin) {
|
||||
support.addUserTicket(content, cb);
|
||||
}
|
||||
|
||||
// User to admin
|
||||
support.addAdminTicket(content, cb);
|
||||
};
|
||||
var supportNotif, adminSupportNotif;
|
||||
handlers['NOTIF_TICKET'] = function (ctx, box, data, cb) {
|
||||
|
|
|
@ -39,6 +39,7 @@ define([
|
|||
if (res.error) { console.error(res); }
|
||||
});
|
||||
}
|
||||
// XXX XXX XXX no need for the "support" mailbox anymore
|
||||
if (!mailboxes['support'] && ctx.loggedIn) {
|
||||
mailboxes.support = {
|
||||
channel: Hash.createChannelId(),
|
||||
|
|
|
@ -41,6 +41,7 @@ define([
|
|||
if (isAdmin) {
|
||||
return ctx.adminRdyEvt.reg(() => {
|
||||
cb(null, {
|
||||
supportKey: supportKey,
|
||||
myCurve: data.adminCurvePrivate || Util.find(ctx.store.proxy, [
|
||||
'mailboxes', 'supportteam', 'keys', 'curvePrivate']),
|
||||
theirPublic: data.curvePublic,
|
||||
|
@ -50,8 +51,9 @@ define([
|
|||
}
|
||||
|
||||
cb(null, {
|
||||
theirPublic: data.curvePublic || supportKey, // old tickets may use deprecated key
|
||||
supportKey: supportKey,
|
||||
myCurve: ctx.store.proxy.curvePrivate,
|
||||
theirPublic: data.curvePublic || supportKey, // old tickets may use deprecated key
|
||||
notifKey: supportKey
|
||||
});
|
||||
});
|
||||
|
@ -108,7 +110,7 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var makeTicket = function (ctx, data, cId, cb) {
|
||||
var makeTicket = 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({error: 'E_NOT_READY'}); }
|
||||
|
@ -117,34 +119,57 @@ define([
|
|||
var channel = data.channel;
|
||||
var title = data.title;
|
||||
var ticket = data.ticket;
|
||||
var supportKey, myCurve;
|
||||
var supportKey, theirPublic, myCurve, notifKey;
|
||||
var time = +new Date();
|
||||
nThen((waitFor) => {
|
||||
// Send ticket to the admins and call back
|
||||
getKeys(ctx, false, data, waitFor((err, obj) => {
|
||||
getKeys(ctx, isAdmin, data, waitFor((err, obj) => {
|
||||
if (err) {
|
||||
waitFor.abort();
|
||||
return void cb({error: err});
|
||||
}
|
||||
supportKey = obj.theirPublic;
|
||||
supportKey = obj.supportKey;
|
||||
theirPublic = obj.theirPublic;
|
||||
myCurve = obj.myCurve;
|
||||
notifKey = obj.notifKey;
|
||||
}));
|
||||
}).nThen((waitFor) => {
|
||||
// Create ticket mailbox
|
||||
var keys = Crypto.Curve.deriveKeys(supportKey, myCurve);
|
||||
var keys = Crypto.Curve.deriveKeys(theirPublic, myCurve);
|
||||
var crypto = Crypto.Curve.createEncryptor(keys);
|
||||
var text = JSON.stringify(ticket);
|
||||
var ciphertext = crypto.encrypt(text);
|
||||
anonRpc.send("WRITE_PRIVATE_MESSAGE", [
|
||||
channel,
|
||||
ciphertext
|
||||
], waitFor((err) => {
|
||||
], waitFor((err, res) => {
|
||||
if (err) {
|
||||
waitFor.abort();
|
||||
return void cb({error: err});
|
||||
}
|
||||
time = res && res[0];
|
||||
}));
|
||||
}).nThen((waitFor) => {
|
||||
// Store in our worker
|
||||
if (!isAdmin) { return; }
|
||||
// ADMIN: Store in chainpad
|
||||
var doc = ctx.adminDoc.proxy;
|
||||
var active = doc.tickets.active;
|
||||
if (active[channel]) {
|
||||
waitFor.abort();
|
||||
return void cb({error: 'EEXISTS'});
|
||||
}
|
||||
active[channel] = {
|
||||
title: ticket.title,
|
||||
premium: false,
|
||||
time: time,
|
||||
author: data.name,
|
||||
supportKey: supportKey, // Store current support key
|
||||
lastAdmin: true,
|
||||
authorKey: data.curvePublic
|
||||
};
|
||||
}).nThen((waitFor) => {
|
||||
if (isAdmin) { return; }
|
||||
// USER: Store in my worker
|
||||
ctx.supportData[channel] = {
|
||||
time: +new Date(),
|
||||
title: title,
|
||||
|
@ -152,31 +177,41 @@ define([
|
|||
};
|
||||
ctx.Store.onSync(null, waitFor());
|
||||
}).nThen(() => {
|
||||
var supportChannel = Hash.getChannelIdFromKey(supportKey);
|
||||
// XXX create tickt as an admin
|
||||
// XXX isAdmin
|
||||
var notifChannel = isAdmin ? data.notifications
|
||||
: Hash.getChannelIdFromKey(supportKey);
|
||||
// First message to deal with the new ticket (store it in the list)
|
||||
mailbox.sendTo('NEW_TICKET', {
|
||||
title: title,
|
||||
channel: channel,
|
||||
premium: Util.find(ctx, ['store', 'account', 'plan'])
|
||||
time,
|
||||
isAdmin,
|
||||
supportKey: supportKey, // Store current support key
|
||||
premium: isAdmin ? '' : Util.find(ctx, ['store', 'account', 'plan']),
|
||||
user: Util.find(data.ticket, ['sender', 'curvePublic']) ? undefined : {
|
||||
supportTeam: true
|
||||
}
|
||||
}, {
|
||||
channel: supportChannel,
|
||||
curvePublic: supportKey
|
||||
channel: notifChannel,
|
||||
curvePublic: theirPublic
|
||||
}, (obj) => {
|
||||
console.error(obj);
|
||||
// Don't store the ticket in case of error
|
||||
if (obj && obj.error) { delete ctx.supportData[channel]; }
|
||||
cb(obj);
|
||||
});
|
||||
// XXX create tickt as an admin
|
||||
// XXX isAdmin
|
||||
// Second message is only a notification to warn the user/admins
|
||||
mailbox.sendTo('NOTIF_TICKET', {
|
||||
title: title,
|
||||
channel: channel,
|
||||
time,
|
||||
isAdmin,
|
||||
isNewTicket: true,
|
||||
user: Util.find(data.ticket, ['sender', 'curvePublic']) ? undefined : {
|
||||
supportTeam: true
|
||||
}
|
||||
}, {
|
||||
channel: supportChannel,
|
||||
curvePublic: supportKey
|
||||
channel: notifChannel,
|
||||
curvePublic: theirPublic
|
||||
}, () => {});
|
||||
});
|
||||
};
|
||||
|
@ -403,6 +438,9 @@ define([
|
|||
cb({tickets: all});
|
||||
});
|
||||
};
|
||||
var makeMyTicket = function (ctx, data, cId, cb) {
|
||||
makeTicket(ctx, data, false, cb);
|
||||
};
|
||||
var replyMyTicket = function (ctx, data, cId, cb) {
|
||||
replyTicket(ctx, data, false, (err) => {
|
||||
if (err) { return void cb({error: err}); }
|
||||
|
@ -479,6 +517,13 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var makeTicketAdmin = function (ctx, data, cId, cb) {
|
||||
if (!ctx.adminRdyEvt) { return void cb({ error: 'EFORBIDDEN' }); }
|
||||
ctx.adminRdyEvt.reg(() => {
|
||||
makeTicket(ctx, data, true, cb);
|
||||
});
|
||||
};
|
||||
|
||||
var replyTicketAdmin = function (ctx, data, cId, cb) {
|
||||
if (!ctx.adminRdyEvt) { return void cb({ error: 'EFORBIDDEN' }); }
|
||||
let supportKey = data.supportKey;
|
||||
|
@ -561,12 +606,12 @@ define([
|
|||
let supportKey;
|
||||
nThen((waitFor) => {
|
||||
// Send ticket to the admins and call back
|
||||
getKeys(ctx, false, data, waitFor((err, obj) => {
|
||||
getKeys(ctx, true, data, waitFor((err, obj) => {
|
||||
if (err) {
|
||||
waitFor.abort();
|
||||
return void cb(true);
|
||||
}
|
||||
supportKey = obj.theirPublic;
|
||||
supportKey = obj.supportKey;
|
||||
}));
|
||||
}).nThen(() => {
|
||||
// random timeout to avoid duplication wiht multiple admins
|
||||
|
@ -581,7 +626,7 @@ define([
|
|||
premium: data.premium,
|
||||
time: data.time,
|
||||
author: data.user && data.user.displayName,
|
||||
supportKey: supportKey, // Store current support key
|
||||
supportKey: data.supportKey || supportKey, // Store current support key
|
||||
authorKey: data.user && data.user.curvePublic
|
||||
};
|
||||
Realtime.whenRealtimeSyncs(ctx.adminDoc.realtime, function () {
|
||||
|
@ -629,6 +674,19 @@ define([
|
|||
cb(exists);
|
||||
});
|
||||
};
|
||||
|
||||
var addUserTicket = function (ctx, data, cb) {
|
||||
if (!ctx.supportData) { return void cb(true); }
|
||||
let channel = data.channel;
|
||||
ctx.supportData[channel] = {
|
||||
time: data.time,
|
||||
title: data.title,
|
||||
curvePublic: data.supportKey // Old tickets still use previous keys
|
||||
};
|
||||
ctx.Store.onSync(null, function () {
|
||||
cb(true);
|
||||
});
|
||||
};
|
||||
var updateUserTicket = function (ctx, data) {
|
||||
notifyClient(ctx, false, 'UPDATE_TICKET', data.channel);
|
||||
if (data.isClose) {
|
||||
|
@ -1013,6 +1071,9 @@ define([
|
|||
support.checkAdminTicket = function (content, cb) {
|
||||
checkAdminTicket(ctx, content, cb);
|
||||
};
|
||||
support.addUserTicket = function (content, cb) {
|
||||
addUserTicket(ctx, content, cb);
|
||||
};
|
||||
support.updateUserTicket = function (content) {
|
||||
updateUserTicket(ctx, content);
|
||||
};
|
||||
|
@ -1021,7 +1082,7 @@ define([
|
|||
var data = obj.data;
|
||||
// User commands
|
||||
if (cmd === 'MAKE_TICKET') {
|
||||
return void makeTicket(ctx, data, clientId, cb);
|
||||
return void makeMyTicket(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'GET_MY_TICKETS') {
|
||||
return void getMyTickets(ctx, data, clientId, cb);
|
||||
|
@ -1036,6 +1097,9 @@ define([
|
|||
return void deleteMyTicket(ctx, data, clientId, cb);
|
||||
}
|
||||
// Moderator commands
|
||||
if (cmd === 'MAKE_TICKET_ADMIN') {
|
||||
return void makeTicketAdmin(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'LIST_TICKETS_ADMIN') {
|
||||
return void listTicketsAdmin(ctx, data, clientId, cb);
|
||||
}
|
||||
|
|
|
@ -49,7 +49,28 @@
|
|||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.cp-moderation-userdata {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
input, textarea {
|
||||
width: 25rem !important;
|
||||
max-width: 25rem;
|
||||
}
|
||||
textarea {
|
||||
margin: 0;
|
||||
height: 5rem;
|
||||
}
|
||||
.cp-moderation-userdata-inputs {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
margin-right: 1rem;
|
||||
:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,8 +59,17 @@ define([
|
|||
Messages.support_notificationsTitle = "Disable notifications";
|
||||
Messages.support_notificationsHint = "Check this option to disable notifications on new or updated ticket";
|
||||
|
||||
Messages.support_openTicketTitle = "Open a ticket for a user";
|
||||
Messages.support_openTicketHint = "Create a ticket for a user. They will receive a CryptPad notification to warn them. You can copy their user data from an existing support ticket, using the Copy button in the user data.";
|
||||
Messages.support_userChannel = "User's notifications channel ID";
|
||||
Messages.support_userKey = "User's curvePublic key";
|
||||
Messages.support_invalChan = "Invalid notifications channel";
|
||||
|
||||
Messages.support_pasteUserData = "Paste user data here";
|
||||
|
||||
var andThen = function (common, $container, linkedTicket) {
|
||||
const sidebar = Sidebar.create(common, 'support', $container);
|
||||
const blocks = sidebar.blocks;
|
||||
|
||||
// Support panel functions
|
||||
let open = [];
|
||||
|
@ -177,7 +186,7 @@ define([
|
|||
refreshAll();
|
||||
});
|
||||
};
|
||||
const onMove = function (ticket, channel, data) {
|
||||
const onMove = function (ticket, channel) {
|
||||
APP.module.execCommand('MOVE_TICKET_ADMIN', {
|
||||
channel: channel,
|
||||
from: type,
|
||||
|
@ -253,6 +262,12 @@ define([
|
|||
'notifications'
|
||||
]
|
||||
},
|
||||
'ticket': {
|
||||
icon: undefined,
|
||||
content: [
|
||||
'open-ticket'
|
||||
]
|
||||
},
|
||||
'refresh': {
|
||||
icon: undefined,
|
||||
onClick: refreshAll
|
||||
|
@ -294,6 +309,74 @@ define([
|
|||
}
|
||||
});
|
||||
|
||||
sidebar.addItem('open-ticket', cb => {
|
||||
let form = APP.support.makeForm();
|
||||
let inputName = blocks.input({type: 'text', readonly: true});
|
||||
let inputChan = blocks.input({type: 'text', readonly: true});
|
||||
let inputKey = blocks.input({type: 'text', readonly: true});
|
||||
let labelName = blocks.labelledInput(Messages.login_username, inputName);
|
||||
let labelChan = blocks.labelledInput(Messages.support_userChannel, inputChan);
|
||||
let labelKey = blocks.labelledInput(Messages.support_userKey, inputKey);
|
||||
|
||||
let send = blocks.button('primary', 'fa-paper-plane', Messages.support_formButton);
|
||||
let nav = blocks.nav([send]);
|
||||
|
||||
let paste = blocks.textArea({
|
||||
placeholder: Messages.support_pasteUserData
|
||||
});
|
||||
let inputs = h('div.cp-moderation-userdata-inputs', [ labelName, labelChan, labelKey ]);
|
||||
let userData = h('div.cp-moderation-userdata', [inputs , paste]);
|
||||
|
||||
|
||||
let $paste = $(paste).on('input', () => {
|
||||
let text = $paste.val().trim();
|
||||
let parsed = Util.tryParse(text);
|
||||
$paste.val('');
|
||||
if (!parsed || !parsed.name || !parsed.notifications || !parsed.curvePublic) {
|
||||
return void UI.warn(Messages.error);
|
||||
}
|
||||
$(inputName).val(parsed.name);
|
||||
$(inputChan).val(parsed.notifications);
|
||||
$(inputKey).val(parsed.curvePublic);
|
||||
$paste.hide();
|
||||
});
|
||||
[inputName, inputChan, inputKey].forEach(input => {
|
||||
$(input).on('input', () => { $paste.show(); });
|
||||
});
|
||||
|
||||
let $send = $(send);
|
||||
Util.onClickEnter($send, function () {
|
||||
let name = $(inputName).val().trim();
|
||||
let chan = $(inputChan).val().trim();
|
||||
let key = $(inputKey).val().trim();
|
||||
let data = APP.support.getFormData(form);
|
||||
|
||||
if (!name) { return void UI.warn(Messages.login_invalUser); }
|
||||
if (!Hash.isValidChannel(chan)) { return void UI.warn(Messages.support_invalChan); }
|
||||
if (key.length !== 44) { return void UI.warn(Messages.admin_invalKey); }
|
||||
|
||||
$send.attr('disabled', 'disabled');
|
||||
APP.module.execCommand('MAKE_TICKET_ADMIN', {
|
||||
name: name,
|
||||
notifications: chan,
|
||||
curvePublic: key,
|
||||
channel: Hash.createChannelId(),
|
||||
title: data.title,
|
||||
ticket: data
|
||||
}, function (obj) {
|
||||
if (obj && obj.error) {
|
||||
console.error(obj.error);
|
||||
return void UI.warn(Messages.error);
|
||||
}
|
||||
refreshAll();
|
||||
sidebar.openCategory('open');
|
||||
});
|
||||
});
|
||||
|
||||
let div = blocks.form([userData, form], nav);
|
||||
cb(div);
|
||||
});
|
||||
|
||||
sidebar.makeLeftside(categories);
|
||||
};
|
||||
|
||||
|
|
|
@ -295,8 +295,6 @@ define([
|
|||
|
||||
var form = APP.support.makeForm();
|
||||
|
||||
var id = Util.uid();
|
||||
|
||||
$div.find('button').click(function () {
|
||||
var data = APP.support.getFormData(form);
|
||||
APP.supportModule.execCommand('MAKE_TICKET', {
|
||||
|
@ -308,7 +306,6 @@ define([
|
|||
console.error(obj.error);
|
||||
return void UI.warn(Messages.error);
|
||||
}
|
||||
id = Util.uid();
|
||||
events.UPDATE_TICKET.fire();
|
||||
$('.cp-sidebarlayout-category[data-category="tickets"]').click();
|
||||
});
|
||||
|
|
|
@ -19,6 +19,7 @@ define([
|
|||
Messages.support_answerAs = "Answer as <b>{0}</b>"; // XXX
|
||||
Messages.support_movePending = "Move to pending";
|
||||
Messages.support_moveActive = "Move to active";
|
||||
Messages.support_copyUserData = "Copy user data";
|
||||
|
||||
var getDebuggingData = function (ctx, data) {
|
||||
var common = ctx.common;
|
||||
|
@ -31,12 +32,9 @@ define([
|
|||
|
||||
data.sender = {
|
||||
name: user.name,
|
||||
accountName: privateData.accountName,
|
||||
drive: privateData.driveChannel,
|
||||
channel: privateData.support,
|
||||
curvePublic: user.curvePublic,
|
||||
edPublic: privateData.edPublic,
|
||||
notifications: user.notifications,
|
||||
notifications: user.notifications
|
||||
};
|
||||
|
||||
if (ctx.isAdmin && ctx.anonymous) {
|
||||
|
@ -53,6 +51,8 @@ define([
|
|||
}
|
||||
|
||||
if (!ctx.isAdmin) {
|
||||
data.sender.drive = privateData.driveChannel;
|
||||
data.sender.accountName = privateData.accountName;
|
||||
data.sender.userAgent = Util.find(window, ['navigator', 'userAgent']);
|
||||
data.sender.vendor = Util.find(window, ['navigator', 'vendor']);
|
||||
data.sender.appVersion = Util.find(window, ['navigator', 'appVersion']);
|
||||
|
@ -223,7 +223,7 @@ define([
|
|||
var content = [
|
||||
h('hr'),
|
||||
category,
|
||||
catContainer,
|
||||
!ctx.isAdmin ? catContainer : undefined,
|
||||
notice,
|
||||
//h('br'),
|
||||
h('input.cp-support-form-title' + (title ? '.cp-hidden' : ''), {
|
||||
|
@ -328,11 +328,7 @@ define([
|
|||
// Admin actions
|
||||
let show = h('button.btn.btn-primary.cp-support-expand', Messages.admin_support_open);
|
||||
let $show = $(show);
|
||||
let url = h('button.btn', { title: Messages.share_linkCopy, }, [
|
||||
h('i.fa.fa-link', {
|
||||
'aria-hidden': true,
|
||||
}),
|
||||
]);
|
||||
let url = h('button.btn.fa.fa-link', { title: Messages.share_linkCopy, });
|
||||
$(url).click(function (e) {
|
||||
e.stopPropagation();
|
||||
var link = privateData.origin + privateData.pathname + '#' + 'active-' + id;
|
||||
|
@ -368,7 +364,7 @@ define([
|
|||
if (onMove) {
|
||||
let text = onMove.isTicketActive ? Messages.support_movePending
|
||||
: Messages.support_moveActive;
|
||||
settings = h('button.btn.btn-secondary.fa.fa-hdd-o', { title: text });
|
||||
settings = h('button.btn.btn-secondary.fa.fa-archive', { title: text });
|
||||
Util.onClickEnter($(settings), function () {
|
||||
onMove(ticket, id, content);
|
||||
});
|
||||
|
@ -439,13 +435,26 @@ define([
|
|||
|| (!senderKey && content.sender.accountName === 'support'); // XXX anon key?
|
||||
var fromPremium = Boolean(content.sender.plan || Util.find(content, ['sender', 'quota', 'plan']));
|
||||
|
||||
var copyUser = h('button.btn.btn-secondary.fa.fa-clipboard.cp-support-copydata', {
|
||||
title: Messages.support_copyUserData
|
||||
});
|
||||
Util.onClickEnter($(copyUser), () => {
|
||||
let data = JSON.stringify({
|
||||
name: content.sender.name,
|
||||
curvePublic: content.sender.curvePublic,
|
||||
notifications: content.sender.notifications
|
||||
});
|
||||
Clipboard.copy(data, (err) => {
|
||||
if (!err) { UI.log(Messages.genericCopySuccess); }
|
||||
});
|
||||
});
|
||||
var userData = h('div.cp-support-showdata', [
|
||||
Messages.support_showData,
|
||||
h('pre.cp-support-message-data', JSON.stringify(content.sender, 0, 2))
|
||||
h('pre.cp-support-message-data', [copyUser, JSON.stringify(content.sender, 0, 2)])
|
||||
]);
|
||||
$(userData).click(function () {
|
||||
$(userData).find('pre').toggle();
|
||||
}).find('pre').click(function (ev) {
|
||||
$(userData).find('.cp-support-message-data').toggle();
|
||||
}).find('*').click(function (ev) {
|
||||
ev.stopPropagation();
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue