Support V2 prototype

This commit is contained in:
yflory 2024-02-12 14:30:33 +01:00
parent 93a285baa5
commit 070d8dc450
16 changed files with 620 additions and 75 deletions

View file

@ -40,6 +40,7 @@ SET_MAINTENANCE
// EASIER CONFIG
SET_ADMIN_EMAIL
SET_SUPPORT_MAILBOX
SET_SUPPORT_MAILBOX2
// COMMUNITY PARTICIPATION AND GOVERNANCE
CONSENT_TO_CONTACT
@ -209,6 +210,10 @@ commands.SET_ADMIN_EMAIL = makeGenericSetter('adminEmail', args_isString);
commands.SET_SUPPORT_MAILBOX = makeGenericSetter('supportMailbox', function (args) {
return args_isString(args) && Core.isValidPublicKey(args[0]);
});
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_SUPPORT_MAILBOX2', ["Tdz6+fE9N9XXBY93rW5qeNa/k27yd40c0vq7EJyt7jA="]]], console.log)
commands.SET_SUPPORT_MAILBOX2 = makeGenericSetter('newSupportMailbox', function (args) {
return args_isString(args) && (Core.isValidPublicKey(args[0]) || !args[0]);
});
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_INSTANCE_PURPOSE', ["development"]]], console.log)
commands.SET_INSTANCE_PURPOSE = makeGenericSetter('instancePurpose', args_isString);

View file

