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' ,
2024-02-21 16:31:07 +00:00
'/common/inner/sidebar-layout.js' ,
2024-02-12 13:30:33 +00:00
'/support/ui.js' ,
2024-02-29 17:36:17 +00:00
'/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 ,
2024-02-21 16:31:07 +00:00
Sidebar ,
2024-02-27 16:17:45 +00:00
Support
2024-02-12 13:30:33 +00:00
)
{
2024-02-21 16:31:07 +00:00
var APP = { } ;
2024-02-29 17:36:17 +00:00
var saveAs = window . saveAs ;
2024-02-12 13:30:33 +00:00
var common ;
2024-02-21 16:31:07 +00:00
var sframeChan ;
2024-02-20 13:00:09 +00:00
var events = {
2024-03-01 15:34:27 +00:00
NEW _TICKET : Util . mkEvent ( ) ,
UPDATE _TICKET : Util . mkEvent ( ) ,
UPDATE _RIGHTS : Util . mkEvent ( ) ,
RECORDED _CHANGE : Util . mkEvent ( )
2024-02-20 13:00:09 +00:00
} ;
2024-02-12 13:30:33 +00:00
2024-02-21 16:31:07 +00:00
// XXX
2024-03-01 10:15:29 +00:00
Messages . support _pending = "Pending tickets:" ;
/ *
2024-02-21 16:31:07 +00:00
Messages . support _activeListTitle = "Active tickets" ;
Messages . support _pendingListTitle = "Pending tickets" ;
2024-03-01 10:15:29 +00:00
Messages . support _activeListHint = "List of tickets that are in an active state" ;
2024-02-21 16:31:07 +00:00
Messages . support _pendingListHint = "List of tickets that may not be updated for a while but should not be closed" ;
2024-03-01 10:15:29 +00:00
* /
2024-02-20 16:50:41 +00:00
2024-02-21 16:31:07 +00:00
Messages . support _privacyTitle = "Answer anonymously" ;
2024-02-21 17:52:12 +00:00
Messages . support _privacyHint = "Check this option to reply as 'The Support Team' instead of your own username" ;
Messages . support _notificationsTitle = "Disable notifications" ;
Messages . support _notificationsHint = "Check this option to disable notifications on new or updated ticket" ;
2024-02-15 17:37:08 +00:00
2024-02-28 16:25:32 +00:00
Messages . support _openTicketTitle = "Open a ticket for a user" ;
Messages . support _openTicketHint = "Create a ticket for a user. They will receive a CryptPad notification to warn them. You can copy their user data from an existing support ticket, using the Copy button in the user data." ;
Messages . support _userChannel = "User's notifications channel ID" ;
Messages . support _userKey = "User's curvePublic key" ;
Messages . support _invalChan = "Invalid notifications channel" ;
Messages . support _pasteUserData = "Paste user data here" ;
2024-02-21 16:31:07 +00:00
var andThen = function ( common , $container , linkedTicket ) {
const sidebar = Sidebar . create ( common , 'support' , $container ) ;
2024-02-28 16:25:32 +00:00
const blocks = sidebar . blocks ;
2024-03-01 15:34:27 +00:00
APP . recorded = { } ;
APP . openTicketCategory = Util . mkEvent ( ) ;
2024-02-15 17:37:08 +00:00
2024-02-21 16:31:07 +00:00
// Support panel functions
2024-02-20 13:00:09 +00:00
let open = [ ] ;
2024-02-27 16:17:45 +00:00
let refreshAll = function ( ) { } ;
2024-03-04 12:48:56 +00:00
let allTags = [ ] ;
2024-03-01 11:21:30 +00:00
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 ) {
2024-03-01 11:21:30 +00:00
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 ( ) ;
2024-02-20 13:00:09 +00: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-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' ) ,
] ) ) ;
2024-03-01 10:15:29 +00:00
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 ;
}
2024-03-01 10:15:29 +00:00
if ( type === 'pending' ) {
// Only one column
col1 = col2 = col3 = col5 ;
}
2024-02-15 17:37:08 +00:00
$container . append ( [ col1 , col2 , col3 ] ) ;
var sortTicket = function ( c1 , c2 ) {
return tickets [ c2 ] . time - tickets [ c1 ] . time ;
} ;
2024-02-12 13:30:33 +00:00
2024-02-20 13:00:09 +00:00
const onShow = function ( ticket , channel , data , done ) {
2024-02-15 17:37:08 +00:00
APP . module . execCommand ( 'LOAD_TICKET_ADMIN' , {
channel : channel ,
2024-02-27 15:52:30 +00:00
curvePublic : data . authorKey ,
supportKey : data . supportKey
2024-02-15 17:37:08 +00:00
} , function ( obj ) {
if ( ! Array . isArray ( obj ) ) {
console . error ( obj && obj . error ) ;
2024-02-20 13:00:09 +00:00
done ( ) ;
2024-02-15 17:37:08 +00:00
return void UI . warn ( Messages . error ) ;
}
2024-02-20 16:50:41 +00:00
var $ticket = $ ( ticket ) ;
2024-02-15 17:37:08 +00:00
obj . forEach ( function ( msg ) {
2024-02-29 17:36:17 +00:00
// Only add notifications channel if this is coming from the other user
if ( ! data . notifications && msg . sender . drive ) {
2024-02-19 14:20:50 +00:00
data . notifications = Util . find ( msg , [ 'sender' , 'notifications' ] ) ;
}
2024-02-20 16:50:41 +00:00
if ( msg . close ) {
$ticket . addClass ( 'cp-support-list-closed' ) ;
return $ticket . append ( APP . support . makeCloseMessage ( msg ) ) ;
}
2024-02-29 17:36:17 +00:00
if ( msg . legacy && msg . messages ) {
msg . messages . forEach ( c => {
$ticket . append ( APP . support . makeMessage ( c ) ) ;
} ) ;
return ;
}
2024-02-20 16:50:41 +00:00
$ticket . append ( APP . support . makeMessage ( msg ) ) ;
2024-02-15 17:37:08 +00:00
} ) ;
2024-02-20 13:00:09 +00:00
if ( ! open . includes ( channel ) ) { open . push ( channel ) ; }
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
} ) ;
2024-02-20 13:00:09 +00:00
done ( ) ;
2024-02-15 17:37:08 +00:00
} ;
2024-02-27 16:17:45 +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 ,
2024-02-19 14:20:50 +00:00
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 ( ) ;
2024-02-20 13:00:09 +00:00
$ ( 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
} ) ;
} ;
2024-02-28 16:25:32 +00:00
const onMove = function ( ticket , channel ) {
2024-02-28 11:37:00 +00: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 ( ) ;
} ) ;
} ;
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 ) ;
}
( tags || [ ] ) . forEach ( tag => {
if ( ! allTags . includes ( tag ) ) { allTags . push ( tag ) ; }
} ) ;
//UI.log(Messags.saved);
//refreshAll();
} ) ;
} ;
onTag . getAllTags = ( ) => {
return allTags || [ ] ;
} ;
2024-03-01 11:21:30 +00:00
// Show tickets, reload the previously open ones and cal back
// once everything is loaded
let n = nThen ;
2024-02-15 17:37:08 +00:00
Object . keys ( tickets ) . sort ( sortTicket ) . 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 => {
if ( ! allTags . includes ( tag ) ) { allTags . push ( tag ) ; }
} ) ;
// Make ticket
2024-02-20 13:00:09 +00:00
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-20 13:00:09 +00:00
} ) ;
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 ) ;
2024-02-20 13:00:09 +00:00
2024-03-01 11:21:30 +00:00
if ( open . includes ( channel ) ) {
n = n ( waitFor => {
ticket . open ( true , waitFor ( ) ) ;
} ) . nThen ;
2024-02-20 13:00:09 +00:00
}
2024-02-15 17:37:08 +00:00
} ) ;
2024-03-01 11:21:30 +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-02-21 16:31:07 +00:00
let activeContainer , pendingContainer , closedContainer ;
2024-02-27 16:17:45 +00:00
refreshAll = function ( ) {
2024-03-01 11:21:30 +00:00
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 12:48:56 +00:00
allTags = [ ] ;
2024-03-01 11:21:30 +00:00
refresh ( $ ( activeContainer ) , 'active' , waitFor ( ) ) ;
refresh ( $ ( pendingContainer ) , 'pending' , waitFor ( ) ) ;
refresh ( $ ( closedContainer ) , 'closed' , waitFor ( ) ) ;
} ) . nThen ( waitFor => {
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
} ;
2024-02-21 17:52:12 +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-02-20 16:50:41 +00:00
2024-02-21 16:31:07 +00:00
// Make sidebar layout
const categories = {
'open' : {
icon : undefined ,
content : [
'privacy' ,
'active-list' ,
'pending-list' ,
]
} ,
'closed' : {
icon : undefined ,
content : [
'closed-list'
]
} ,
2024-02-21 17:52:12 +00:00
'settings' : {
icon : undefined ,
content : [
2024-03-01 15:34:27 +00:00
'notifications' ,
'recorded'
2024-02-21 17:52:12 +00:00
]
} ,
2024-02-28 16:25:32 +00:00
'ticket' : {
icon : undefined ,
content : [
'open-ticket'
2024-03-01 15:34:27 +00:00
] ,
onOpen : ( ) => {
APP . openTicketCategory . fire ( ) ;
}
2024-02-28 16:25:32 +00:00
} ,
2024-02-28 17:37:15 +00:00
'legacy' : {
icon : undefined ,
content : [
'legacy'
]
} ,
2024-02-21 16:31:07 +00:00
'refresh' : {
icon : undefined ,
2024-03-01 15:34:27 +00:00
onClick : ( ) => { refreshAll ( ) ; }
2024-02-21 16:31:07 +00:00
}
} ;
2024-03-01 15:34:27 +00:00
if ( ! APP . privateKey ) { delete categories . legacy ; }
2024-02-21 16:31:07 +00:00
sidebar . addCheckboxItem ( {
key : 'privacy' ,
getState : ( ) => false ,
query : ( val , setState ) => {
APP . support . setAnonymous ( val ) ;
setState ( val ) ;
}
} ) ;
sidebar . addItem ( 'active-list' , cb => {
2024-02-27 16:17:45 +00:00
activeContainer = h ( 'div.cp-support-container' ) ; // XXX block
cb ( activeContainer ) ;
2024-03-01 10:15:29 +00:00
} , { noTitle : true , noHint : true } ) ;
2024-02-21 16:31:07 +00:00
sidebar . addItem ( 'pending-list' , cb => {
2024-02-27 16:17:45 +00:00
pendingContainer = h ( 'div.cp-support-container' ) ;
cb ( pendingContainer ) ;
2024-03-01 10:15:29 +00:00
} , { noTitle : true , noHint : true } ) ;
2024-02-21 16:31:07 +00:00
sidebar . addItem ( 'closed-list' , cb => {
2024-02-27 16:17:45 +00:00
closedContainer = h ( 'div.cp-support-container' ) ;
cb ( closedContainer ) ;
2024-02-21 16:31:07 +00:00
} , { noTitle : true , noHint : true } ) ;
2024-02-20 16:50:41 +00:00
refreshAll ( ) ;
2024-02-21 16:31:07 +00:00
2024-02-21 17:52:12 +00: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-01 15:34:27 +00:00
Messages . support _recordedTitle = "Prerecorded messages" ;
Messages . support _recordedHint = "You can store prerecorded message in order to insert them with one click in a support ticket." ;
Messages . support _recordedList = "List of recorded messages" ;
Messages . support _recordedEmpty = "No recorded messages" ;
Messages . support _recordedId = "Unique Identifier" ;
Messages . support _recordedContent = "Content" ;
sidebar . addItem ( 'recorded' , cb => {
let empty = blocks . text ( Messages . support _recordedEmpty ) ;
let list = blocks . list ( [
Messages . support _recordedId ,
Messages . support _recordedContent ,
Messages . kanban _delete
] , [ ] ) ;
let labelledList = blocks . labelledInput ( Messages . support _recordedList , list ) ;
let inputId = blocks . input ( { type : 'text' , maxlength : 20 } ) ;
let inputContent = blocks . textArea ( ) ;
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 ,
labelledList ,
labelId ,
labelContent ,
] , nav ) ;
let $empty = $ ( empty ) ;
let $list = $ ( labelledList ) . hide ( ) ;
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 lines = [ ] ;
let messages = obj . messages ;
Object . keys ( messages ) . forEach ( id => {
let del = blocks . button ( 'danger-alt' , 'fa-trash-o' , Messages . kanban _delete ) ;
Util . onClickEnter ( $ ( del ) , ( ) => {
edit ( id , '' , true ) ;
} ) ;
lines . push ( [ id , h ( 'pre' , messages [ id ] . content ) , del ] ) ;
} ) ;
list . updateContent ( lines ) ;
if ( ! lines . length ) {
$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-02-28 16:25:32 +00:00
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 ) ;
2024-02-28 16:25:32 +00: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 11:21:30 +00:00
let reset = blocks . button ( 'danger-alt' , 'fa-times' , Messages . form _reset ) ;
2024-02-28 16:25:32 +00:00
let paste = blocks . textArea ( {
placeholder : Messages . support _pasteUserData
} ) ;
let inputs = h ( 'div.cp-moderation-userdata-inputs' , [ labelName , labelChan , labelKey ] ) ;
2024-03-01 11:21:30 +00:00
let userData = h ( 'div.cp-moderation-userdata' , [ inputs , paste , reset ] ) ;
2024-02-28 16:25:32 +00:00
2024-03-01 11:21:30 +00:00
let $reset = $ ( reset ) . hide ( ) ;
2024-02-28 16:25:32 +00: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 11:21:30 +00:00
$reset . show ( ) ;
} ) ;
Util . onClickEnter ( $reset , ( ) => {
$ ( inputName ) . val ( '' ) ;
$ ( inputChan ) . val ( '' ) ;
$ ( inputKey ) . val ( '' ) ;
$reset . hide ( ) ;
$paste . show ( ) ;
setTimeout ( ( ) => { $paste . focus ( ) } ) ;
2024-02-28 16:25:32 +00: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-02-28 17:37:15 +00:00
// XXX
Messages . support _legacyTitle = "View old support data" ;
Messages . support _legacyHint = "View tickets from the legacy system. You'll be able to recreate thse tickets on the new support system." ;
2024-02-29 17:36:17 +00:00
Messages . support _legacyButton = "Get active" ;
Messages . support _legacyDump = "Export all" ;
2024-02-28 17:37:15 +00:00
Messages . support _legacyClear = "Delete from my account" ;
sidebar . addItem ( 'legacy' , cb => {
if ( ! APP . privateKey ) { return void cb ( false ) ; }
let start = blocks . button ( 'primary' , 'fa-paper-plane' , Messages . support _legacyButton ) ;
2024-02-29 17:36:17 +00:00
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' ) ;
2024-02-29 17:36:17 +00:00
let nav = blocks . nav ( [ start , dump , clean ] ) ;
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 ] ;
} ) ;
} ;
UI . confirmButton ( dump , { classes : 'btn-secondary' } , function ( ) {
APP . module . execCommand ( 'DUMP_LEGACY' , { } , contentByHash => {
// 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 ( ) {
2024-02-29 17:36:17 +00:00
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 ( ) ;
2024-02-29 17:36:17 +00:00
APP . module . execCommand ( 'GET_LEGACY' , { } , contentByHash => {
// 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 ) ;
}
} ) ;
} ;
if ( ! $ticket . length ) {
$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-29 17:36:17 +00:00
2024-02-28 17:37:15 +00:00
let div = blocks . form ( [ content ] , nav ) ;
cb ( div ) ;
} ) ;
2024-02-21 16:31:07 +00:00
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 . 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 ) {
2024-02-21 16:31:07 +00:00
APP . $container = $ ( '#cp-sidebarlayout-container' ) ;
2024-02-12 13:30:33 +00:00
APP . $toolbar = $ ( '#cp-toolbar' ) ;
2024-02-21 16:31:07 +00:00
sframeChan = common . getSframeChannel ( ) ;
sframeChan . onReady ( waitFor ( ) ) ;
2024-02-21 17:52:12 +00:00
} ) . 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 . supportPage ) ;
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 ;
2024-02-20 13:00:09 +00: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 13:30:33 +00:00
APP . support = Support . create ( common , true ) ;
2024-02-20 13:00:09 +00:00
let active = privateData . category || 'active' ;
let linkedTicket ;
if ( active . indexOf ( '-' ) !== - 1 ) {
linkedTicket = active . split ( '-' ) [ 1 ] ;
active = active . split ( '-' ) [ 0 ] ;
}
2024-02-21 16:31:07 +00:00
andThen ( common , APP . $container , linkedTicket ) ;
2024-02-12 13:30:33 +00:00
UI . removeLoadingScreen ( ) ;
} ) ;
} ) ;