New support: answer anonymously for admins

This commit is contained in:
yflory 2024-02-21 17:31:07 +01:00
parent 62052929e6
commit 06d9201bbd
7 changed files with 116 additions and 81 deletions

View file

@ -44,6 +44,14 @@
.avatar_main(30px);
padding: 0 5px;
}
.cp-avatar-image {
display: inline-flex;
align-items: center;
justify-content: center;
img {
height: 40px;
}
}
.cp-notification-content {
flex: 1;
align-items: stretch;

View file

@ -497,7 +497,7 @@ define([
};
content.handler = function () {
let url = msg.isAdmin ? '/support/#tickets' : `/moderation/#support-${content.channel}`;
common.openURL(msg.url);
common.openURL(url);
defaultDismiss(common, data)();
};
if (!content.archived) {

View file

@ -212,6 +212,9 @@ define([
title: data.ticket.title,
isClose: data.ticket.close,
channel: data.channel,
user: Util.find(data.ticket, ['sender', 'curvePublic']) ? undefined : {
supportTeam: true
}
}, {
channel: notifChannel,
curvePublic: notifKey

View file

@ -87,6 +87,8 @@ define([
e.stopPropagation();
Common.openURL(Hash.hashToHref(userData.profile, 'profile'));
});
} else if (userData && userData.supportTeam) {
avatar = h('span.cp-avatar-image', h('img', { src:'/customize/CryptPad_logo.svg' }));
}
var order = -Math.floor((Util.find(data, ['content', 'msg', 'ctime']) || 0) / 1000);
const tabIndexValue = undefined;//data.content.isDismissible ? undefined : '0';

View file

@ -17,6 +17,6 @@ SPDX-License-Identifier: AGPL-3.0-or-later
</head>
<body class="cp-app-moderation">
<div id="cp-toolbar" class="cp-toolbar-container"></div>
<div id="cp-content-container"></div>
<div id="cp-sidebarlayout-container"></div>
</body>

View file

@ -6,7 +6,6 @@ define([
'jquery',
'/api/config',
'/customize/application_config.js',
'/components/chainpad-crypto/crypto.js',
'/common/toolbar.js',
'/components/nthen/index.js',
'/common/sframe-common.js',
@ -16,20 +15,15 @@ define([
'/common/common-ui-elements.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-signing-keys.js',
'/common/inner/sidebar-layout.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,
@ -39,38 +33,33 @@ define([
UIElements,
Util,
Hash,
Keys,
Sidebar,
Support,
Clipboard,
Sortify
)
{
var APP = {
'instanceStatus': {}
};
var APP = {};
var Nacl = window.nacl;
var common;
var sFrameChan;
var sframeChan;
var events = {
'NEW_TICKET': Util.mkEvent(),
'UPDATE_TICKET': Util.mkEvent()
};
var andThen = function (linkedTicket) {
var $body = $('#cp-content-container');
// XXX
Messages.support_activeListTitle = "Active tickets";
Messages.support_activeListHint = "List of tickets that are in an active state";
Messages.support_pendingListTitle = "Pending tickets";
Messages.support_pendingListHint = "List of tickets that may not be updated for a while but should not be closed";
var button = h('button.btn.btn-primary', 'refresh'); // XXX
$body.append(h('div', button));
$body.append(h('h1'), 'ACTIVE');
var $containerActive = $(h('div.cp-support-container')).appendTo($body);
$body.append(h('h1'), 'PENDING');
var $containerPending = $(h('div.cp-support-container')).appendTo($body);
$body.append(h('h1'), 'CLOSED');
var $containerClosed = $(h('div.cp-support-container')).appendTo($body);
Messages.support_privacyTitle = "Answer anonymously";
Messages.support_privacyHint = "Check this option to reply as 'The Support Team' instead of your own usenrame";
var andThen = function (common, $container, linkedTicket) {
const sidebar = Sidebar.create(common, 'support', $container);
const blocks = sidebar.blocks;
// Support panel functions
let open = [];
let refresh = ($container, type) => {
APP.module.execCommand('LIST_TICKETS_ADMIN', {
@ -201,18 +190,66 @@ define([
console.log(type, tickets);
});
};
let activeContainer, pendingContainer, closedContainer;
var refreshAll = function () {
refresh($containerActive, 'active');
refresh($containerPending, 'pending');
refresh($containerClosed, 'closed');
console.error('refresh');
refresh($(activeContainer), 'active');
refresh($(pendingContainer), 'pending');
refresh($(closedContainer), 'closed');
};
// Make sidebar layout
const categories = {
'open': {
icon: undefined,
content: [
'privacy',
'active-list',
'pending-list',
]
},
'closed': {
icon: undefined,
content: [
'closed-list'
]
},
'refresh': {
icon: undefined,
action: refreshAll
}
};
sidebar.addCheckboxItem({
key: 'privacy',
getState: () => false,
query: (val, setState) => {
APP.support.setAnonymous(val);
setState(val);
}
});
sidebar.addItem('active-list', cb => {
let div = activeContainer = h('div.cp-support-container'); // XXX block
cb(div);
});
sidebar.addItem('pending-list', cb => {
let div = pendingContainer = h('div.cp-support-container'); // XXX block
cb(div);
});
sidebar.addItem('closed-list', cb => {
let div = closedContainer = h('div.cp-support-container'); // XXX block
cb(div);
}, { noTitle: true, noHint: true });
let _refresh = Util.throttle(refreshAll, 500);
events.NEW_TICKET.reg(_refresh);
events.UPDATE_TICKET.reg(_refresh);
Util.onClickEnter($(button), refreshAll); // XXX temp button?
refreshAll();
sidebar.makeLeftside(categories);
};
var createToolbar = function () {
@ -232,10 +269,10 @@ define([
$(waitFor(UI.addLoadingScreen));
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
}).nThen(function (waitFor) {
APP.$container = $('#cp-content-container');
APP.$container = $('#cp-sidebarlayout-container');
APP.$toolbar = $('#cp-toolbar');
sFrameChan = common.getSframeChannel();
sFrameChan.onReady(waitFor());
sframeChan = common.getSframeChannel();
sframeChan.onReady(waitFor());
}).nThen(function (/*waitFor*/) {
createToolbar();
var metadataMgr = common.getMetadataMgr();
@ -266,7 +303,7 @@ define([
active = active.split('-')[0];
}
andThen(linkedTicket);
andThen(common, APP.$container, linkedTicket);
UI.removeLoadingScreen();
});

View file

@ -15,6 +15,9 @@ define([
'/customize/pages.js',
], function ($, ApiConfig, h, UI, Hash, Util, Clipboard, UIElements, Messages, Pages) {
Messages.support_team = "The Support Team"; // XXX
Messages.support_answerAs = "Answer as <b>{0}</b>"; // XXX
var getDebuggingData = function (ctx, data) {
var common = ctx.common;
var metadataMgr = common.getMetadataMgr();
@ -34,6 +37,14 @@ define([
notifications: user.notifications,
};
if (ctx.isAdmin && ctx.anonymous) {
data.sender = {
name: Messages.support_team,
accountName: 'support'
// XXX send edPublic? or keep it private
};
}
if (typeof(ctx.pinUsage) === 'object') {
// pass pin.usage, pin.limit, and pin.plan if supplied
data.sender.quota = ctx.pinUsage;
@ -63,45 +74,6 @@ define([
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 getFormData = function (ctx, form) {
var $form = $(form);
var $cat = $form.find('.cp-support-form-category');
@ -140,10 +112,6 @@ define([
message: content,
});
};
var sendForm = function (ctx, id, form, dest) {
send(ctx, id, 'TICKET', getFormData(ctx, form), dest);
return true;
};
var makeCategoryDropdown = function (ctx, container, onChange, all) {
var categories = [
@ -187,6 +155,14 @@ define([
abuse: Pages.customURLs.terms,
};
var getAnswerAs = function (ctx) {
var common = ctx.common;
var metadataMgr = common.getMetadataMgr();
var user = metadataMgr.getUserData();
var answerName = Util.fixHTML(ctx.anonymous ? Messages.support_team : user.name);
return Messages._getKey('support_answerAs', [answerName]);
};
var makeForm = function (ctx, oldData, cb, title, hideNotice) {
var button;
@ -239,6 +215,8 @@ define([
var attachments, addAttachment;
var answerAs = UI.setHTML(h('span.cp-support-answeras'), getAnswerAs(ctx));
var content = [
h('hr'),
category,
@ -254,12 +232,14 @@ define([
h('textarea.cp-support-form-msg', {
placeholder: Messages.support_formMessage
}, (oldData && oldData.message) || ''),
h('br'),
h('label', Messages.support_attachments),
attachments = h('div.cp-support-attachments'),
addAttachment = h('button.btn', Messages.support_addAttachment),
h('hr'),
button,
cancel
cancel,
ctx.isAdmin? answerAs : undefined
];
var _addAttachment = (name, href) => {
@ -440,7 +420,8 @@ define([
// 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 fromAdmin = ctx.adminKeys.indexOf(senderKey) !== -1
|| (!senderKey && content.sender.accountName === 'support'); // XXX anon key?
var fromPremium = Boolean(content.sender.plan || Util.find(content, ['sender', 'quota', 'plan']));
var userData = h('div.cp-support-showdata', [
@ -550,6 +531,10 @@ define([
};
ctx.FM = common.createFileManager(fmConfig);
ui.setAnonymous = function (val) {
ctx.anonymous = ctx.isAdmin && val;
$('.cp-support-answeras').html(getAnswerAs(ctx));
};
ui.getFormData = function (form) {
return getFormData(ctx, form);
};