2024-02-12 21:30:33 +08:00
|
|
|
|
// 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',
|
|
|
|
|
'/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',
|
2024-02-22 00:31:07 +08:00
|
|
|
|
'/common/inner/sidebar-layout.js',
|
2024-02-12 21:30:33 +08:00
|
|
|
|
'/support/ui.js',
|
|
|
|
|
|
2024-03-01 01:36:17 +08:00
|
|
|
|
'/components/file-saver/FileSaver.min.js',
|
|
|
|
|
|
2024-02-12 21:30:33 +08:00
|
|
|
|
'css!/components/components-font-awesome/css/font-awesome.min.css',
|
|
|
|
|
'less!/moderation/app-moderation.less',
|
|
|
|
|
], function (
|
|
|
|
|
$,
|
|
|
|
|
ApiConfig,
|
|
|
|
|
AppConfig,
|
|
|
|
|
Toolbar,
|
|
|
|
|
nThen,
|
|
|
|
|
SFCommon,
|
|
|
|
|
h,
|
|
|
|
|
Messages,
|
|
|
|
|
UI,
|
|
|
|
|
UIElements,
|
|
|
|
|
Util,
|
|
|
|
|
Hash,
|
2024-02-22 00:31:07 +08:00
|
|
|
|
Sidebar,
|
2024-02-28 00:17:45 +08:00
|
|
|
|
Support
|
2024-02-12 21:30:33 +08:00
|
|
|
|
)
|
|
|
|
|
{
|
2024-02-22 00:31:07 +08:00
|
|
|
|
var APP = {};
|
2024-03-01 01:36:17 +08:00
|
|
|
|
var saveAs = window.saveAs;
|
2024-02-12 21:30:33 +08:00
|
|
|
|
|
|
|
|
|
var common;
|
2024-02-22 00:31:07 +08:00
|
|
|
|
var sframeChan;
|
2024-02-20 21:00:09 +08:00
|
|
|
|
var events = {
|
2024-03-01 23:34:27 +08:00
|
|
|
|
NEW_TICKET: Util.mkEvent(),
|
|
|
|
|
UPDATE_TICKET: Util.mkEvent(),
|
|
|
|
|
UPDATE_RIGHTS: Util.mkEvent(),
|
2024-03-05 01:14:59 +08:00
|
|
|
|
RECORDED_CHANGE: Util.mkEvent(),
|
|
|
|
|
REFRESH_FILTER: Util.mkEvent(),
|
|
|
|
|
REFRESH_TAGS: Util.mkEvent()
|
2024-02-20 21:00:09 +08:00
|
|
|
|
};
|
2024-02-12 21:30:33 +08:00
|
|
|
|
|
2024-03-06 22:29:28 +08:00
|
|
|
|
|
2024-02-22 00:31:07 +08:00
|
|
|
|
var andThen = function (common, $container, linkedTicket) {
|
|
|
|
|
const sidebar = Sidebar.create(common, 'support', $container);
|
2024-02-29 00:25:32 +08:00
|
|
|
|
const blocks = sidebar.blocks;
|
2024-03-01 23:34:27 +08:00
|
|
|
|
APP.recorded = {};
|
2024-03-05 01:14:59 +08:00
|
|
|
|
APP.allTags = [];
|
2024-03-01 23:34:27 +08:00
|
|
|
|
APP.openTicketCategory = Util.mkEvent();
|
2024-02-16 01:37:08 +08:00
|
|
|
|
|
2024-03-06 22:29:28 +08:00
|
|
|
|
var sortTicket = tickets => (c1, c2) => {
|
|
|
|
|
return tickets[c2].time - tickets[c1].time;
|
|
|
|
|
};
|
|
|
|
|
const onShowTicket = function (ticket, channel, data, done) {
|
|
|
|
|
APP.module.execCommand('LOAD_TICKET_ADMIN', {
|
|
|
|
|
channel: channel,
|
|
|
|
|
curvePublic: data.authorKey,
|
|
|
|
|
supportKey: data.supportKey
|
|
|
|
|
}, function (obj) {
|
|
|
|
|
if (!Array.isArray(obj)) {
|
|
|
|
|
console.error(obj && obj.error);
|
|
|
|
|
done(false);
|
|
|
|
|
return void UI.warn(Messages.error);
|
|
|
|
|
}
|
|
|
|
|
var $ticket = $(ticket);
|
|
|
|
|
obj.forEach(function (msg) {
|
|
|
|
|
// Only add notifications channel if this is coming from the other user
|
|
|
|
|
if (!data.notifications && msg.sender.drive) {
|
|
|
|
|
data.notifications = Util.find(msg, ['sender', 'notifications']);
|
|
|
|
|
}
|
|
|
|
|
if (msg.close) {
|
|
|
|
|
$ticket.addClass('cp-support-list-closed');
|
|
|
|
|
return $ticket.append(APP.support.makeCloseMessage(msg));
|
|
|
|
|
}
|
|
|
|
|
if (msg.legacy && msg.messages) {
|
|
|
|
|
msg.messages.forEach(c => {
|
|
|
|
|
$ticket.append(APP.support.makeMessage(c));
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$ticket.append(APP.support.makeMessage(msg));
|
|
|
|
|
});
|
|
|
|
|
done(true);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2024-02-22 00:31:07 +08:00
|
|
|
|
// Support panel functions
|
2024-02-20 21:00:09 +08:00
|
|
|
|
let open = [];
|
2024-03-27 21:42:13 +08:00
|
|
|
|
let refreshAll = function () {
|
|
|
|
|
APP.$refreshButton.prop('disabled', false);
|
|
|
|
|
};
|
2024-03-01 19:21:30 +08:00
|
|
|
|
let refresh = ($container, type, _cb) => {
|
|
|
|
|
let cb = Util.mkAsync(_cb || function () {});
|
2024-02-21 00:50:41 +08:00
|
|
|
|
APP.module.execCommand('LIST_TICKETS_ADMIN', {
|
|
|
|
|
type: type
|
|
|
|
|
}, (tickets) => {
|
2024-02-27 02:07:39 +08:00
|
|
|
|
if (tickets.error) {
|
2024-03-01 19:21:30 +08:00
|
|
|
|
cb();
|
2024-02-27 02:07:39 +08:00
|
|
|
|
if (tickets.error === 'EFORBIDDEN') {
|
|
|
|
|
return void UI.errorLoadingScreen(Messages.admin_authError || '403 Forbidden');
|
|
|
|
|
}
|
|
|
|
|
return void UI.errorLoadingScreen(tickets.error);
|
|
|
|
|
}
|
2024-03-01 23:34:27 +08:00
|
|
|
|
open = open.filter(chan => {
|
|
|
|
|
// Remove deleted tickets from memory
|
|
|
|
|
return tickets[chan];
|
|
|
|
|
});
|
2024-02-27 02:07:39 +08:00
|
|
|
|
UI.removeLoadingScreen();
|
|
|
|
|
|
2024-02-20 21:00:09 +08:00
|
|
|
|
let activeForms = {};
|
|
|
|
|
$container.find('.cp-support-form-container').each((i, el) => {
|
|
|
|
|
let id = $(el).attr('data-id');
|
|
|
|
|
if (!id) { return; }
|
|
|
|
|
activeForms[id] = el;
|
|
|
|
|
});
|
2024-02-16 01:37:08 +08:00
|
|
|
|
$container.empty();
|
|
|
|
|
var col1 = h('div.cp-support-column', h('h1', [
|
|
|
|
|
h('span', Messages.admin_support_premium),
|
|
|
|
|
h('span.cp-support-count'),
|
|
|
|
|
]));
|
|
|
|
|
var col2 = h('div.cp-support-column', h('h1', [
|
|
|
|
|
h('span', Messages.admin_support_normal),
|
|
|
|
|
h('span.cp-support-count'),
|
|
|
|
|
]));
|
|
|
|
|
var col3 = h('div.cp-support-column', h('h1', [
|
|
|
|
|
h('span', Messages.admin_support_answered),
|
|
|
|
|
h('span.cp-support-count'),
|
|
|
|
|
]));
|
2024-02-21 00:50:41 +08:00
|
|
|
|
var col4 = h('div.cp-support-column', h('h1', [
|
|
|
|
|
h('span', Messages.admin_support_closed),
|
|
|
|
|
h('span.cp-support-count'),
|
|
|
|
|
]));
|
2024-03-01 18:15:29 +08:00
|
|
|
|
var col5 = h('div.cp-support-column', h('h1', [
|
|
|
|
|
h('span', Messages.support_pending),
|
|
|
|
|
h('span.cp-support-count'),
|
|
|
|
|
]));
|
2024-02-21 00:50:41 +08:00
|
|
|
|
if (type === 'closed') {
|
|
|
|
|
// Only one column
|
|
|
|
|
col1 = col2 = col3 = col4;
|
|
|
|
|
}
|
2024-03-01 18:15:29 +08:00
|
|
|
|
if (type === 'pending') {
|
|
|
|
|
// Only one column
|
|
|
|
|
col1 = col2 = col3 = col5;
|
|
|
|
|
}
|
2024-02-16 01:37:08 +08:00
|
|
|
|
$container.append([col1, col2, col3]);
|
2024-02-12 21:30:33 +08:00
|
|
|
|
|
2024-02-20 21:00:09 +08:00
|
|
|
|
const onShow = function (ticket, channel, data, done) {
|
2024-03-06 22:29:28 +08:00
|
|
|
|
onShowTicket(ticket, channel, data, (success) => {
|
|
|
|
|
if (success) {
|
|
|
|
|
if (!open.includes(channel)) { open.push(channel); }
|
2024-02-16 01:37:08 +08:00
|
|
|
|
}
|
2024-02-20 21:00:09 +08:00
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
const onHide = function (ticket, channel, data, done) {
|
|
|
|
|
$(ticket).find('.cp-support-list-message').remove();
|
|
|
|
|
open = open.filter((chan) => {
|
|
|
|
|
return chan !== channel;
|
2024-02-16 01:37:08 +08:00
|
|
|
|
});
|
2024-02-20 21:00:09 +08:00
|
|
|
|
done();
|
2024-02-16 01:37:08 +08:00
|
|
|
|
};
|
2024-02-28 00:17:45 +08:00
|
|
|
|
const onReply = function (ticket, channel, data, form) {
|
2024-02-16 01:37:08 +08:00
|
|
|
|
var formData = APP.support.getFormData(form);
|
|
|
|
|
APP.module.execCommand('REPLY_TICKET_ADMIN', {
|
|
|
|
|
channel: channel,
|
|
|
|
|
curvePublic: data.authorKey,
|
2024-02-19 22:20:50 +08:00
|
|
|
|
notifChannel: data.notifications,
|
2024-02-27 23:52:30 +08:00
|
|
|
|
supportKey: data.supportKey,
|
2024-02-16 01:37:08 +08:00
|
|
|
|
ticket: formData
|
|
|
|
|
}, function (obj) {
|
|
|
|
|
if (obj && obj.error) {
|
|
|
|
|
console.error(obj && obj.error);
|
|
|
|
|
return void UI.warn(Messages.error);
|
|
|
|
|
}
|
|
|
|
|
$(ticket).find('.cp-support-list-message').remove();
|
2024-02-20 21:00:09 +08:00
|
|
|
|
$(ticket).find('.cp-support-form-container').remove();
|
2024-02-21 00:50:41 +08:00
|
|
|
|
refresh($container, type);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
const onClose = function (ticket, channel, data) {
|
|
|
|
|
APP.module.execCommand('CLOSE_TICKET_ADMIN', {
|
|
|
|
|
channel: channel,
|
|
|
|
|
curvePublic: data.authorKey,
|
|
|
|
|
notifChannel: data.notifications,
|
2024-02-27 23:52:30 +08:00
|
|
|
|
supportKey: data.supportKey,
|
2024-02-21 00:50:41 +08:00
|
|
|
|
ticket: APP.support.getDebuggingData({
|
|
|
|
|
close: true
|
|
|
|
|
})
|
|
|
|
|
}, function (obj) {
|
|
|
|
|
if (obj && obj.error) {
|
|
|
|
|
console.error(obj && obj.error);
|
|
|
|
|
return void UI.warn(Messages.error);
|
|
|
|
|
}
|
|
|
|
|
refreshAll();
|
2024-02-16 01:37:08 +08:00
|
|
|
|
});
|
|
|
|
|
};
|
2024-02-29 00:25:32 +08:00
|
|
|
|
const onMove = function (ticket, channel) {
|
2024-02-28 19:37:00 +08:00
|
|
|
|
APP.module.execCommand('MOVE_TICKET_ADMIN', {
|
|
|
|
|
channel: channel,
|
|
|
|
|
from: type,
|
|
|
|
|
to: onMove.isTicketActive ? 'pending' : 'active'
|
|
|
|
|
}, function (obj) {
|
|
|
|
|
if (obj && obj.error) {
|
|
|
|
|
console.error(obj && obj.error);
|
|
|
|
|
return void UI.warn(Messages.error);
|
|
|
|
|
}
|
|
|
|
|
refreshAll();
|
|
|
|
|
});
|
|
|
|
|
};
|
2024-03-06 22:39:22 +08:00
|
|
|
|
onMove.disableMove = type === 'closed';
|
2024-02-28 19:37:00 +08:00
|
|
|
|
onMove.isTicketActive = type === 'active';
|
2024-02-12 21:30:33 +08:00
|
|
|
|
|
2024-03-04 20:48:56 +08:00
|
|
|
|
const onTag = (channel, tags) => {
|
|
|
|
|
APP.module.execCommand('SET_TAGS_ADMIN', {
|
|
|
|
|
channel, tags
|
|
|
|
|
}, function (obj) {
|
|
|
|
|
if (obj && obj.error) {
|
|
|
|
|
console.error(obj && obj.error);
|
|
|
|
|
return void UI.warn(Messages.error);
|
|
|
|
|
}
|
2024-03-21 00:40:20 +08:00
|
|
|
|
if (obj.allTags) { APP.allTags = obj.allTags; }
|
2024-03-05 01:14:59 +08:00
|
|
|
|
events.REFRESH_TAGS.fire();
|
2024-03-04 20:48:56 +08:00
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
onTag.getAllTags = () => {
|
2024-03-05 01:14:59 +08:00
|
|
|
|
return APP.allTags || [];
|
2024-03-04 20:48:56 +08:00
|
|
|
|
};
|
|
|
|
|
|
2024-03-01 19:21:30 +08:00
|
|
|
|
// Show tickets, reload the previously open ones and cal back
|
|
|
|
|
// once everything is loaded
|
|
|
|
|
let n = nThen;
|
2024-03-06 22:29:28 +08:00
|
|
|
|
Object.keys(tickets).sort(sortTicket(tickets)).forEach(function (channel) {
|
2024-03-04 20:48:56 +08:00
|
|
|
|
// Update allTags
|
2024-02-16 01:37:08 +08:00
|
|
|
|
var d = tickets[channel];
|
2024-03-04 20:48:56 +08:00
|
|
|
|
(d.tags || []).forEach(tag => {
|
2024-03-05 01:14:59 +08:00
|
|
|
|
if (!APP.allTags.includes(tag)) { APP.allTags.push(tag); }
|
2024-03-04 20:48:56 +08:00
|
|
|
|
});
|
|
|
|
|
// Make ticket
|
2024-02-20 21:00:09 +08:00
|
|
|
|
var ticket = APP.support.makeTicket({
|
|
|
|
|
id: channel,
|
|
|
|
|
content: d,
|
|
|
|
|
form: activeForms[channel],
|
2024-03-01 23:34:27 +08:00
|
|
|
|
recorded: APP.recorded,
|
2024-03-04 20:48:56 +08:00
|
|
|
|
onShow, onHide, onClose, onReply, onMove, onTag
|
2024-02-20 21:00:09 +08:00
|
|
|
|
});
|
|
|
|
|
|
2024-02-16 01:37:08 +08:00
|
|
|
|
var container;
|
|
|
|
|
if (d.lastAdmin) { container = col3; }
|
|
|
|
|
else if (d.premium) { container = col1; }
|
|
|
|
|
else { container = col2; }
|
|
|
|
|
$(container).append(ticket);
|
2024-02-20 21:00:09 +08:00
|
|
|
|
|
2024-03-01 19:21:30 +08:00
|
|
|
|
if (open.includes(channel)) {
|
|
|
|
|
n = n(waitFor => {
|
|
|
|
|
ticket.open(true, waitFor());
|
|
|
|
|
}).nThen;
|
2024-02-20 21:00:09 +08:00
|
|
|
|
}
|
2024-02-16 01:37:08 +08:00
|
|
|
|
});
|
2024-03-01 19:21:30 +08:00
|
|
|
|
// Wait for all open tickets to be loaded before calling back
|
|
|
|
|
// otherwise we may have a wrong scroll position
|
|
|
|
|
n(() => {
|
|
|
|
|
cb();
|
|
|
|
|
});
|
2024-02-16 01:37:08 +08:00
|
|
|
|
});
|
|
|
|
|
};
|
2024-03-05 01:14:59 +08:00
|
|
|
|
let onFilter = () => {
|
|
|
|
|
let tags = APP.filterTags || [];
|
|
|
|
|
APP.module.execCommand('FILTER_TAGS_ADMIN', { tags }, function (obj) {
|
|
|
|
|
if (!obj || obj.error) { return; }
|
|
|
|
|
$container.find('.cp-support-list-ticket').toggleClass('cp-filtered', false);
|
|
|
|
|
if (obj.all || !obj.tickets || !obj.tickets.length) { return; }
|
|
|
|
|
obj.tickets.forEach(id => {
|
|
|
|
|
$container.find(`.cp-support-list-ticket[data-id="${id}"]`)
|
|
|
|
|
.toggleClass('cp-filtered', true);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
2024-02-22 00:31:07 +08:00
|
|
|
|
|
|
|
|
|
let activeContainer, pendingContainer, closedContainer;
|
2024-02-28 00:17:45 +08:00
|
|
|
|
refreshAll = function () {
|
2024-03-01 19:21:30 +08:00
|
|
|
|
let $rightside = sidebar.$rightside;
|
|
|
|
|
let s = $rightside.scrollTop();
|
|
|
|
|
nThen(waitFor => {
|
2024-03-01 23:34:27 +08:00
|
|
|
|
APP.module.execCommand('GET_RECORDED', {}, waitFor(function (obj) {
|
|
|
|
|
if (obj && obj.error) {
|
|
|
|
|
APP.recorded = {};
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
APP.recorded = {
|
|
|
|
|
all: obj.messages,
|
|
|
|
|
onClick: id => {
|
|
|
|
|
APP.module.execCommand('USE_RECORDED', {id}, () => {});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}));
|
|
|
|
|
}).nThen(waitFor => {
|
2024-03-05 01:14:59 +08:00
|
|
|
|
APP.allTags = [];
|
2024-03-01 19:21:30 +08:00
|
|
|
|
refresh($(activeContainer), 'active', waitFor());
|
|
|
|
|
refresh($(pendingContainer), 'pending', waitFor());
|
|
|
|
|
refresh($(closedContainer), 'closed', waitFor());
|
2024-03-05 01:14:59 +08:00
|
|
|
|
}).nThen(() => {
|
|
|
|
|
onFilter();
|
|
|
|
|
events.REFRESH_TAGS.fire();
|
2024-03-01 19:21:30 +08:00
|
|
|
|
}).nThen(waitFor => {
|
2024-03-27 21:42:13 +08:00
|
|
|
|
APP.$refreshButton.prop('disabled', false);
|
2024-03-01 19:21:30 +08:00
|
|
|
|
if (!linkedTicket) { return; }
|
|
|
|
|
let $ticket = $container.find(`[data-link-id="${linkedTicket}"]`);
|
|
|
|
|
linkedTicket = undefined;
|
|
|
|
|
if ($ticket.length) {
|
|
|
|
|
let ticket = $ticket[0];
|
|
|
|
|
if (typeof(ticket.open) === "function") {
|
|
|
|
|
waitFor.abort();
|
|
|
|
|
ticket.open(true, () => {
|
|
|
|
|
ticket.scrollIntoView();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}).nThen(() => {
|
|
|
|
|
$rightside.scrollTop(s);
|
|
|
|
|
});
|
2024-02-21 00:50:41 +08:00
|
|
|
|
};
|
2024-02-22 01:52:12 +08:00
|
|
|
|
let _refresh = Util.throttle(refreshAll, 500);
|
|
|
|
|
events.NEW_TICKET.reg(_refresh);
|
|
|
|
|
events.UPDATE_TICKET.reg(_refresh);
|
2024-03-01 23:34:27 +08:00
|
|
|
|
events.RECORDED_CHANGE.reg(_refresh);
|
2024-02-27 02:07:39 +08:00
|
|
|
|
events.UPDATE_RIGHTS.reg(_refresh);
|
2024-03-05 01:14:59 +08:00
|
|
|
|
events.REFRESH_FILTER.reg(onFilter);
|
2024-02-21 00:50:41 +08:00
|
|
|
|
|
2024-02-22 00:31:07 +08:00
|
|
|
|
// Make sidebar layout
|
|
|
|
|
const categories = {
|
2024-03-26 02:05:12 +08:00
|
|
|
|
'open': { // Msg.support_cat_open
|
2024-03-21 00:40:20 +08:00
|
|
|
|
icon: 'fa fa-inbox',
|
2024-02-22 00:31:07 +08:00
|
|
|
|
content: [
|
2024-03-21 00:40:20 +08:00
|
|
|
|
'refresh',
|
2024-03-05 01:14:59 +08:00
|
|
|
|
'filter',
|
2024-02-22 00:31:07 +08:00
|
|
|
|
'active-list',
|
|
|
|
|
'pending-list',
|
|
|
|
|
]
|
|
|
|
|
},
|
2024-03-26 02:05:12 +08:00
|
|
|
|
'closed': { // Msg.support_cat_closed
|
2024-03-21 00:40:20 +08:00
|
|
|
|
icon: 'fa fa-archive',
|
2024-02-22 00:31:07 +08:00
|
|
|
|
content: [
|
2024-03-21 00:40:20 +08:00
|
|
|
|
'refresh',
|
2024-03-05 01:14:59 +08:00
|
|
|
|
'filter',
|
2024-02-22 00:31:07 +08:00
|
|
|
|
'closed-list'
|
|
|
|
|
]
|
|
|
|
|
},
|
2024-03-26 02:05:12 +08:00
|
|
|
|
'search': { // Msg.support_cat_search
|
2024-03-21 00:40:20 +08:00
|
|
|
|
icon: 'fa fa-search',
|
2024-03-06 22:29:28 +08:00
|
|
|
|
content: [
|
|
|
|
|
'filter',
|
|
|
|
|
'search'
|
|
|
|
|
],
|
|
|
|
|
onOpen: () => {
|
|
|
|
|
APP.searchAutoRefresh = true;
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
$('.cp-support-search-input').focus();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-03-26 02:05:12 +08:00
|
|
|
|
'new': { // Msg.support_cat_new
|
2024-03-21 00:40:20 +08:00
|
|
|
|
icon: 'fa fa-envelope',
|
2024-02-29 00:25:32 +08:00
|
|
|
|
content: [
|
|
|
|
|
'open-ticket'
|
2024-03-01 23:34:27 +08:00
|
|
|
|
],
|
|
|
|
|
onOpen: () => {
|
|
|
|
|
APP.openTicketCategory.fire();
|
2024-03-06 22:29:28 +08:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
$('.cp-support-newticket-paste').focus();
|
|
|
|
|
});
|
2024-03-01 23:34:27 +08:00
|
|
|
|
}
|
2024-02-29 00:25:32 +08:00
|
|
|
|
},
|
2024-03-26 02:05:12 +08:00
|
|
|
|
'legacy': { // Msg.support_cat_legacy
|
2024-03-21 00:40:20 +08:00
|
|
|
|
icon: 'fa fa-server',
|
2024-02-29 01:37:15 +08:00
|
|
|
|
content: [
|
|
|
|
|
'legacy'
|
|
|
|
|
]
|
|
|
|
|
},
|
2024-03-26 02:05:12 +08:00
|
|
|
|
'settings': { // Msg.support_cat_settings
|
2024-03-21 00:40:20 +08:00
|
|
|
|
icon: 'fa fa-cogs',
|
|
|
|
|
content: [
|
|
|
|
|
'privacy',
|
|
|
|
|
'notifications',
|
|
|
|
|
'recorded'
|
|
|
|
|
],
|
|
|
|
|
onOpen: () => {
|
|
|
|
|
setTimeout(() => {
|
2024-03-21 21:30:13 +08:00
|
|
|
|
$('.cp-moderation-recorded-id').focus();
|
2024-03-21 00:40:20 +08:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-02-22 00:31:07 +08:00
|
|
|
|
};
|
|
|
|
|
|
2024-03-01 23:34:27 +08:00
|
|
|
|
if (!APP.privateKey) { delete categories.legacy; }
|
2024-02-22 00:31:07 +08:00
|
|
|
|
|
2024-03-21 00:40:20 +08:00
|
|
|
|
sidebar.addItem('refresh', cb => {
|
|
|
|
|
let button = blocks.button('secondary', 'fa-refresh', Messages.oo_refresh);
|
2024-03-27 21:42:13 +08:00
|
|
|
|
APP.$refreshButton = $(button);
|
2024-03-21 00:40:20 +08:00
|
|
|
|
Util.onClickEnter($(button), () => {
|
2024-03-27 21:42:13 +08:00
|
|
|
|
APP.$refreshButton.prop('disabled', 'disabled');
|
2024-03-21 00:40:20 +08:00
|
|
|
|
refreshAll();
|
|
|
|
|
});
|
|
|
|
|
let content = blocks.block([button]);
|
|
|
|
|
cb(content);
|
|
|
|
|
}, { noTitle: true, noHint: true });
|
|
|
|
|
|
2024-03-26 02:05:12 +08:00
|
|
|
|
// Msg.support_privacyHint.support_privacyTitle
|
2024-02-22 00:31:07 +08:00
|
|
|
|
sidebar.addCheckboxItem({
|
|
|
|
|
key: 'privacy',
|
|
|
|
|
getState: () => false,
|
|
|
|
|
query: (val, setState) => {
|
|
|
|
|
APP.support.setAnonymous(val);
|
|
|
|
|
setState(val);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
sidebar.addItem('active-list', cb => {
|
2024-02-28 00:17:45 +08:00
|
|
|
|
activeContainer = h('div.cp-support-container'); // XXX block
|
|
|
|
|
cb(activeContainer);
|
2024-03-01 18:15:29 +08:00
|
|
|
|
}, { noTitle: true, noHint: true });
|
2024-02-22 00:31:07 +08:00
|
|
|
|
sidebar.addItem('pending-list', cb => {
|
2024-02-28 00:17:45 +08:00
|
|
|
|
pendingContainer = h('div.cp-support-container');
|
|
|
|
|
cb(pendingContainer);
|
2024-03-01 18:15:29 +08:00
|
|
|
|
}, { noTitle: true, noHint: true });
|
2024-02-22 00:31:07 +08:00
|
|
|
|
sidebar.addItem('closed-list', cb => {
|
2024-02-28 00:17:45 +08:00
|
|
|
|
closedContainer = h('div.cp-support-container');
|
|
|
|
|
cb(closedContainer);
|
2024-02-22 00:31:07 +08:00
|
|
|
|
}, { noTitle: true, noHint: true });
|
2024-02-21 00:50:41 +08:00
|
|
|
|
refreshAll();
|
2024-02-22 00:31:07 +08:00
|
|
|
|
|
2024-03-26 02:05:12 +08:00
|
|
|
|
// Msg.support_notificationsHint.support_notificationsTitle.support_notificationsLabel
|
2024-02-22 01:52:12 +08:00
|
|
|
|
sidebar.addCheckboxItem({
|
|
|
|
|
key: 'notifications',
|
|
|
|
|
getState: () => APP.disableSupportNotif,
|
|
|
|
|
query: (val, setState) => {
|
|
|
|
|
common.setAttribute(['general', 'disableSupportNotif'], val, function (err) {
|
|
|
|
|
if (err) { val = APP.disableSupportNotif; }
|
|
|
|
|
APP.disableSupportNotif = val;
|
|
|
|
|
setState(val);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2024-03-06 22:29:28 +08:00
|
|
|
|
sidebar.addItem('search', cb => {
|
|
|
|
|
|
|
|
|
|
let inputSearch = blocks.input({type:'text', class: 'cp-support-search-input'});
|
|
|
|
|
let button = blocks.button('primary', 'fa-search');
|
|
|
|
|
let inputBlock = blocks.inputButton(inputSearch, button, { onEnterDelegate: true });
|
|
|
|
|
let searchBlock = blocks.labelledInput(Messages.support_searchLabel,
|
|
|
|
|
inputSearch, inputBlock);
|
|
|
|
|
|
2024-03-15 23:51:02 +08:00
|
|
|
|
let list = blocks.block([], 'cp-support-container');
|
|
|
|
|
let container = blocks.block([searchBlock, list], 'cp-support-search-container');
|
2024-03-06 22:29:28 +08:00
|
|
|
|
let $list = $(list);
|
|
|
|
|
let searchText = '';
|
|
|
|
|
APP.searchAutoRefresh = false;
|
|
|
|
|
|
|
|
|
|
let redraw = (_cb) => {
|
|
|
|
|
let cb = _cb || function () {};
|
|
|
|
|
$list.empty();
|
|
|
|
|
let tags = APP.filterTags || [];
|
|
|
|
|
let text = searchText;
|
|
|
|
|
if (!text.length && !tags.length) { return void cb(); }
|
|
|
|
|
APP.module.execCommand('SEARCH_ADMIN', { text, tags }, function (obj) {
|
|
|
|
|
cb();
|
|
|
|
|
if (obj && obj.error) {
|
|
|
|
|
console.error(obj && obj.error);
|
|
|
|
|
return void UI.warn(Messages.error);
|
|
|
|
|
}
|
|
|
|
|
$list.empty();
|
|
|
|
|
let tickets = obj.tickets || {};
|
|
|
|
|
|
|
|
|
|
const onShow = onShowTicket;
|
|
|
|
|
const onHide = function (ticket, channel, data, done) {
|
|
|
|
|
$(ticket).find('.cp-support-list-message').remove();
|
|
|
|
|
done();
|
|
|
|
|
};
|
|
|
|
|
const onTag = () => {};
|
|
|
|
|
onTag.readOnly = true;
|
|
|
|
|
onTag.getAllTags = () => [];
|
|
|
|
|
Object.keys(tickets).sort(sortTicket(tickets)).forEach(id => {
|
|
|
|
|
let content = tickets[id];
|
|
|
|
|
content.tags = content.tags || [];
|
|
|
|
|
|
|
|
|
|
let catTag = Messages[`support_${content.category}_tag`];
|
|
|
|
|
if (catTag) {
|
|
|
|
|
// Msg.support_active_tag.support_pending_tag.support_closed_tag
|
|
|
|
|
content.tags.unshift(catTag.toUpperCase());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ticket = APP.support.makeTicket({
|
|
|
|
|
id,
|
|
|
|
|
content,
|
|
|
|
|
onTag, onShow, onHide
|
|
|
|
|
});
|
|
|
|
|
$list.append(ticket);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let $input = $(inputSearch);
|
|
|
|
|
let $button = $(button);
|
|
|
|
|
Util.onClickEnter($button, function () {
|
|
|
|
|
$button.prop('disabled', 'disabled');
|
|
|
|
|
searchText = $input.val().trim();
|
|
|
|
|
redraw(() => {
|
|
|
|
|
APP.searchAutoRefresh = true;
|
|
|
|
|
$button.prop('disabled', false);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
events.REFRESH_FILTER.reg(() => {
|
|
|
|
|
if (!APP.searchAutoRefresh) { return; }
|
|
|
|
|
redraw();
|
|
|
|
|
});
|
|
|
|
|
cb(container);
|
|
|
|
|
}, { noTitle: true, noHint: true });
|
|
|
|
|
|
2024-03-05 01:14:59 +08:00
|
|
|
|
sidebar.addItem('filter', cb => {
|
2024-03-16 00:00:31 +08:00
|
|
|
|
let container = blocks.block([], 'cp-support-filter-container');
|
2024-03-05 01:14:59 +08:00
|
|
|
|
let $container = $(container);
|
|
|
|
|
let redrawTags = () => {
|
|
|
|
|
$container.empty();
|
|
|
|
|
var existing = APP.allTags;
|
|
|
|
|
var list = h('div.cp-tags-list');
|
|
|
|
|
var reset = h('button.btn.btn-cancel.cp-tags-filter-reset', [
|
|
|
|
|
h('i.fa.fa-times'),
|
|
|
|
|
Messages.kanban_clearFilter
|
|
|
|
|
]);
|
|
|
|
|
var hint = h('span', Messages.kanban_tags);
|
|
|
|
|
var tags = h('div.cp-tags-filter', [
|
|
|
|
|
h('span.cp-tags-filter-toggle', [
|
|
|
|
|
hint,
|
|
|
|
|
reset,
|
|
|
|
|
]),
|
|
|
|
|
list,
|
|
|
|
|
]);
|
|
|
|
|
var $reset = $(reset);
|
|
|
|
|
var $list = $(list);
|
|
|
|
|
var $hint = $(hint);
|
|
|
|
|
var setTagFilterState = function (bool) {
|
|
|
|
|
$hint.css('visibility', bool? 'hidden': 'visible');
|
|
|
|
|
$reset.css('visibility', bool? 'visible': 'hidden');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var getTags = function () {
|
|
|
|
|
return $list.find('span.active').map(function () {
|
|
|
|
|
return String($(this).data('tag'));
|
|
|
|
|
}).get();
|
|
|
|
|
};
|
|
|
|
|
var commitTags = function () {
|
|
|
|
|
var t = getTags();
|
|
|
|
|
setTagFilterState(t.length);
|
|
|
|
|
APP.filterTags = t;
|
|
|
|
|
events.REFRESH_FILTER.fire();
|
|
|
|
|
};
|
|
|
|
|
APP.filterTags = (APP.filterTags || []).filter(tag => {
|
|
|
|
|
return existing.includes(tag);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var redrawList = function (allTags) {
|
2024-03-21 00:40:20 +08:00
|
|
|
|
if (!Array.isArray(allTags) || !allTags.length) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
$list.closest('.cp-sidebarlayout-element')
|
|
|
|
|
.toggleClass('cp-sidebar-force-hide', true);
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
$list.closest('.cp-sidebarlayout-element')
|
|
|
|
|
.toggleClass('cp-sidebar-force-hide', false);
|
|
|
|
|
});
|
2024-03-05 01:14:59 +08:00
|
|
|
|
$list.empty();
|
|
|
|
|
$list.removeClass('cp-empty');
|
|
|
|
|
if (!allTags.length) {
|
|
|
|
|
$list.addClass('cp-empty');
|
|
|
|
|
$list.append(h('em', Messages.kanban_noTags));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
allTags.forEach(function (t) {
|
|
|
|
|
let active = APP.filterTags.includes(t) ? '.active' : '';
|
|
|
|
|
var $tag = $(h('span'+active, {'data-tag':t}, t)).appendTo($list);
|
|
|
|
|
Util.onClickEnter($tag, function () {
|
|
|
|
|
$tag.toggleClass('active');
|
|
|
|
|
commitTags();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
redrawList(existing);
|
|
|
|
|
commitTags();
|
|
|
|
|
|
|
|
|
|
Util.onClickEnter($reset, function () {
|
|
|
|
|
$list.find('span').removeClass('active');
|
|
|
|
|
commitTags();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$container.append(tags);
|
|
|
|
|
};
|
|
|
|
|
events.REFRESH_TAGS.reg(redrawTags);
|
|
|
|
|
cb(container);
|
|
|
|
|
}, { noTitle: true, noHint: true });
|
|
|
|
|
|
2024-03-26 02:05:12 +08:00
|
|
|
|
// Msg.support_recordedHint.support_recordedTitle
|
2024-03-01 23:34:27 +08:00
|
|
|
|
sidebar.addItem('recorded', cb => {
|
2024-03-15 23:51:02 +08:00
|
|
|
|
let empty = blocks.inline(Messages.support_recordedEmpty);
|
2024-03-21 21:30:13 +08:00
|
|
|
|
let list = blocks.block([], 'cp-moderation-recorded-list');
|
|
|
|
|
let inputId = blocks.input({type:'text', class: 'cp-moderation-recorded-id',
|
2024-03-06 22:29:28 +08:00
|
|
|
|
maxlength: 20 });
|
2024-03-15 23:51:02 +08:00
|
|
|
|
let inputContent = blocks.textarea();
|
2024-03-01 23:34:27 +08:00
|
|
|
|
let labelId = blocks.labelledInput(Messages.support_recordedId, inputId);
|
|
|
|
|
let labelContent = blocks.labelledInput(Messages.support_recordedContent, inputContent);
|
|
|
|
|
|
|
|
|
|
let create = blocks.button('primary', 'fa-plus', Messages.tag_add);
|
|
|
|
|
let nav = blocks.nav([create]);
|
|
|
|
|
|
|
|
|
|
let form = blocks.form([
|
|
|
|
|
empty,
|
2024-03-21 21:30:13 +08:00
|
|
|
|
list,
|
2024-03-01 23:34:27 +08:00
|
|
|
|
labelId,
|
|
|
|
|
labelContent,
|
|
|
|
|
], nav);
|
|
|
|
|
|
|
|
|
|
let $empty = $(empty);
|
2024-03-21 21:30:13 +08:00
|
|
|
|
let $list = $(list).hide();
|
2024-03-01 23:34:27 +08:00
|
|
|
|
let $create = $(create);
|
|
|
|
|
let $inputId = $(inputId).on('input', () => {
|
|
|
|
|
let val = $inputId.val().toLowerCase().replace(/ /g, '-').replace(/[^a-z-_]/g, '');
|
|
|
|
|
$inputId.val(val);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let refresh = function () {};
|
|
|
|
|
let edit = (id, content, remove) => {
|
|
|
|
|
APP.module.execCommand('SET_RECORDED', {id, content, remove}, function (obj) {
|
|
|
|
|
$create.removeAttr('disabled');
|
|
|
|
|
if (obj && obj.error) {
|
|
|
|
|
console.error(obj.error);
|
|
|
|
|
return void UI.warn(Messages.error);
|
|
|
|
|
}
|
|
|
|
|
$(inputId).val('');
|
|
|
|
|
$(inputContent).val('');
|
|
|
|
|
events.RECORDED_CHANGE.fire();
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
refresh = () => {
|
|
|
|
|
APP.module.execCommand('GET_RECORDED', {}, function (obj) {
|
|
|
|
|
if (obj && obj.error) {
|
|
|
|
|
console.error(obj.error);
|
|
|
|
|
return void UI.warn(Messages.error);
|
|
|
|
|
}
|
|
|
|
|
let messages = obj.messages;
|
2024-03-21 21:30:13 +08:00
|
|
|
|
$list.empty();
|
2024-03-01 23:34:27 +08:00
|
|
|
|
Object.keys(messages).forEach(id => {
|
|
|
|
|
let del = blocks.button('danger-alt', 'fa-trash-o', Messages.kanban_delete);
|
|
|
|
|
Util.onClickEnter($(del), () => {
|
|
|
|
|
edit(id, '', true);
|
|
|
|
|
});
|
2024-03-21 21:30:13 +08:00
|
|
|
|
$list.append(h('div.cp-moderation-recorded', [
|
|
|
|
|
h('span.cp-moderation-recorded-header', id),
|
|
|
|
|
h('div.cp-moderation-recorded-body', [
|
|
|
|
|
h('div.cp-moderation-recorded-content', messages[id].content),
|
|
|
|
|
h('nav', del)
|
|
|
|
|
])
|
|
|
|
|
]));
|
2024-03-01 23:34:27 +08:00
|
|
|
|
});
|
2024-03-21 21:30:13 +08:00
|
|
|
|
if (!Object.keys(messages).length) {
|
2024-03-01 23:34:27 +08:00
|
|
|
|
$list.hide();
|
|
|
|
|
$empty.show();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$list.show();
|
|
|
|
|
$empty.hide();
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Util.onClickEnter($create, function () {
|
|
|
|
|
$create.attr('disabled', 'disabled');
|
|
|
|
|
let id = $(inputId).val().trim();
|
|
|
|
|
let content = $(inputContent).val().trim();
|
|
|
|
|
edit(id, content, false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
events.RECORDED_CHANGE.reg(refresh);
|
|
|
|
|
|
|
|
|
|
refresh();
|
|
|
|
|
cb(form);
|
|
|
|
|
});
|
|
|
|
|
|
2024-03-26 02:05:12 +08:00
|
|
|
|
// Msg.support_openTicketHint.support_openTicketTitle
|
2024-02-29 00:25:32 +08:00
|
|
|
|
sidebar.addItem('open-ticket', cb => {
|
2024-03-01 23:34:27 +08:00
|
|
|
|
let form = APP.support.makeForm({});
|
|
|
|
|
|
|
|
|
|
let updateRecorded = () => {
|
|
|
|
|
APP.module.execCommand('GET_RECORDED', {}, function (obj) {
|
|
|
|
|
if (obj && obj.error) { return; }
|
|
|
|
|
form.updateRecorded({
|
|
|
|
|
all: obj.messages,
|
|
|
|
|
onClick: id => {
|
|
|
|
|
APP.module.execCommand('USE_RECORDED', {id}, () => {});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
events.RECORDED_CHANGE.reg(updateRecorded);
|
|
|
|
|
APP.openTicketCategory.reg(updateRecorded);
|
|
|
|
|
|
2024-02-29 00:25:32 +08:00
|
|
|
|
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]);
|
|
|
|
|
|
2024-03-01 19:21:30 +08:00
|
|
|
|
let reset = blocks.button('danger-alt', 'fa-times', Messages.form_reset);
|
|
|
|
|
|
2024-03-15 23:51:02 +08:00
|
|
|
|
let paste = blocks.textarea({
|
2024-03-06 22:29:28 +08:00
|
|
|
|
class: 'cp-support-newticket-paste',
|
2024-02-29 00:25:32 +08:00
|
|
|
|
placeholder: Messages.support_pasteUserData
|
|
|
|
|
});
|
|
|
|
|
let inputs = h('div.cp-moderation-userdata-inputs', [ labelName, labelChan, labelKey ]);
|
2024-03-01 19:21:30 +08:00
|
|
|
|
let userData = h('div.cp-moderation-userdata', [inputs , paste, reset]);
|
2024-02-29 00:25:32 +08:00
|
|
|
|
|
2024-03-01 19:21:30 +08:00
|
|
|
|
let $reset = $(reset).hide();
|
2024-02-29 00:25:32 +08:00
|
|
|
|
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();
|
2024-03-01 19:21:30 +08:00
|
|
|
|
$reset.show();
|
|
|
|
|
});
|
|
|
|
|
Util.onClickEnter($reset, () => {
|
|
|
|
|
$(inputName).val('');
|
|
|
|
|
$(inputChan).val('');
|
|
|
|
|
$(inputKey).val('');
|
|
|
|
|
$reset.hide();
|
|
|
|
|
$paste.show();
|
2024-03-05 01:14:59 +08:00
|
|
|
|
setTimeout(() => { $paste.focus(); });
|
2024-02-29 00:25:32 +08:00
|
|
|
|
});
|
|
|
|
|
[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);
|
|
|
|
|
});
|
|
|
|
|
|
2024-03-26 02:05:12 +08:00
|
|
|
|
// Msg.support_legacyHint.support_legacyTitle
|
2024-02-29 01:37:15 +08:00
|
|
|
|
sidebar.addItem('legacy', cb => {
|
|
|
|
|
if (!APP.privateKey) { return void cb(false); }
|
|
|
|
|
|
|
|
|
|
let start = blocks.button('primary', 'fa-paper-plane', Messages.support_legacyButton);
|
2024-03-01 01:36:17 +08:00
|
|
|
|
let dump = blocks.button('secondary', 'fa-database', Messages.support_legacyDump);
|
2024-02-29 01:37:15 +08:00
|
|
|
|
let clean = blocks.button('danger', 'fa-trash-o', Messages.support_legacyClear);
|
|
|
|
|
let content = h('div.cp-support-container');
|
2024-03-01 01:36:17 +08:00
|
|
|
|
let nav = blocks.nav([start, dump, clean]);
|
2024-03-27 21:42:13 +08:00
|
|
|
|
let spinner = UI.makeSpinner($(nav));
|
2024-03-01 01:36:17 +08:00
|
|
|
|
|
|
|
|
|
let sortLegacyTickets = contentByHash => {
|
|
|
|
|
let all = {};
|
|
|
|
|
Object.keys(contentByHash).forEach(key => {
|
|
|
|
|
let data = contentByHash[key];
|
|
|
|
|
let content = data.content;
|
|
|
|
|
let id = content.id;
|
|
|
|
|
content.hash = key;
|
|
|
|
|
if (data.ctime) { content.time = data.ctime; }
|
|
|
|
|
if (content.sender && content.sender.curvePublic !== data.author) { return; }
|
|
|
|
|
all[id] = all[id] || [];
|
|
|
|
|
all[id].push(content);
|
|
|
|
|
all[id].sort((c1, c2) => {
|
|
|
|
|
return c1.time - c2.time;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
// sort
|
|
|
|
|
let sorted = Object.keys(all).sort((t1, t2) => {
|
|
|
|
|
let a = t1[0];
|
|
|
|
|
let b = t2[0];
|
|
|
|
|
return (a.time || 0) - (b.time || 0);
|
|
|
|
|
});
|
|
|
|
|
return sorted.map(id => {
|
|
|
|
|
return all[id];
|
|
|
|
|
});
|
|
|
|
|
};
|
2024-03-27 21:42:13 +08:00
|
|
|
|
let $dumpBtn = $(dump);
|
2024-03-01 01:36:17 +08:00
|
|
|
|
UI.confirmButton(dump, { classes: 'btn-secondary' }, function () {
|
2024-03-27 21:42:13 +08:00
|
|
|
|
spinner.spin();
|
|
|
|
|
$dumpBtn.prop('disabled', 'disabled').blur();
|
2024-03-01 01:36:17 +08:00
|
|
|
|
APP.module.execCommand('DUMP_LEGACY', {}, contentByHash => {
|
2024-03-27 21:42:13 +08:00
|
|
|
|
$dumpBtn.prop('disabled', false);
|
|
|
|
|
spinner.done();
|
2024-03-01 01:36:17 +08:00
|
|
|
|
// group by ticket id
|
|
|
|
|
let sorted = sortLegacyTickets(contentByHash);
|
|
|
|
|
let dump = '';
|
|
|
|
|
sorted.forEach((t,i) => {
|
|
|
|
|
if (!Array.isArray(t) || !t.length) { return; }
|
|
|
|
|
let first = t[0];
|
|
|
|
|
if (i) { dump += '\n\n'; }
|
|
|
|
|
dump += `================================
|
|
|
|
|
================================
|
|
|
|
|
ID: #${first.id}
|
|
|
|
|
Title: ${first.title}
|
|
|
|
|
User: ${first.sender.name}
|
|
|
|
|
Date: ${new Date(first.time).toISOString()}`;
|
|
|
|
|
t.forEach(msg => {
|
|
|
|
|
if (!msg.message) {
|
|
|
|
|
dump += `
|
|
|
|
|
--------------------------------
|
|
|
|
|
CLOSED: ${new Date(msg.time).toISOString()}`;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
dump += `
|
|
|
|
|
--------------------------------
|
|
|
|
|
From: ${msg.sender.name}
|
|
|
|
|
Date: ${new Date(msg.time).toISOString()}
|
|
|
|
|
---
|
|
|
|
|
${msg.message}
|
|
|
|
|
---
|
|
|
|
|
Attachments:${JSON.stringify(msg.attachments, 0, 2)}`;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
saveAs(new Blob([dump], {type: 'text/plain'}), "cryptpad-support-dump.txt");
|
|
|
|
|
});
|
|
|
|
|
});
|
2024-02-29 01:37:15 +08:00
|
|
|
|
UI.confirmButton(clean, { classes: 'btn-danger' }, function () {
|
2024-03-01 01:36:17 +08:00
|
|
|
|
APP.module.execCommand('CLEAR_LEGACY', {}, () => {
|
|
|
|
|
delete APP.privateKey;
|
|
|
|
|
sidebar.deleteCategory('legacy');
|
|
|
|
|
sidebar.openCategory('open');
|
|
|
|
|
});
|
2024-02-29 01:37:15 +08:00
|
|
|
|
});
|
|
|
|
|
let run = () => {
|
|
|
|
|
let $div = $(content);
|
|
|
|
|
$div.empty();
|
2024-03-27 21:42:13 +08:00
|
|
|
|
spinner.spin();
|
2024-03-27 21:15:31 +08:00
|
|
|
|
$(start).prop('disabled', 'disabled').blur();
|
2024-03-01 01:36:17 +08:00
|
|
|
|
APP.module.execCommand('GET_LEGACY', {}, contentByHash => {
|
2024-03-27 21:11:56 +08:00
|
|
|
|
$(start).prop('disabled', false);
|
2024-03-27 21:42:13 +08:00
|
|
|
|
spinner.done();
|
2024-03-01 01:36:17 +08:00
|
|
|
|
// group by ticket id
|
|
|
|
|
let sorted = sortLegacyTickets(contentByHash);
|
|
|
|
|
sorted.forEach(ticket => {
|
|
|
|
|
if (!Array.isArray(ticket) || !ticket.length) { return; }
|
|
|
|
|
ticket.forEach(content => {
|
|
|
|
|
var id = content.id;
|
|
|
|
|
var $ticket = $div.find('.cp-support-list-ticket[data-id="'+id+'"]');
|
|
|
|
|
|
|
|
|
|
if (!content.message) {
|
|
|
|
|
// A ticket has been closed by the admins...
|
|
|
|
|
if (!$ticket.length) { return; }
|
|
|
|
|
$ticket.hide();
|
|
|
|
|
$ticket.append(APP.support.makeCloseMessage(content));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$ticket.show();
|
|
|
|
|
|
|
|
|
|
const onMove = function () {
|
|
|
|
|
let hashes = [];
|
|
|
|
|
let messages = [];
|
|
|
|
|
ticket.forEach(content => {
|
|
|
|
|
hashes.push(content.hash);
|
|
|
|
|
let clone = Util.clone(content);
|
|
|
|
|
delete clone.hash;
|
|
|
|
|
messages.push(clone);
|
|
|
|
|
});
|
|
|
|
|
APP.module.execCommand('RESTORE_LEGACY', {
|
|
|
|
|
messages, hashes
|
|
|
|
|
}, obj => {
|
|
|
|
|
if (obj && obj.error) {
|
|
|
|
|
console.error(obj.error);
|
|
|
|
|
return void UI.warn(Messages.error);
|
|
|
|
|
}
|
2024-03-27 20:03:06 +08:00
|
|
|
|
$ticket.remove();
|
2024-03-01 01:36:17 +08:00
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
if (!$ticket.length) {
|
2024-03-06 22:39:22 +08:00
|
|
|
|
content.category = 'legacy'; // Hide invalid features
|
2024-03-27 20:03:06 +08:00
|
|
|
|
$ticket = $(APP.support.makeTicket({id, content, onMove}));
|
2024-03-01 01:36:17 +08:00
|
|
|
|
$div.append($ticket);
|
|
|
|
|
}
|
|
|
|
|
$ticket.append(APP.support.makeMessage(content));
|
|
|
|
|
});
|
|
|
|
|
});
|
2024-02-29 01:37:15 +08:00
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
Util.onClickEnter($(start), run);
|
|
|
|
|
|
2024-03-01 01:36:17 +08:00
|
|
|
|
|
|
|
|
|
|
2024-02-29 01:37:15 +08:00
|
|
|
|
let div = blocks.form([content], nav);
|
|
|
|
|
cb(div);
|
|
|
|
|
});
|
|
|
|
|
|
2024-02-22 00:31:07 +08:00
|
|
|
|
sidebar.makeLeftside(categories);
|
2024-02-12 21:30:33 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var createToolbar = function () {
|
|
|
|
|
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications'];
|
|
|
|
|
var configTb = {
|
|
|
|
|
displayed: displayed,
|
|
|
|
|
sfCommon: common,
|
|
|
|
|
$container: APP.$toolbar,
|
2024-03-19 22:36:57 +08:00
|
|
|
|
pageTitle: Messages.moderationPage,
|
2024-02-12 21:30:33 +08:00
|
|
|
|
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) {
|
2024-02-22 00:31:07 +08:00
|
|
|
|
APP.$container = $('#cp-sidebarlayout-container');
|
2024-02-12 21:30:33 +08:00
|
|
|
|
APP.$toolbar = $('#cp-toolbar');
|
2024-02-22 00:31:07 +08:00
|
|
|
|
sframeChan = common.getSframeChannel();
|
|
|
|
|
sframeChan.onReady(waitFor());
|
2024-02-22 01:52:12 +08:00
|
|
|
|
}).nThen(function (waitFor) {
|
|
|
|
|
common.getAttribute(['general', 'disableSupportNotif'], waitFor(function (err, value) {
|
|
|
|
|
APP.disableSupportNotif = !!value;
|
|
|
|
|
}));
|
2024-02-12 21:30:33 +08:00
|
|
|
|
}).nThen(function (/*waitFor*/) {
|
|
|
|
|
createToolbar();
|
|
|
|
|
var metadataMgr = common.getMetadataMgr();
|
|
|
|
|
var privateData = metadataMgr.getPrivateData();
|
2024-03-19 22:36:57 +08:00
|
|
|
|
common.setTabTitle(Messages.moderationPage);
|
2024-02-12 21:30:33 +08:00
|
|
|
|
|
2024-02-27 23:52:30 +08:00
|
|
|
|
if (!ApiConfig.supportMailboxKey) {
|
|
|
|
|
return void UI.errorLoadingScreen(Messages.support_disabledTitle);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-12 21:30:33 +08:00
|
|
|
|
APP.privateKey = privateData.supportPrivateKey;
|
|
|
|
|
APP.origin = privateData.origin;
|
|
|
|
|
APP.readOnly = privateData.readOnly;
|
2024-02-20 21:00:09 +08:00
|
|
|
|
APP.module = common.makeUniversal('support', {
|
|
|
|
|
onEvent: (obj) => {
|
|
|
|
|
let cmd = obj.ev;
|
|
|
|
|
let data = obj.data;
|
|
|
|
|
if (!events[cmd]) { return; }
|
|
|
|
|
events[cmd].fire(data);
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-02-12 21:30:33 +08:00
|
|
|
|
APP.support = Support.create(common, true);
|
|
|
|
|
|
2024-02-20 21:00:09 +08:00
|
|
|
|
let active = privateData.category || 'active';
|
|
|
|
|
let linkedTicket;
|
|
|
|
|
if (active.indexOf('-') !== -1) {
|
2024-03-28 23:11:48 +08:00
|
|
|
|
linkedTicket = active.slice(active.indexOf('-')+1);
|
2024-02-20 21:00:09 +08:00
|
|
|
|
active = active.split('-')[0];
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-22 00:31:07 +08:00
|
|
|
|
andThen(common, APP.$container, linkedTicket);
|
2024-02-12 21:30:33 +08:00
|
|
|
|
UI.removeLoadingScreen();
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
});
|