cryptpad/www/moderation/inner.js

1004 lines
40 KiB
JavaScript
Raw Normal View History

2024-02-12 13:30:33 +00: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',
'/common/inner/sidebar-layout.js',
2024-02-12 13:30:33 +00:00
'/support/ui.js',
'/components/file-saver/FileSaver.min.js',
2024-02-12 13:30:33 +00: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,
Sidebar,
Support
2024-02-12 13:30:33 +00:00
)
{
var APP = {};
var saveAs = window.saveAs;
2024-02-12 13:30:33 +00:00
var common;
var sframeChan;
var events = {
2024-03-01 15:34:27 +00:00
NEW_TICKET: Util.mkEvent(),
UPDATE_TICKET: Util.mkEvent(),
UPDATE_RIGHTS: Util.mkEvent(),
2024-03-04 17:14:59 +00:00
RECORDED_CHANGE: Util.mkEvent(),
REFRESH_FILTER: Util.mkEvent(),
REFRESH_TAGS: Util.mkEvent()
};
2024-02-12 13:30:33 +00:00
var andThen = function (common, $container, linkedTicket) {
const sidebar = Sidebar.create(common, 'support', $container);
const blocks = sidebar.blocks;
2024-03-01 15:34:27 +00:00
APP.recorded = {};
2024-03-04 17:14:59 +00:00
APP.allTags = [];
2024-03-01 15:34:27 +00:00
APP.openTicketCategory = Util.mkEvent();
2024-02-15 17:37:08 +00: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);
});
};
// Support panel functions
let open = [];
let refreshAll = function () {
APP.$refreshButton.prop('disabled', false);
};
let refresh = ($container, type, _cb) => {
let cb = Util.mkAsync(_cb || function () {});
2024-02-20 16:50:41 +00:00
APP.module.execCommand('LIST_TICKETS_ADMIN', {
type: type
}, (tickets) => {
2024-02-26 18:07:39 +00:00
if (tickets.error) {
cb();
2024-02-26 18:07:39 +00:00
if (tickets.error === 'EFORBIDDEN') {
return void UI.errorLoadingScreen(Messages.admin_authError || '403 Forbidden');
}
return void UI.errorLoadingScreen(tickets.error);
}
2024-03-01 15:34:27 +00:00
open = open.filter(chan => {
// Remove deleted tickets from memory
return tickets[chan];
});
2024-02-26 18:07:39 +00:00
UI.removeLoadingScreen();
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-15 17:37:08 +00: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-20 16:50:41 +00:00
var col4 = h('div.cp-support-column', h('h1', [
h('span', Messages.admin_support_closed),
h('span.cp-support-count'),
]));
var col5 = h('div.cp-support-column', h('h1', [
h('span', Messages.support_pending),
h('span.cp-support-count'),
]));
2024-02-20 16:50:41 +00:00
if (type === 'closed') {
// Only one column
col1 = col2 = col3 = col4;
}
if (type === 'pending') {
// Only one column
col1 = col2 = col3 = col5;
}
2024-02-15 17:37:08 +00:00
$container.append([col1, col2, col3]);
2024-02-12 13:30:33 +00:00
const onShow = function (ticket, channel, data, done) {
onShowTicket(ticket, channel, data, (success) => {
if (success) {
if (!open.includes(channel)) { open.push(channel); }
2024-02-15 17:37:08 +00: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-15 17:37:08 +00:00
});
done();
2024-02-15 17:37:08 +00:00
};
const onReply = function (ticket, channel, data, form) {
2024-02-15 17:37:08 +00:00
var formData = APP.support.getFormData(form);
APP.module.execCommand('REPLY_TICKET_ADMIN', {
channel: channel,
curvePublic: data.authorKey,
notifChannel: data.notifications,
2024-02-27 15:52:30 +00:00
supportKey: data.supportKey,
2024-02-15 17:37:08 +00: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();
$(ticket).find('.cp-support-form-container').remove();
2024-02-20 16:50:41 +00: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 15:52:30 +00:00
supportKey: data.supportKey,
2024-02-20 16:50:41 +00: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-15 17:37:08 +00:00
});
};
const onMove = function (ticket, channel) {
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 14:39:22 +00:00
onMove.disableMove = type === 'closed';
onMove.isTicketActive = type === 'active';
2024-02-12 13:30:33 +00:00
2024-03-04 12:48:56 +00: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-20 16:40:20 +00:00
if (obj.allTags) { APP.allTags = obj.allTags; }
2024-03-04 17:14:59 +00:00
events.REFRESH_TAGS.fire();
2024-03-04 12:48:56 +00:00
});
};
onTag.getAllTags = () => {
2024-03-04 17:14:59 +00:00
return APP.allTags || [];
2024-03-04 12:48:56 +00:00
};
// Show tickets, reload the previously open ones and cal back
// once everything is loaded
let n = nThen;
Object.keys(tickets).sort(sortTicket(tickets)).forEach(function (channel) {
2024-03-04 12:48:56 +00:00
// Update allTags
2024-02-15 17:37:08 +00:00
var d = tickets[channel];
2024-03-04 12:48:56 +00:00
(d.tags || []).forEach(tag => {
2024-03-04 17:14:59 +00:00
if (!APP.allTags.includes(tag)) { APP.allTags.push(tag); }
2024-03-04 12:48:56 +00:00
});
// Make ticket
var ticket = APP.support.makeTicket({
id: channel,
content: d,
form: activeForms[channel],
2024-03-01 15:34:27 +00:00
recorded: APP.recorded,
2024-03-04 12:48:56 +00:00
onShow, onHide, onClose, onReply, onMove, onTag
});
2024-02-15 17:37:08 +00:00
var container;
if (d.lastAdmin) { container = col3; }
else if (d.premium) { container = col1; }
else { container = col2; }
$(container).append(ticket);
if (open.includes(channel)) {
n = n(waitFor => {
ticket.open(true, waitFor());
}).nThen;
}
2024-02-15 17:37:08 +00:00
});
// Wait for all open tickets to be loaded before calling back
// otherwise we may have a wrong scroll position
n(() => {
cb();
});
2024-02-15 17:37:08 +00:00
});
};
2024-03-04 17:14:59 +00: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);
});
});
};
let activeContainer, pendingContainer, closedContainer;
refreshAll = function () {
let $rightside = sidebar.$rightside;
let s = $rightside.scrollTop();
nThen(waitFor => {
2024-03-01 15:34:27 +00: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-04 17:14:59 +00:00
APP.allTags = [];
refresh($(activeContainer), 'active', waitFor());
refresh($(pendingContainer), 'pending', waitFor());
refresh($(closedContainer), 'closed', waitFor());
2024-03-04 17:14:59 +00:00
}).nThen(() => {
onFilter();
events.REFRESH_TAGS.fire();
}).nThen(waitFor => {
APP.$refreshButton.prop('disabled', false);
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-20 16:50:41 +00:00
};
let _refresh = Util.throttle(refreshAll, 500);
events.NEW_TICKET.reg(_refresh);
events.UPDATE_TICKET.reg(_refresh);
2024-03-01 15:34:27 +00:00
events.RECORDED_CHANGE.reg(_refresh);
2024-02-26 18:07:39 +00:00
events.UPDATE_RIGHTS.reg(_refresh);
2024-03-04 17:14:59 +00:00
events.REFRESH_FILTER.reg(onFilter);
2024-02-20 16:50:41 +00:00
// Make sidebar layout
const categories = {
'open': { // Msg.support_cat_open
2024-03-20 16:40:20 +00:00
icon: 'fa fa-inbox',
content: [
2024-03-20 16:40:20 +00:00
'refresh',
2024-03-04 17:14:59 +00:00
'filter',
'active-list',
'pending-list',
]
},
'closed': { // Msg.support_cat_closed
2024-03-20 16:40:20 +00:00
icon: 'fa fa-archive',
content: [
2024-03-20 16:40:20 +00:00
'refresh',
2024-03-04 17:14:59 +00:00
'filter',
'closed-list'
]
},
'search': { // Msg.support_cat_search
2024-03-20 16:40:20 +00:00
icon: 'fa fa-search',
content: [
'filter',
'search'
],
onOpen: () => {
APP.searchAutoRefresh = true;
setTimeout(() => {
$('.cp-support-search-input').focus();
});
}
},
'new': { // Msg.support_cat_new
2024-03-20 16:40:20 +00:00
icon: 'fa fa-envelope',
content: [
'open-ticket'
2024-03-01 15:34:27 +00:00
],
onOpen: () => {
APP.openTicketCategory.fire();
setTimeout(() => {
$('.cp-support-newticket-paste').focus();
});
2024-03-01 15:34:27 +00:00
}
},
'legacy': { // Msg.support_cat_legacy
2024-03-20 16:40:20 +00:00
icon: 'fa fa-server',
2024-02-28 17:37:15 +00:00
content: [
'legacy'
]
},
'settings': { // Msg.support_cat_settings
2024-03-20 16:40:20 +00:00
icon: 'fa fa-cogs',
content: [
'privacy',
'notifications',
'recorded'
],
onOpen: () => {
setTimeout(() => {
2024-03-21 13:30:13 +00:00
$('.cp-moderation-recorded-id').focus();
2024-03-20 16:40:20 +00:00
});
}
},
};
2024-03-01 15:34:27 +00:00
if (!APP.privateKey) { delete categories.legacy; }
2024-03-20 16:40:20 +00:00
sidebar.addItem('refresh', cb => {
let button = blocks.button('secondary', 'fa-refresh', Messages.oo_refresh);
APP.$refreshButton = $(button);
2024-03-20 16:40:20 +00:00
Util.onClickEnter($(button), () => {
APP.$refreshButton.prop('disabled', 'disabled');
2024-03-20 16:40:20 +00:00
refreshAll();
});
let content = blocks.block([button]);
cb(content);
}, { noTitle: true, noHint: true });
// Msg.support_privacyHint.support_privacyTitle
sidebar.addCheckboxItem({
key: 'privacy',
getState: () => false,
query: (val, setState) => {
APP.support.setAnonymous(val);
setState(val);
}
});
sidebar.addItem('active-list', cb => {
activeContainer = h('div.cp-support-container'); // XXX block
cb(activeContainer);
}, { noTitle: true, noHint: true });
sidebar.addItem('pending-list', cb => {
pendingContainer = h('div.cp-support-container');
cb(pendingContainer);
}, { noTitle: true, noHint: true });
sidebar.addItem('closed-list', cb => {
closedContainer = h('div.cp-support-container');
cb(closedContainer);
}, { noTitle: true, noHint: true });
2024-02-20 16:50:41 +00:00
refreshAll();
// Msg.support_notificationsHint.support_notificationsTitle.support_notificationsLabel
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);
});
}
});
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 15:51:02 +00:00
let list = blocks.block([], 'cp-support-container');
let container = blocks.block([searchBlock, list], 'cp-support-search-container');
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-04 17:14:59 +00:00
sidebar.addItem('filter', cb => {
2024-03-15 16:00:31 +00:00
let container = blocks.block([], 'cp-support-filter-container');
2024-03-04 17:14:59 +00: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-20 16:40:20 +00: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-04 17:14:59 +00: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 });
// Msg.support_recordedHint.support_recordedTitle
2024-03-01 15:34:27 +00:00
sidebar.addItem('recorded', cb => {
2024-03-15 15:51:02 +00:00
let empty = blocks.inline(Messages.support_recordedEmpty);
2024-03-21 13:30:13 +00:00
let list = blocks.block([], 'cp-moderation-recorded-list');
let inputId = blocks.input({type:'text', class: 'cp-moderation-recorded-id',
maxlength: 20 });
2024-03-15 15:51:02 +00:00
let inputContent = blocks.textarea();
2024-03-01 15:34:27 +00: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 13:30:13 +00:00
list,
2024-03-01 15:34:27 +00:00
labelId,
labelContent,
], nav);
let $empty = $(empty);
2024-03-21 13:30:13 +00:00
let $list = $(list).hide();
2024-03-01 15:34:27 +00: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 13:30:13 +00:00
$list.empty();
2024-03-01 15:34:27 +00: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 13:30:13 +00: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 15:34:27 +00:00
});
2024-03-21 13:30:13 +00:00
if (!Object.keys(messages).length) {
2024-03-01 15:34:27 +00: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);
});
// Msg.support_openTicketHint.support_openTicketTitle
sidebar.addItem('open-ticket', cb => {
2024-03-01 15:34:27 +00: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);
let inputName = blocks.input({type: 'text', readonly: true});
let inputChan = blocks.input({type: 'text', readonly: true});
let inputKey = blocks.input({type: 'text', readonly: true});
let labelName = blocks.labelledInput(Messages.login_username, inputName);
let labelChan = blocks.labelledInput(Messages.support_userChannel, inputChan);
let labelKey = blocks.labelledInput(Messages.support_userKey, inputKey);
let send = blocks.button('primary', 'fa-paper-plane', Messages.support_formButton);
let nav = blocks.nav([send]);
let reset = blocks.button('danger-alt', 'fa-times', Messages.form_reset);
2024-03-15 15:51:02 +00:00
let paste = blocks.textarea({
class: 'cp-support-newticket-paste',
placeholder: Messages.support_pasteUserData
});
let inputs = h('div.cp-moderation-userdata-inputs', [ labelName, labelChan, labelKey ]);
let userData = h('div.cp-moderation-userdata', [inputs , paste, reset]);
let $reset = $(reset).hide();
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();
$reset.show();
});
Util.onClickEnter($reset, () => {
$(inputName).val('');
$(inputChan).val('');
$(inputKey).val('');
$reset.hide();
$paste.show();
2024-03-04 17:14:59 +00:00
setTimeout(() => { $paste.focus(); });
});
[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);
});
// Msg.support_legacyHint.support_legacyTitle
2024-02-28 17:37:15 +00:00
sidebar.addItem('legacy', cb => {
if (!APP.privateKey) { return void cb(false); }
let start = blocks.button('primary', 'fa-paper-plane', Messages.support_legacyButton);
let dump = blocks.button('secondary', 'fa-database', Messages.support_legacyDump);
2024-02-28 17:37:15 +00:00
let clean = blocks.button('danger', 'fa-trash-o', Messages.support_legacyClear);
let content = h('div.cp-support-container');
let nav = blocks.nav([start, dump, clean]);
let spinner = UI.makeSpinner($(nav));
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];
});
};
let $dumpBtn = $(dump);
UI.confirmButton(dump, { classes: 'btn-secondary' }, function () {
spinner.spin();
$dumpBtn.prop('disabled', 'disabled').blur();
APP.module.execCommand('DUMP_LEGACY', {}, contentByHash => {
$dumpBtn.prop('disabled', false);
spinner.done();
// 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-28 17:37:15 +00:00
UI.confirmButton(clean, { classes: 'btn-danger' }, function () {
APP.module.execCommand('CLEAR_LEGACY', {}, () => {
delete APP.privateKey;
sidebar.deleteCategory('legacy');
sidebar.openCategory('open');
});
2024-02-28 17:37:15 +00:00
});
let run = () => {
let $div = $(content);
$div.empty();
spinner.spin();
$(start).prop('disabled', 'disabled').blur();
APP.module.execCommand('GET_LEGACY', {}, contentByHash => {
2024-03-27 13:11:56 +00:00
$(start).prop('disabled', false);
spinner.done();
// 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);
}
$ticket.remove();
});
};
if (!$ticket.length) {
2024-03-06 14:39:22 +00:00
content.category = 'legacy'; // Hide invalid features
$ticket = $(APP.support.makeTicket({id, content, onMove}));
$div.append($ticket);
}
$ticket.append(APP.support.makeMessage(content));
});
});
2024-02-28 17:37:15 +00:00
});
};
Util.onClickEnter($(start), run);
2024-02-28 17:37:15 +00:00
let div = blocks.form([content], nav);
cb(div);
});
sidebar.makeLeftside(categories);
2024-02-12 13:30:33 +00:00
};
var createToolbar = function () {
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications'];
var configTb = {
displayed: displayed,
sfCommon: common,
$container: APP.$toolbar,
pageTitle: Messages.moderationPage,
2024-02-12 13:30:33 +00: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) {
APP.$container = $('#cp-sidebarlayout-container');
2024-02-12 13:30:33 +00:00
APP.$toolbar = $('#cp-toolbar');
sframeChan = common.getSframeChannel();
sframeChan.onReady(waitFor());
}).nThen(function (waitFor) {
common.getAttribute(['general', 'disableSupportNotif'], waitFor(function (err, value) {
APP.disableSupportNotif = !!value;
}));
2024-02-12 13:30:33 +00:00
}).nThen(function (/*waitFor*/) {
createToolbar();
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
common.setTabTitle(Messages.moderationPage);
2024-02-12 13:30:33 +00:00
2024-02-27 15:52:30 +00:00
if (!ApiConfig.supportMailboxKey) {
return void UI.errorLoadingScreen(Messages.support_disabledTitle);
}
2024-02-12 13:30:33 +00:00
APP.privateKey = privateData.supportPrivateKey;
APP.origin = privateData.origin;
APP.readOnly = privateData.readOnly;
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 13:30:33 +00:00
APP.support = Support.create(common, true);
let active = privateData.category || 'active';
let linkedTicket;
if (active.indexOf('-') !== -1) {
linkedTicket = active.slice(active.indexOf('-')+1);
active = active.split('-')[0];
}
andThen(common, APP.$container, linkedTicket);
2024-02-12 13:30:33 +00:00
UI.removeLoadingScreen();
});
});