@ -147,6 +147,7 @@ module.exports.create = function (config) {
adminEmail: config.adminEmail,
supportMailbox: config.supportMailboxPublicKey,
newSupportMailbox: undefined,
metadata_cache: {},
channel_cache: {},

View file

@ -571,6 +571,7 @@ var serveConfig = makeRouteCache(function () {
adminKeys: Env.admins,
inactiveTime: Env.inactiveTime,
supportMailbox: Env.supportMailbox,
newSupportMailbox: Env.newSupportMailbox,
defaultStorageLimit: Env.defaultStorageLimit,
maxUploadSize: Env.maxUploadSize,
premiumUploadSize: Env.premiumUploadSize,

View file

@ -45,6 +45,7 @@ Stats.instanceData = function (Env) {
// we expect that you enable your support mailbox
data.supportMailbox = Boolean(Env.supportMailbox);
data.newSupportMailbox = Boolean(Env.newSupportMailbox);
// do you allow registration?
data.restrictRegistration = Boolean(Env.restrictRegistration);

View file

@ -98,6 +98,7 @@ define([
'cp-admin-disk-usage',
],
'support': [ // Msg.admin_cat_support
'cp-admin-support-new',
'cp-admin-support-list',
'cp-admin-support-init',
'cp-admin-support-priv',
@ -2914,51 +2915,125 @@ Example
});
return $div;
};
// XXX
Messages.admin_supportNewHint = "Create or update the current support keys.";
Messages.admin_supportNewTitle = "Initialize support";
Messages.admin_supportNewEnabled = "Modern support system is enabled.";
Messages.admin_supportNewDisabled = "Modern support system is disabled.";
Messages.admin_supportNewInit = "Initialize support page on this instance";
Messages.admin_supportNewDelete = "Disable support";
Messages.admin_supportNewConfirm = "Are you sure? This will remove access to all current moderators.";
create['support-new'] = function () {
var $div = makeBlock('support-new'); // Msg.admin_supportNewHint, .admin_supportNewTitle
var newSupportKey = ApiConfig.newSupportMailbox;
(function () {
var state = h('div');
var $state = $(state).appendTo($div);
var button = h('button.btn.btn-primary', Messages.admin_supportNewInit);
var $button = $(button).appendTo($div);
var delButton = h('button.btn.btn-danger', Messages.admin_supportNewDelete);
var $delButton = $(delButton).appendTo($div).hide();;
var spinner = UI.makeSpinner($div);
var setState = function () {
$state.html('');
if (newSupportKey) {
$button.hide();
$delButton.show();
return $state.append([
h('i.fa.fa-check'),
h('span', Messages.admin_supportNewEnabled)
]);
}
$button.show();
$state.append([
h('i.fa.fa-times'),
h('span', Messages.admin_supportNewDisabled)
]);
};
setState();
Util.onClickEnter($delButton, function () {
UI.confirm(Messages.admin_supportNewConfirm, function (yes) {
if (!yes) { return; }
// Send the decree, don't delete data locally, we just want to remove
// the support UI for the clients
spinner.spin();
$delButton.attr('disabled', 'disabled');
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_SUPPORT_MAILBOX2', ['']]
}, function (e, response) {
$delButton.removeAttr('disabled');
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
spinner.hide();
return;
}
spinner.done();
UI.log(Messages.saved);
newSupportKey = undefined;
setState();
});
});
});
var next = function () {
spinner.spin();
$button.attr('disabled', 'disabled');
var keyPair = Nacl.box.keyPair();
var pub = Nacl.util.encodeBase64(keyPair.publicKey);
var priv = Nacl.util.encodeBase64(keyPair.secretKey);
// Store the private key first. It won't be used until the decree is accepted.
sFrameChan.query("Q_ADMIN_MAILBOX", {
version: 2,
priv: priv
}, function (err, obj) {
if (err || (obj && obj.error)) {
console.error(err || obj.error);
UI.warn(Messages.error);
spinner.hide();
return;
}
// Then send the decree
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_SUPPORT_MAILBOX2', [pub]]
}, function (e, response) {
$button.removeAttr('disabled');
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
spinner.hide();
return;
}
spinner.done();
UI.log(Messages.saved);
newSupportKey = pub;
setState();
//$('.cp-admin-support-init').hide();
//APP.$rightside.append(create['support-list']());
//APP.$rightside.append(create['support-priv']());
});
});
};
Util.onClickEnter($button, function () {
if (newSupportKey) {
return void UI.confirm(Messages.admin_supportNewConfirm, function (yes) {
if (yes) { next(); }
});
}
next();
});
})();
return $div;
};
create['support-init'] = function () {
var $div = makeBlock('support-init'); // Msg.admin_supportInitHint, .admin_supportInitTitle
if (!supportKey) {
(function () {
$div.append(h('p', Messages.admin_supportInitHelp));
var button = h('button.btn.btn-primary', Messages.admin_supportInitGenerate);
var $button = $(button).appendTo($div);
$div.append($button);
var spinner = UI.makeSpinner($div);
$button.click(function () {
spinner.spin();
$button.attr('disabled', 'disabled');
var keyPair = Nacl.box.keyPair();
var pub = Nacl.util.encodeBase64(keyPair.publicKey);
var priv = Nacl.util.encodeBase64(keyPair.secretKey);
// Store the private key first. It won't be used until the decree is accepted.
sFrameChan.query("Q_ADMIN_MAILBOX", priv, function (err, obj) {
if (err || (obj && obj.error)) {
console.error(err || obj.error);
UI.warn(Messages.error);
spinner.hide();
return;
}
// Then send the decree
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_SUPPORT_MAILBOX', [pub]]
}, function (e, response) {
$button.removeAttr('disabled');
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
spinner.hide();
return;
}
spinner.done();
UI.log(Messages.saved);
supportKey = pub;
APP.privateKey = priv;
$('.cp-admin-support-init').hide();
APP.$rightside.append(create['support-list']());
APP.$rightside.append(create['support-priv']());
});
});
});
return; // XXX old support can't be created anymore
})();
return $div;
}

View file

@ -26,7 +26,7 @@ define(['/customize/application_config.js'], function (AppConfig) {
MAX_PREMIUM_TEAMS_SLOTS: Math.max(AppConfig.maxTeamsSlots || 0, AppConfig.maxPremiumTeamsSlots || 0) || 5,
MAX_PREMIUM_TEAMS_OWNED: Math.max(AppConfig.maxTeamsOwned || 0, AppConfig.maxPremiumTeamsOwned || 0) || 5,
// Apps
criticalApps: ['profile', 'settings', 'debug', 'admin', 'support', 'notifications', 'calendar'],
criticalApps: ['profile', 'settings', 'debug', 'admin', 'support', 'notifications', 'calendar', 'moderation'],
earlyAccessApps: ['doc', 'presentation']
};
});

View file

