|
|
define([
|
|
|
'jquery',
|
|
|
'/api/config',
|
|
|
'/common/hyperscript.js',
|
|
|
'/common/common-interface.js',
|
|
|
'/common/common-hash.js',
|
|
|
'/common/common-util.js',
|
|
|
'/common/clipboard.js',
|
|
|
'/common/common-ui-elements.js',
|
|
|
'/customize/messages.js',
|
|
|
'/customize/pages.js',
|
|
|
], function ($, ApiConfig, h, UI, Hash, Util, Clipboard, UIElements, Messages, Pages) {
|
|
|
|
|
|
var getDebuggingData = function (ctx, data) {
|
|
|
var common = ctx.common;
|
|
|
var metadataMgr = common.getMetadataMgr();
|
|
|
var privateData = metadataMgr.getPrivateData();
|
|
|
var user = metadataMgr.getUserData();
|
|
|
var teams = privateData.teams || {};
|
|
|
data = data || {};
|
|
|
|
|
|
data.sender = {
|
|
|
name: user.name,
|
|
|
drive: privateData.driveChannel,
|
|
|
channel: privateData.support,
|
|
|
curvePublic: user.curvePublic,
|
|
|
edPublic: privateData.edPublic,
|
|
|
notifications: user.notifications,
|
|
|
};
|
|
|
|
|
|
if (typeof(ctx.pinUsage) === 'object') {
|
|
|
// pass pin.usage, pin.limit, and pin.plan if supplied
|
|
|
data.sender.quota = ctx.pinUsage;
|
|
|
}
|
|
|
|
|
|
if (!ctx.isAdmin) {
|
|
|
data.sender.userAgent = Util.find(window, ['navigator', 'userAgent']);
|
|
|
data.sender.vendor = Util.find(window, ['navigator', 'vendor']);
|
|
|
data.sender.appVersion = Util.find(window, ['navigator', 'appVersion']);
|
|
|
data.sender.screenWidth = Util.find(window, ['screen', 'width']);
|
|
|
data.sender.screenHeight = Util.find(window, ['screen', 'height']);
|
|
|
data.sender.blockLocation = privateData.blockLocation || '';
|
|
|
data.sender.teams = Object.keys(teams).map(function (key) {
|
|
|
var team = teams[key];
|
|
|
if (!team) { return; }
|
|
|
var ret = {};
|
|
|
['channel', 'roster', 'numberPads', 'numberSf', 'edPublic', 'curvePublic', 'owner', 'viewer', 'hasSecondaryKey', 'validKeys'].forEach(function (k) {
|
|
|
ret[k] = team[k];
|
|
|
});
|
|
|
if (ctx.teamsUsage && ctx.teamsUsage[key]) {
|
|
|
ret.quota = ctx.teamsUsage[key];
|
|
|
}
|
|
|
return ret;
|
|
|
}).filter(Boolean);
|
|
|
}
|
|
|
|
|
|
return data;
|
|
|
};
|
|
|
|
|
|
var send = function (ctx, id, type, data, dest) {
|
|
|
var common = ctx.common;
|
|
|
var supportKey = ApiConfig.supportMailbox;
|
|
|
var supportChannel = Hash.getChannelIdFromKey(supportKey);
|
|
|
var metadataMgr = common.getMetadataMgr();
|
|
|
var user = metadataMgr.getUserData();
|
|
|
var privateData = metadataMgr.getPrivateData();
|
|
|
|
|
|
data = getDebuggingData(ctx, data);
|
|
|
|
|
|
data.id = id;
|
|
|
data.time = +new Date();
|
|
|
|
|
|
if (!ctx.isAdmin) {
|
|
|
// "dest" is the recipient that is not the admin support mailbox.
|
|
|
// In the support page, make sure dest is always ourselves.
|
|
|
dest.channel = privateData.support;
|
|
|
dest.curvePublic = user.curvePublic;
|
|
|
}
|
|
|
|
|
|
// Send the message to the admin mailbox and to the user mailbox
|
|
|
common.mailbox.sendTo(type, data, {
|
|
|
channel: supportChannel,
|
|
|
curvePublic: supportKey
|
|
|
});
|
|
|
common.mailbox.sendTo(type, data, {
|
|
|
channel: dest.channel,
|
|
|
curvePublic: dest.curvePublic
|
|
|
});
|
|
|
|
|
|
if (ctx.isAdmin) {
|
|
|
common.mailbox.sendTo('SUPPORT_MESSAGE', {}, {
|
|
|
channel: dest.notifications,
|
|
|
curvePublic: dest.curvePublic
|
|
|
});
|
|
|
}
|
|
|
};
|
|
|
|
|
|
var sendForm = function (ctx, id, form, dest) {
|
|
|
var $form = $(form);
|
|
|
var $cat = $form.find('.cp-support-form-category');
|
|
|
var $title = $form.find('.cp-support-form-title');
|
|
|
var $content = $form.find('.cp-support-form-msg');
|
|
|
// TODO block submission until pending uploads are complete?
|
|
|
var $attachments = $form.find('.cp-support-attachments');
|
|
|
|
|
|
var category = $cat.val().trim();
|
|
|
/*
|
|
|
// || ($form.closest('.cp-support-list-ticket').data('cat') || "").trim();
|
|
|
// Messages.support_formCategoryError = "Error: category is empty"; // TODO ensure this is translated before use
|
|
|
|
|
|
if (!category) {
|
|
|
console.log($cat);
|
|
|
return void UI.alert(Messages.support_formCategoryError);
|
|
|
}
|
|
|
*/
|
|
|
|
|
|
var title = $title.val().trim();
|
|
|
if (!title) {
|
|
|
return void UI.alert(Messages.support_formTitleError);
|
|
|
}
|
|
|
var content = $content.val().trim();
|
|
|
if (!content) {
|
|
|
return void UI.alert(Messages.support_formContentError);
|
|
|
}
|
|
|
$cat.val('');
|
|
|
$content.val('');
|
|
|
$title.val('');
|
|
|
|
|
|
var attachments = [];
|
|
|
$attachments.find('> span').each(function (i, el) {
|
|
|
var $el = $(el);
|
|
|
attachments.push({
|
|
|
href: $el.attr('data-href'),
|
|
|
name: $el.attr('data-name')
|
|
|
});
|
|
|
});
|
|
|
$attachments.html('');
|
|
|
|
|
|
send(ctx, id, 'TICKET', {
|
|
|
category: category,
|
|
|
title: title,
|
|
|
attachments: attachments,
|
|
|
message: content,
|
|
|
}, dest);
|
|
|
|
|
|
return true;
|
|
|
};
|
|
|
|
|
|
Messages.support_cat_abuse = "Abuse"; // XXX
|
|
|
Messages.support_cat_terms = "Terms violation"; // XXX
|
|
|
|
|
|
var makeCategoryDropdown = function (ctx, container, onChange, all) {
|
|
|
var categories = [
|
|
|
'account', // Msg.support_cat_account
|
|
|
'data', // Msg.support_cat_data
|
|
|
'bug', // Msg.support_cat_bug
|
|
|
'abuse', // Msg.support_cat_abuse
|
|
|
Pages.customURLs.terms? 'terms': undefined, // Msg.support_cat_terms
|
|
|
'other' // Msg.support_cat_other
|
|
|
];
|
|
|
if (all) { categories.push('all'); } // Msg.support_cat_all
|
|
|
|
|
|
categories = categories.map(function (key) {
|
|
|
if (!key) { return; }
|
|
|
return {
|
|
|
tag: 'a',
|
|
|
content: h('span', Messages['support_cat_'+key]),
|
|
|
action: function () {
|
|
|
onChange(key);
|
|
|
}
|
|
|
};
|
|
|
});
|
|
|
var dropdownCfg = {
|
|
|
text: Messages.support_category,
|
|
|
angleDown: 1,
|
|
|
options: categories,
|
|
|
container: $(container),
|
|
|
isSelect: true
|
|
|
};
|
|
|
var $select = UIElements.createDropdown(dropdownCfg);
|
|
|
$select.find('button').addClass('btn');
|
|
|
return $select;
|
|
|
};
|
|
|
|
|
|
Messages.support_warning_prompt = "We may require additional information depending on the nature of your issue. Choose the most relevant category for suggestions.";
|
|
|
|
|
|
Messages.support_warning_account = "CryptPad administrators are unable to identify accounts, teams, folders, and files by their names. Please provide their identifiers if your issue relates to one of these features."; // XXX
|
|
|
Messages.support_warning_data = 'What data was lost? Is it entirely gone or only corrupted? Is it backed up? Can you provide a link to the content or at least its document id?'; // XXX
|
|
|
Messages.support_warning_bug = "Describe the bug in as much detail as possible. In which browser did you first notice the problem? In which other browser does it first occur, if any? Can you provide a list of all the extensions you've installed?"; // XXX
|
|
|
Messages.support_warning_report = 'Give us a link to the reported content, some context about its contents, and where it is being distributed.'; // XXX
|
|
|
Messages.support_warning_terms = 'Reports of content or behaviour which violate our <a>terms of service</a> should include links to any related content and descriptions of how they violate the terms. If possible, a description of the context in which you discovered the behaviour may help us prevent future violations.'; // XXX
|
|
|
Messages.support_warning_abuse = "If you have experienced targeted abuse on the platform, please provide a description of what occurred and indicate any evidence that might help us prevent this abuse in the future.";
|
|
|
Messages.support_warning_other = "What is the nature of your query? Providing as much relevant information as possible in your first message may make it easier for us to address your issue quickly."; // XXX
|
|
|
|
|
|
var makeForm = function (ctx, cb, title) {
|
|
|
var button;
|
|
|
|
|
|
if (typeof(cb) === "function") {
|
|
|
button = h('button.btn.btn-primary.cp-support-list-send', Messages.contacts_send);
|
|
|
$(button).click(cb);
|
|
|
}
|
|
|
|
|
|
var cancel = title ? h('button.btn.btn-secondary', Messages.cancel) : undefined;
|
|
|
|
|
|
var category = h('input.cp-support-form-category', {
|
|
|
type: 'hidden',
|
|
|
value: ''
|
|
|
});
|
|
|
var catContainer = h('div.cp-dropdown-container' + (title ? '.cp-hidden': ''));
|
|
|
var notice = h('div.alert.alert-info', Messages.support_warning_prompt);
|
|
|
makeCategoryDropdown(ctx, catContainer, function (key) {
|
|
|
$(category).val(key);
|
|
|
console.log(key);
|
|
|
var warning = Messages['support_warning_' + key] || '';
|
|
|
if (key === 'terms') {
|
|
|
// XXX AppConfig.terms or Pages.termsLink
|
|
|
notice.innerHTML = '';
|
|
|
//notice.appendChild(UI.setHTML(h('span'), Messages.support_
|
|
|
var content = UI.setHTML(h('span'), Messages.support_warning_terms);
|
|
|
var link = content.querySelector('a');
|
|
|
link.href = Pages.customURLs.terms;
|
|
|
notice.appendChild(content);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
notice.innerText = warning;
|
|
|
|
|
|
// TODO add a hint suggesting relevant information to include for the chosen category
|
|
|
});
|
|
|
|
|
|
var attachments, addAttachment;
|
|
|
|
|
|
|
|
|
var content = [
|
|
|
h('hr'),
|
|
|
category,
|
|
|
catContainer,
|
|
|
notice,
|
|
|
//h('br'),
|
|
|
h('input.cp-support-form-title' + (title ? '.cp-hidden' : ''), {
|
|
|
placeholder: Messages.support_formTitle,
|
|
|
type: 'text',
|
|
|
value: title || ''
|
|
|
}),
|
|
|
cb ? undefined : h('br'),
|
|
|
h('textarea.cp-support-form-msg', {
|
|
|
placeholder: Messages.support_formMessage
|
|
|
}),
|
|
|
h('label', Messages.support_attachments),
|
|
|
attachments = h('div.cp-support-attachments'),
|
|
|
addAttachment = h('button.btn', Messages.support_addAttachment),
|
|
|
h('hr'),
|
|
|
button,
|
|
|
cancel
|
|
|
];
|
|
|
|
|
|
$(addAttachment).click(function () {
|
|
|
var $input = $('<input>', {
|
|
|
'type': 'file',
|
|
|
'style': 'display: none;',
|
|
|
'multiple': 'multiple',
|
|
|
'accept': 'image/*'
|
|
|
}).on('change', function (e) {
|
|
|
var files = Util.slice(e.target.files);
|
|
|
files.forEach(function (file) {
|
|
|
var ev = {};
|
|
|
ev.callback = function (data) {
|
|
|
var x, a;
|
|
|
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
|
|
|
ctx.FM.handleFile(file, ev, {});
|
|
|
});
|
|
|
});
|
|
|
$input.click();
|
|
|
});
|
|
|
|
|
|
var form = h('div.cp-support-form-container', content);
|
|
|
|
|
|
$(cancel).click(function () {
|
|
|
$(form).closest('.cp-support-list-ticket').find('.cp-support-list-actions').css('display', '');
|
|
|
$(form).remove();
|
|
|
});
|
|
|
|
|
|
return form;
|
|
|
};
|
|
|
|
|
|
var makeTicket = function (ctx, $div, content, onHide) {
|
|
|
var common = ctx.common;
|
|
|
var metadataMgr = common.getMetadataMgr();
|
|
|
var privateData = metadataMgr.getPrivateData();
|
|
|
|
|
|
var ticketTitle = content.title + ' (#' + content.id + ')';
|
|
|
var ticketCategory;
|
|
|
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 hide = h('button.btn.btn-danger.cp-support-hide', Messages.support_remove);
|
|
|
|
|
|
var actions = h('div.cp-support-list-actions', [
|
|
|
answer,
|
|
|
close,
|
|
|
hide
|
|
|
]);
|
|
|
|
|
|
var url;
|
|
|
if (ctx.isAdmin) {
|
|
|
ticketCategory = Messages['support_cat_'+(content.category || 'all')] + ' - ';
|
|
|
url = h('button.btn.fa.fa-clipboard');
|
|
|
$(url).click(function (e) {
|
|
|
e.stopPropagation();
|
|
|
var link = privateData.origin + privateData.pathname + '#' + 'support-' + content.id;
|
|
|
var success = Clipboard.copy(link);
|
|
|
if (success) { UI.log(Messages.shareSuccess); }
|
|
|
});
|
|
|
}
|
|
|
|
|
|
var $ticket = $(h('div.cp-support-list-ticket', {
|
|
|
'data-cat': content.category,
|
|
|
'data-id': content.id
|
|
|
}, [
|
|
|
h('h2', [
|
|
|
h('span', [ticketCategory, ticketTitle]),
|
|
|
h('span.cp-support-title-buttons',url)
|
|
|
]),
|
|
|
actions
|
|
|
]));
|
|
|
|
|
|
/*
|
|
|
$(close).click(function () {
|
|
|
send(ctx, content.id, 'CLOSE', {}, content.sender);
|
|
|
});
|
|
|
|
|
|
$(hide).click(function () {
|
|
|
if (typeof(onHide) !== "function") { return; }
|
|
|
onHide();
|
|
|
});
|
|
|
*/
|
|
|
|
|
|
UI.confirmButton(close, {
|
|
|
classes: 'btn-danger'
|
|
|
}, function() {
|
|
|
send(ctx, content.id, 'CLOSE', {}, content.sender);
|
|
|
$(close).hide();
|
|
|
});
|
|
|
UI.confirmButton(hide, {
|
|
|
classes: 'btn-danger'
|
|
|
}, function() {
|
|
|
if (typeof(onHide) !== "function") { return; }
|
|
|
onHide(hide);
|
|
|
});
|
|
|
|
|
|
$(answer).click(function () {
|
|
|
$ticket.find('.cp-support-form-container').remove();
|
|
|
$(actions).hide();
|
|
|
var form = makeForm(ctx, function () {
|
|
|
var sent = sendForm(ctx, content.id, form, content.sender);
|
|
|
if (sent) {
|
|
|
$(actions).css('display', '');
|
|
|
$(form).remove();
|
|
|
}
|
|
|
}, content.title);
|
|
|
$ticket.append(form);
|
|
|
});
|
|
|
|
|
|
$div.append($ticket);
|
|
|
return $ticket;
|
|
|
};
|
|
|
|
|
|
var makeMessage = function (ctx, content, hash) {
|
|
|
var common = ctx.common;
|
|
|
var isAdmin = ctx.isAdmin;
|
|
|
var metadataMgr = common.getMetadataMgr();
|
|
|
var privateData = metadataMgr.getPrivateData();
|
|
|
|
|
|
// Check content.sender to see if it comes from us or from an admin
|
|
|
var senderKey = content.sender && content.sender.edPublic;
|
|
|
var fromMe = senderKey === privateData.edPublic;
|
|
|
var fromAdmin = ctx.adminKeys.indexOf(senderKey) !== -1;
|
|
|
var fromPremium = Boolean(content.sender.plan || Util.find(content, ['sender', 'quota', 'plan']));
|
|
|
|
|
|
var userData = h('div.cp-support-showdata', [
|
|
|
Messages.support_showData,
|
|
|
h('pre.cp-support-message-data', JSON.stringify(content.sender, 0, 2))
|
|
|
]);
|
|
|
$(userData).click(function () {
|
|
|
$(userData).find('pre').toggle();
|
|
|
}).find('pre').click(function (ev) {
|
|
|
ev.stopPropagation();
|
|
|
});
|
|
|
|
|
|
var attachments = (content.attachments || []).map(function (obj) {
|
|
|
if (!obj || !obj.name || !obj.href) { return; }
|
|
|
// only support files explicitly beginning with /file/ so that users can't link outside of the instance
|
|
|
if (!/^\/file\//.test(obj.href)) { return; }
|
|
|
var a = h('a', {
|
|
|
href: '#'
|
|
|
}, obj.name);
|
|
|
$(a).click(function (e) {
|
|
|
e.preventDefault();
|
|
|
ctx.common.openURL(obj.href);
|
|
|
});
|
|
|
return h('span', [
|
|
|
a
|
|
|
]);
|
|
|
});
|
|
|
|
|
|
var displayed = content.message;
|
|
|
var pre = h('pre.cp-support-message-content');
|
|
|
var $pre = $(pre);
|
|
|
var more;
|
|
|
if (content.message.length >= 2000) {
|
|
|
displayed = content.message.slice(0, 2000) + '...';
|
|
|
var expand = h('button.btn.btn-secondary', Messages.admin_support_open);
|
|
|
var collapse = h('button.btn.btn-secondary', Messages.admin_support_collapse);
|
|
|
var $collapse = $(collapse).hide();
|
|
|
var $expand = $(expand).click(function () {
|
|
|
$pre.text(content.message);
|
|
|
$expand.hide();
|
|
|
$collapse.show();
|
|
|
});
|
|
|
$collapse.click(function () {
|
|
|
$pre.text(displayed);
|
|
|
$collapse.hide();
|
|
|
$expand.show();
|
|
|
});
|
|
|
more = h('div', [expand, collapse]);
|
|
|
}
|
|
|
$pre.text(displayed);
|
|
|
|
|
|
var adminClass = (fromAdmin? '.cp-support-fromadmin': '');
|
|
|
var premiumClass = (fromPremium && !fromAdmin? '.cp-support-frompremium': '');
|
|
|
var name = Util.fixHTML(content.sender.name) || Messages.anonymous;
|
|
|
return h('div.cp-support-list-message' + adminClass + premiumClass, {
|
|
|
'data-hash': hash
|
|
|
}, [
|
|
|
h('div.cp-support-message-from' + (fromMe ? '.cp-support-fromme' : ''), [
|
|
|
UI.setHTML(h('span'), Messages._getKey('support_from', [name])),
|
|
|
h('span.cp-support-message-time', content.time ? new Date(content.time).toLocaleString() : '')
|
|
|
]),
|
|
|
pre,
|
|
|
more,
|
|
|
h('div.cp-support-attachments', attachments),
|
|
|
isAdmin ? userData : undefined,
|
|
|
]);
|
|
|
};
|
|
|
|
|
|
var makeCloseMessage = function (ctx, content, hash) {
|
|
|
var common = ctx.common;
|
|
|
var metadataMgr = common.getMetadataMgr();
|
|
|
var privateData = metadataMgr.getPrivateData();
|
|
|
var fromMe = content.sender && content.sender.edPublic === privateData.edPublic;
|
|
|
|
|
|
var name = Util.fixHTML(content.sender.name) || Messages.anonymous;
|
|
|
return h('div.cp-support-list-message', {
|
|
|
'data-hash': hash
|
|
|
}, [
|
|
|
h('div.cp-support-message-from' + (fromMe ? '.cp-support-fromme' : ''), [
|
|
|
UI.setHTML(h('span'), Messages._getKey('support_from', [name])),
|
|
|
h('span.cp-support-message-time', content.time ? new Date(content.time).toLocaleString() : '')
|
|
|
]),
|
|
|
h('pre.cp-support-message-content', Messages.support_closed)
|
|
|
]);
|
|
|
};
|
|
|
|
|
|
var create = function (common, isAdmin, pinUsage, teamsUsage) {
|
|
|
var ui = {};
|
|
|
var ctx = {
|
|
|
common: common,
|
|
|
isAdmin: isAdmin,
|
|
|
pinUsage: pinUsage || false,
|
|
|
teamsUsage: teamsUsage || false,
|
|
|
adminKeys: Array.isArray(ApiConfig.adminKeys)? ApiConfig.adminKeys.slice(): [],
|
|
|
};
|
|
|
|
|
|
var fmConfig = {
|
|
|
body: $('body'),
|
|
|
noStore: true, // Don't store attachments into our drive
|
|
|
onUploaded: function (ev, data) {
|
|
|
if (ev.callback) {
|
|
|
ev.callback(data);
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
ctx.FM = common.createFileManager(fmConfig);
|
|
|
|
|
|
ui.sendForm = function (id, form, dest) {
|
|
|
return sendForm(ctx, id, form, dest);
|
|
|
};
|
|
|
ui.makeForm = function (cb, title) {
|
|
|
return makeForm(ctx, cb, title);
|
|
|
};
|
|
|
ui.makeCategoryDropdown = function (container, onChange, all) {
|
|
|
return makeCategoryDropdown(ctx, container, onChange, all);
|
|
|
};
|
|
|
ui.makeTicket = function ($div, content, onHide) {
|
|
|
return makeTicket(ctx, $div, content, onHide);
|
|
|
};
|
|
|
ui.makeMessage = function (content, hash) {
|
|
|
return makeMessage(ctx, content, hash);
|
|
|
};
|
|
|
ui.makeCloseMessage = function (content, hash) {
|
|
|
return makeCloseMessage(ctx, content, hash);
|
|
|
};
|
|
|
ui.getDebuggingData = function (data) {
|
|
|
return getDebuggingData(ctx, data);
|
|
|
};
|
|
|
|
|
|
return ui;
|
|
|
};
|
|
|
|
|
|
return {
|
|
|
create: create
|
|
|
};
|
|
|
});
|