@ -18,6 +18,7 @@ define([
'/common/outer/cache-store.js',
'/common/outer/sharedfolder.js',
'/common/outer/cursor.js',
'/common/outer/support.js',
'/common/outer/integration.js',
'/common/outer/onlyoffice.js',
'/common/outer/mailbox.js',
@ -39,7 +40,7 @@ define([
'/components/saferphore/index.js',
], function (ApiConfig, Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback,
Realtime, Messaging, Pinpad, Cache,
SF, Cursor, Integration, OnlyOffice, Mailbox, Profile, Team, Messenger, History,
SF, Cursor, Support, Integration, OnlyOffice, Mailbox, Profile, Team, Messenger, History,
Calendar, Block, NetConfig, AppConfig,
Crypto, ChainPad, CpNetflux, Listmap, Netflux, nThen, Saferphore) {
@ -1616,12 +1617,14 @@ define([
});
};
Store.addAdminMailbox = function (clientId, data, cb) {
var priv = data;
var priv = data && data.priv;
var pub = Hash.getBoxPublicFromSecret(priv);
var isNewSupport = data && data.version === 2;
if (!priv || !pub) { return void cb({error: 'EINVAL'}); }
var channel = Hash.getChannelIdFromKey(pub);
var mailboxes = store.proxy.mailboxes = store.proxy.mailboxes || {};
var box = mailboxes.supportadmin = {
var key = isNewSupport ? 'support2' : 'supportadmin';
var box = mailboxes['supportadmin'] = {
channel: channel,
viewed: [],
lastKnownHash: '',
@ -1631,7 +1634,7 @@ define([
}
};
Store.pinPads(null, [channel], function () {});
store.mailbox.open('supportadmin', box, function () {
store.mailbox.open(key, box, function () {
console.log('ready');
});
onSync(null, cb);
@ -2826,6 +2829,7 @@ define([
postMessage(clientId, 'LOADING_DRIVE', data);
});
loadUniversal(Cursor, 'cursor', waitFor);
loadUniversal(Support, 'support', waitFor);
loadUniversal(Integration, 'integration', waitFor);
loadOnlyOffice();
loadUniversal(Messenger, 'messenger', waitFor);

200
www/common/outer/support.js Normal file
View file

@ -0,0 +1,200 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
define([
'/common/common-util.js',
'/common/common-hash.js',
'/components/nthen/index.js',
'/components/chainpad-crypto/crypto.js',
'chainpad-netflux'
], function (Util, Hash, nThen, Crypto, CpNetflux) {
var Support = {};
var getKeys = function (ctx, cb) {
require(['/api/config?' + (+new Date())], function (NewConfig) {
supportKey = NewConfig.newSupportMailbox;
if (!supportKey) { return void cb('E_NOT_INIT'); }
var myCurve = ctx.store.proxy.curvePrivate;
cb(null, {
supportKey,
myCurve
});
});
};
// Get the content of a mailbox and close it
// Used for support tickets
var getContent = function (ctx, data, _cb) {
var supportKey, myCurve;
nThen((waitFor) => {
// Send ticket to the admins and call back
getKeys(ctx, waitFor((err, obj) => {
if (err) {
waitFor.abort();
return void cb({error: err});
}
supportKey = obj.supportKey;
myCurve = obj.myCurve;
}));
}).nThen((waitFor) => {
var keys = Crypto.Curve.deriveKeys(supportKey, myCurve);
var crypto = Crypto.Curve.createEncryptor(keys);
var cfg = {
network: ctx.store.network,
channel: data.channel,
noChainPad: true,
crypto: crypto,
owners: []
};
var all = [];
var cpNf;
var close = function () {
if (!cpNf) { return; }
if (typeof(cpNf.stop) !== "function") { return; }
cpNf.stop();
};
var cb = Util.once(Util.both(close, Util.mkAsync(_cb)));
cfg.onMessage = function (msg, user, vKey, isCp, hash, author, data) {
var time = data && data.time;
try {
msg = JSON.parse(msg);
} catch (e) {
console.error(e);
}
if (author) { msg.author = author; }
console.log(msg);
all.push(msg);
};
cfg.onError = cb;
cfg.onChannelError = cb;
cfg.onReady = function () {
cb(null, all);
};
cpNf = CpNetflux.start(cfg);
});
};
var makeTicket = function (ctx, data, cId, 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'}); }
if (!anonRpc) { return void cb({error: "anonymous rpc session not ready"}); }
var channel = data.channel;
var title = data.title;
var ticket = data.ticket;
var supportKey, myCurve;
nThen((waitFor) => {
// Send ticket to the admins and call back
getKeys(ctx, waitFor((err, obj) => {
if (err) {
waitFor.abort();
return void cb({error: err});
}
supportKey = obj.supportKey;
myCurve = obj.myCurve;
}));
}).nThen((waitFor) => {
// Create ticket mailbox
var keys = Crypto.Curve.deriveKeys(supportKey, 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) => {
if (err) {
waitFor.abort();
return void cb({error: err});
}
}));
}).nThen((waitFor) => {
// Store in our worker
console.error(channel);
ctx.supportData[channel] = {
time: +new Date(),
title: title,
curvePublic: supportKey // XXX Should we store this value or delete the ticket on key rotation?
};
ctx.Store.onSync(null, waitFor());
}).nThen(() => {
var supportChannel = Hash.getChannelIdFromKey(supportKey);
console.error(supportChannel);
mailbox.sendTo('NEW_TICKET', {
title: title,
channel: channel
}, {
channel: supportChannel,
curvePublic: supportKey
}, (obj) => {
// Don't store the ticket in case of error
if (obj && obj.error) { delete ctx.supportData[channel]; }
cb(obj);
});
});
};
var getMyTickets = function (ctx, data, cId, cb) {
var all = [];
var n = nThen;
Object.keys(ctx.supportData).forEach(function (ticket) {
n = n((waitFor) => {
var t = Util.clone(ctx.supportData[ticket]);
getContent(ctx, {
channel: ticket,
}, waitFor((err, messages) => {
if (err) { t.error = err; }
else { t.messages = messages; }
all.push(t);
}));
}).nThen;
});
n(() => {
cb({tickets: all});
});
};
Support.init = function (cfg, waitFor, emit) {
var support = {};
// Already initialized by a "noDrive" tab?
if (cfg.store && cfg.store.modules && cfg.store.modules['support']) {
return cfg.store.modules['support'];
}
var store = cfg.store;
var proxy = store.proxy.support = store.proxy.support || {};
var ctx = {
supportData: proxy,
store: cfg.store,
Store: cfg.Store,
emit: emit,
channels: {},
clients: {}
};
support.removeClient = function (clientId) {
removeClient(ctx, clientId);
};
support.leavePad = function (padChan) {
leaveChannel(ctx, padChan);
};
support.execCommand = function (clientId, obj, cb) {
var cmd = obj.cmd;
var data = obj.data;
if (cmd === 'MAKE_TICKET') {
return void makeTicket(ctx, data, clientId, cb);
}
if (cmd === 'GET_MY_TICKETS') {
return void getMyTickets(ctx, data, clientId, cb);
}
};
return support;
};
return Support;
});

View file

@ -0,0 +1,42 @@
/*
* SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@import (reference) '../../customize/src/less2/include/framework.less';
@import (reference) '../../customize/src/less2/include/sidebar-layout.less';
@import (reference) '../../customize/src/less2/include/support.less';
@import (reference) '../../customize/src/less2/include/charts.less';
&.cp-app-moderation {
.framework_min_main();
.sidebar-layout_main();
.support_main();
.charts_main();
.cp-hidden {
display: none !important;
}
label{
margin-top:0.5rem;
}
display: flex;
flex-flow: column;
.cp-sidebarlayout-element {
label:not(.cp-admin-label) {
font-weight: normal !important;
}
input {
max-width: 25rem;
}
nav {
display: flex;
margin-top: 0.5rem;
}
}
}

20
www/moderation/index.html Normal file
View file

@ -0,0 +1,20 @@
<!--
SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-->
<!DOCTYPE html>
<html>
<head>
<title>CryptPad</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script src="/customize/pre-loading.js?ver=1.1"></script>
<link href="/customize/src/pre-loading.css?ver=1.0" rel="stylesheet" type="text/css">
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.5"></script>
<link href="/customize/src/outer.css?ver=1.3.2" rel="stylesheet" type="text/css">
</head>
<body>
<iframe-placeholder>

22
www/moderation/inner.html Normal file
View file

@ -0,0 +1,22 @@
<!--
SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-->
<!DOCTYPE html>
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script src="/customize/pre-loading.js?ver=1.1"></script>
<link href="/customize/src/pre-loading.css?ver=1.0" rel="stylesheet" type="text/css">
<script async data-bootload="/moderation/inner.js" data-main="/common/sframe-boot.js?ver=1.11" src="/components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
</style>
</head>
<body class="cp-app-moderation">
<div id="cp-toolbar" class="cp-toolbar-container"></div>
<div id="cp-content-container"></div>
</body>

104
www/moderation/inner.js Normal file
View file

@ -0,0 +1,104 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
define([
'jquery',
'/api/config',
'/customize/application_config.js',
'/components/chainpad-crypto/crypto.js',
'/common/toolbar.js',
'/components/nthen/index.js',
'/common/sframe-common.js',
'/common/hyperscript.js',
'/customize/messages.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-signing-keys.js',
'/support/ui.js',
'/common/clipboard.js',
'json.sortify',
'css!/lib/datepicker/flatpickr.min.css',
'css!/components/bootstrap/dist/css/bootstrap.min.css',
'css!/components/components-font-awesome/css/font-awesome.min.css',
'less!/moderation/app-moderation.less',
], function (
$,
ApiConfig,
AppConfig,
Crypto,
Toolbar,
nThen,
SFCommon,
h,
Messages,
UI,
UIElements,
Util,
Hash,
Keys,
Support,
Clipboard,
Sortify,
)
{
var APP = {
'instanceStatus': {}
};
var Nacl = window.nacl;
var common;
var sFrameChan;
// XXX
var andThen = function () {
};
var createToolbar = function () {
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications'];
var configTb = {
displayed: displayed,
sfCommon: common,
$container: APP.$toolbar,
pageTitle: Messages.supportPage,
metadataMgr: common.getMetadataMgr(),
};
APP.toolbar = Toolbar.create(configTb);
APP.toolbar.$rightside.hide();
};
nThen(function (waitFor) {
$(waitFor(UI.addLoadingScreen));
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
}).nThen(function (waitFor) {
APP.$container = $('#cp-content-container');
APP.$toolbar = $('#cp-toolbar');
sFrameChan = common.getSframeChannel();
sFrameChan.onReady(waitFor());
}).nThen(function (waitFor) {
if (!common.isAdmin()) { return; } // XXX moderator
}).nThen(function (/*waitFor*/) {
createToolbar();
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
common.setTabTitle(Messages.supportPage);
if (!common.isAdmin()) {
return void UI.errorLoadingScreen(Messages.admin_authError || '403 Forbidden');
}
APP.privateKey = privateData.supportPrivateKey;
APP.origin = privateData.origin;
APP.readOnly = privateData.readOnly;
APP.support = Support.create(common, true);
UI.removeLoadingScreen();
});
});

47
www/moderation/main.js Normal file
View file

@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// Load #1, load as little as possible because we are in a race to get the loading screen up.
define([
'/components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/sframe-common-outer.js',
], function (nThen, ApiConfig, DomReady, SFCommonO) {
// Loaded in load #2
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
SFCommonO.initIframe(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;
if (window.location.hash) {
category = window.location.hash.slice(1);
window.location.hash = '';
}
var addData = function (obj) {
if (category) { obj.category = category; }
};
SFCommonO.start({
noRealtime: true,
addRpc: addRpc,
addData: addData
});
});
});

View file

@ -47,6 +47,7 @@ define([
var categories = {
'tickets': [ // Msg.support_cat_tickets
'cp-support-list',
'cp-support-listnew',
],
'new': [ // Msg.support_cat_new
'cp-support-subscribe',
@ -151,6 +152,29 @@ define([
return $div;
};
create['listnew'] = function () {
var key = 'listnew';
var $div = makeBlock(key); // Msg.support_listHint, .support_listTitle
let refresh = function () {
APP.supportModule.execCommand('GET_MY_TICKETS', {}, function (obj) {
console.error(obj);
if (obj && obj.error) {
console.error(obj.error);
return void UI.warn(Messages.error);
}
});
};
var button = h('btn.btn-primary', 'refresh'); // XXX
Util.onClickEnter($(button), function () {
refresh();
});
$div.append([
button
]);
return $div;
};
create['language'] = function () {
if (!Array.isArray(AppConfig.supportLanguages)) { return $(h('div')); }
var languages = AppConfig.supportLanguages;
@ -209,17 +233,19 @@ define([
var id = Util.uid();
$div.find('button').click(function () {
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
var user = metadataMgr.getUserData();
var sent = APP.support.sendForm(id, form, {
channel: privateData.support,
curvePublic: user.curvePublic
});
id = Util.uid();
if (sent) {
var data = APP.support.getFormData(form);
APP.supportModule.execCommand('MAKE_TICKET', {
channel: Hash.createChannelId(),
title: data.title,
ticket: data
}, function (obj) {
if (obj && obj.error) {
console.error(obj.error);
return void UI.warn(Messages.error);
}
id = Util.uid();
$('.cp-sidebarlayout-category[data-category="tickets"]').click();
}
});
});
$div.find('button').before(form);
return $div;
@ -345,6 +371,7 @@ define([
APP.origin = privateData.origin;
APP.readOnly = privateData.readOnly;
APP.support = Support.create(common, false, APP.pinUsage, APP.teamsUsage);
APP.supportModule = common.makeUniversal('support');
// Content
var $rightside = APP.$rightside;

View file

@ -34,7 +34,7 @@ define([
};
SFCommonO.start({
noRealtime: true,
addData: addData
addData: addData,
});
});
});

View file

@ -22,6 +22,7 @@ define([
var user = metadataMgr.getUserData();
var teams = privateData.teams || {};
data = data || {};
if (data.sender) { return data; }
data.sender = {
name: user.name,
@ -101,7 +102,7 @@ define([
};
var sendForm = function (ctx, id, form, dest) {
var getFormData = function (ctx, form) {
var $form = $(form);
var $cat = $form.find('.cp-support-form-category');
var $title = $form.find('.cp-support-form-title');
@ -110,14 +111,6 @@ define([
var $attachments = $form.find('.cp-support-attachments');
var category = $cat.val().trim();
/*
Messages.support_formCategoryError = "Please select a ticket category from the dropdown menu"; // TODO
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);
@ -140,13 +133,15 @@ Messages.support_formCategoryError = "Please select a ticket category from the d
});
$attachments.html('');
send(ctx, id, 'TICKET', {
return getDebuggingData(ctx, {
category: category,
title: title,
attachments: attachments,
message: content,
}, dest);
});
};
var sendForm = function (ctx, id, form, dest) {
send(ctx, id, 'TICKET', getFormData(ctx, form), dest, isNew);
return true;
};
@ -167,9 +162,7 @@ Messages.support_formCategoryError = "Please select a ticket category from the d
return {
tag: 'a',
content: h('span', Messages['support_cat_'+key]),
action: function () {
onChange(key);
}
attributes: { 'data-value': key }
};
});
var dropdownCfg = {
@ -181,6 +174,7 @@ Messages.support_formCategoryError = "Please select a ticket category from the d
};
var $select = UIElements.createDropdown(dropdownCfg);
$select.find('button').addClass('btn');
$select.onChange.reg(onChange);
return $select;
};
@ -221,7 +215,7 @@ Messages.support_formCategoryError = "Please select a ticket category from the d
ctx.common.openUnsafeURL(href);
};
makeCategoryDropdown(ctx, catContainer, function (key) {
makeCategoryDropdown(ctx, catContainer, function (text, key) {
$(category).val(key);
if (!notice) { return; }
//console.log(key);
@ -532,6 +526,8 @@ Messages.support_formCategoryError = "Please select a ticket category from the d
adminKeys: Array.isArray(ApiConfig.adminKeys)? ApiConfig.adminKeys.slice(): [],
};
ctx.supportModule = common.makeUniversal('support');
var fmConfig = {
body: $('body'),
noStore: true, // Don't store attachments into our drive
@ -543,8 +539,8 @@ Messages.support_formCategoryError = "Please select a ticket category from the d
};
ctx.FM = common.createFileManager(fmConfig);
ui.sendForm = function (id, form, dest) {
return sendForm(ctx, id, form, dest);
ui.getFormData = function (form) {
return getFormData(ctx, form);
};
ui.makeForm = function (cb, title, hideNotice) {
return makeForm(ctx, cb, title, hideNotice);