2020-01-15 08:47:33 +01:00
/ * *
* EGroupware clientside Application javascript base object
*
* @ license http : //opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @ package etemplate
* @ subpackage api
2021-06-07 17:33:53 +02:00
* @ link https : //www.egroupware.org
2020-01-15 08:47:33 +01:00
* @ author Ralf Becker < rb @ egroupware . org >
* @ author Hadi Nategh < hn @ groupware . org >
* @ author Nathan Gray < ng @ groupware . org >
* /
2021-06-05 20:39:39 +02:00
import { etemplate2 } from "../etemplate/etemplate2" ;
import { et2 _nextmatch } from "../etemplate/et2_extension_nextmatch" ;
import { et2 _dialog } from "../etemplate/et2_widget_dialog" ;
import { et2 _createWidget } from "../etemplate/et2_core_widget" ;
import { et2 _favorites } from "../etemplate/et2_widget_favorites" ;
2020-01-15 08:47:33 +01:00
/ * *
* Common base class for application javascript
* Each app should extend as needed .
*
* All application javascript should be inside . Intitialization goes in init ( ) ,
* clean - up code goes in destroy ( ) . Initialization is done once all js is loaded .
*
* var app . appname = AppJS . extend ( {
* // Actually set this one, the rest is example
* appname : appname ,
*
* internal _var : 1000 ,
*
* init : function ( )
* {
* // Call the super
* this . _super . apply ( this , arguments ) ;
*
* // Init the stuff
* if ( egw . preference ( 'dateformat' , 'common' ) )
* {
* // etc
* }
* } ,
* _private : function ( )
* {
* // Underscore private by convention
* }
* } ) ;
* /
2021-06-05 20:39:39 +02:00
export class EgwApp {
2020-01-15 08:47:33 +01:00
/ * *
* Initialization and setup goes here , but the etemplate2 object
* is not yet ready .
* /
2021-06-05 20:39:39 +02:00
constructor ( appname ) {
2021-03-09 15:19:27 +01:00
/ * *
* PGP begin and end tags
* /
this . begin _pgp _message = '-----BEGIN PGP MESSAGE-----' ;
this . end _pgp _message = '-----END PGP MESSAGE-----' ;
2020-01-15 08:47:33 +01:00
/ * *
* Mailvelope "egroupware" Keyring
* /
this . mailvelope _keyring = undefined ;
2021-03-09 15:19:27 +01:00
/ * *
* jQuery selector for Mailvelope iframes in all browsers
* /
this . mailvelope _iframe _selector = 'iframe[src^="chrome-extension"],iframe[src^="about:blank?mvelo"]' ;
2020-03-20 18:38:38 +01:00
this . appname = appname ;
2020-01-15 08:47:33 +01:00
this . egw = egw ( this . appname , window ) ;
// Initialize sidebox for non-popups.
// ID set server side
if ( ! this . egw . is _popup ( ) ) {
var sidebox = jQuery ( '#favorite_sidebox_' + this . appname ) ;
if ( sidebox . length == 0 && egw _getFramework ( ) != null ) {
var egw _fw = egw _getFramework ( ) ;
sidebox = jQuery ( '#favorite_sidebox_' + this . appname , egw _fw . sidemenuDiv ) ;
}
// Make sure we're running in the top window when we init sidebox
//@ts-ignore
2020-05-18 20:55:57 +02:00
if ( window . app [ this . appname ] === this && egw . top . app [ this . appname ] !== this && egw . top . app [ this . appname ] ) {
2020-01-15 08:47:33 +01:00
//@ts-ignore
2020-05-18 20:55:57 +02:00
egw . top . app [ this . appname ] . _init _sidebox ( sidebox ) ;
2020-01-15 08:47:33 +01:00
}
else {
this . _init _sidebox ( sidebox ) ;
}
}
this . mailvelopeSyncHandlerObj = this . mailvelopeSyncHandler ( ) ;
2020-04-21 23:36:17 +02:00
// Keep track of this instance
EgwApp . _register _instance ( this ) ;
2020-01-15 08:47:33 +01:00
}
/ * *
* Clean up any created objects & references
* @ param { object } _app local app object
* /
2021-06-05 20:39:39 +02:00
destroy ( _app ) {
2020-01-15 08:47:33 +01:00
delete this . et2 ;
if ( this . sidebox )
this . sidebox . off ( ) ;
delete this . sidebox ;
if ( ! _app )
delete app [ this . appname ] ;
2021-06-05 20:39:39 +02:00
let index = - 1 ;
2020-04-21 23:36:17 +02:00
if ( ( index = EgwApp . _instances . indexOf ( this ) ) >= 0 ) {
EgwApp . _instances . splice ( index , 1 ) ;
}
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* This function is called when the etemplate2 object is loaded
* and ready . If you must store a reference to the et2 object ,
* make sure to clean it up in destroy ( ) . Note that this can be called
* several times , with different et2 objects , as templates are loaded .
*
* @ param { etemplate2 } et2
* @ param { string } name template name
* /
2021-06-05 20:39:39 +02:00
et2 _ready ( et2 , name ) {
2020-01-15 08:47:33 +01:00
if ( this . et2 !== null ) {
egw . debug ( 'log' , "Changed et2 object" ) ;
}
this . et2 = et2 . widgetContainer ;
this . _fix _iFrameScrolling ( ) ;
2021-01-27 12:26:37 +01:00
if ( this . egw && this . egw . is _popup ( ) ) {
2020-01-15 08:47:33 +01:00
this . _set _Window _title ( ) ;
2021-01-27 12:26:37 +01:00
}
2020-01-15 08:47:33 +01:00
// Highlights the favorite based on initial list state
this . highlight _favorite ( ) ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Observer method receives update notifications from all applications
*
* App is responsible for only reacting to "messages" it is interested in !
*
* @ param { string } _msg message ( already translated ) to show , eg . 'Entry deleted'
* @ param { string } _app application name
* @ param { ( string | number ) } _id id of entry to refresh or null
* @ param { string } _type either 'update' , 'edit' , 'delete' , 'add' or null
* - update : request just modified data from given rows . Sorting is not considered ,
* so if the sort field is changed , the row will not be moved .
* - edit : rows changed , but sorting may be affected . Requires full reload .
* - delete : just delete the given rows clientside ( no server interaction neccessary )
* - add : requires full reload for proper sorting
* @ param { string } _msg _type 'error' , 'warning' or 'success' ( default )
* @ param { object | null } _links app => array of ids of linked entries
* or null , if not triggered on server - side , which adds that info
* @ return { false | * } false to stop regular refresh , thought all observers are run
* /
2021-06-05 20:39:39 +02:00
observer ( _msg , _app , _id , _type , _msg _type , _links ) {
}
2020-01-15 08:47:33 +01:00
/ * *
2020-01-23 19:03:29 +01:00
* Handle a push notification about entry changes from the websocket
2020-01-15 08:47:33 +01:00
*
2020-01-24 13:31:56 +01:00
* Get ' s called for data of all apps , but should only handle data of apps it displays ,
* which is by default only it ' s own , but can be for multiple apps eg . for calendar .
*
2020-01-23 19:03:29 +01:00
* @ param pushData
* @ param { string } pushData . app application name
* @ param { ( string | number ) } pushData . id id of entry to refresh or null
* @ param { string } pushData . type either 'update' , 'edit' , 'delete' , 'add' or null
2020-01-15 08:47:33 +01:00
* - update : request just modified data from given rows . Sorting is not considered ,
* so if the sort field is changed , the row will not be moved .
* - edit : rows changed , but sorting may be affected . Requires full reload .
* - delete : just delete the given rows clientside ( no server interaction neccessary )
* - add : requires full reload for proper sorting
2020-01-23 19:03:29 +01:00
* @ param { object | null } pushData . acl Extra data for determining relevance . eg : owner or responsible to decide if update is necessary
* @ param { number } pushData . account _id User that caused the notification
2020-01-15 08:47:33 +01:00
* /
2021-06-05 20:39:39 +02:00
push ( pushData ) {
2021-03-04 21:53:52 +01:00
var _a ;
2020-01-24 13:31:56 +01:00
// don't care about other apps data, reimplement if your app does care eg. calendar
if ( pushData . app !== this . appname )
return ;
2021-02-24 17:18:42 +01:00
// handle delete, for simple case of uid === "$app::$id"
2020-08-05 18:48:02 +02:00
if ( pushData . type === 'delete' && egw . dataHasUID ( this . uid ( pushData ) ) ) {
2020-08-05 18:26:40 +02:00
egw . refresh ( '' , pushData . app , pushData . id , 'delete' ) ;
2021-02-24 17:18:42 +01:00
return ;
}
// If we know about it and it's an update, just update.
// This must be before all ACL checks, as responsible might have changed and entry need to be removed
// (server responds then with null / no entry causing the entry to disappear)
2021-03-04 21:53:52 +01:00
if ( pushData . type !== "add" && this . egw . dataHasUID ( this . uid ( pushData ) ) && this . et2 ) {
2021-02-24 17:18:42 +01:00
return this . et2 . getInstanceManager ( ) . refresh ( "" , pushData . app , pushData . id , pushData . type ) ;
}
// Check grants to see if we know we aren't supposed to show it
if ( typeof this . push _grant _fields !== "undefined" && this . push _grant _fields . length > 0
&& ! this . _push _grant _check ( pushData , this . push _grant _fields ) ) {
return ;
}
// Nextmatch does the hard part of updating. Try to find one.
2021-06-05 20:39:39 +02:00
let nm = ( _a = this . et2 ) === null || _a === void 0 ? void 0 : _a . getDOMWidgetById ( 'nm' ) ;
2021-02-24 17:18:42 +01:00
if ( ! nm ) {
return ;
}
// Filter what's allowed down to those we can see / care about based on nm filters
if ( typeof this . push _filter _fields !== "undefined" && this . push _filter _fields . length > 0 &&
! this . _push _field _filter ( pushData , nm , this . push _filter _fields ) ) {
return ;
}
// Pass actual refresh on to just nextmatch
nm . refresh ( pushData . id , pushData . type ) ;
2021-06-05 20:39:39 +02:00
}
2021-02-24 17:18:42 +01:00
/ * *
* Check grants to see if we can quickly tell if this entry is not for us
*
* Override this method if the app has non - standard access control .
*
* @ param pushData
* @ param grant _fields List of fields in pushData . acl with account IDs that might grant access eg : info _responsible
2021-03-04 19:27:35 +01:00
* @ param appname Optional , to check against the grants for a different application . Defaults to this . appname .
*
* @ return boolean Entry has ACL access
2021-02-24 17:18:42 +01:00
* /
2021-06-05 20:39:39 +02:00
_push _grant _check ( pushData , grant _fields , appname ) {
let grants = egw . grants ( appname || this . appname ) ;
2021-02-24 22:28:17 +01:00
// No grants known
if ( ! grants )
return true ;
2021-06-05 20:39:39 +02:00
// check user has a grant from owner or something
for ( let i = 0 ; i < grant _fields . length ; i ++ ) {
let grant _field = pushData . acl [ grant _fields [ i ] ] ;
2021-02-24 22:28:17 +01:00
if ( [ "number" , "string" ] . indexOf ( typeof grant _field ) >= 0 && grants [ grant _field ] !== 'undefined' ) {
2021-06-05 20:39:39 +02:00
// ACL access
return true ;
2021-02-24 22:28:17 +01:00
}
else if ( ! Object . keys ( grants ) . filter ( function ( grant _account ) {
return grant _field . indexOf ( grant _account ) >= 0 ||
grant _field . indexOf ( parseInt ( grant _account ) ) . length ;
} ) ) {
2021-06-05 20:39:39 +02:00
return false ;
2021-02-24 22:28:17 +01:00
}
2021-02-24 17:18:42 +01:00
}
return false ;
2021-06-05 20:39:39 +02:00
}
2021-02-24 17:18:42 +01:00
/ * *
* Check pushData . acl values against a list of fields to see if we care about this entry based on current nextmatch
* filter values . This is not a definitive yes or no ( the server will tell us when we ask ) , we just want to cheaply
* avoid a server call if we know it won ' t be in the list .
*
* @ param pushData
* @ param filter _fields List of filter field names eg : [ owner , cat _id ]
* @ return boolean True if the nextmatch filters might include the entry , false if not
* /
2021-06-05 20:39:39 +02:00
_push _field _filter ( pushData , nm , filter _fields ) {
let filters = { } ;
for ( let i = 0 ; i < filter _fields . length ; i ++ ) {
2021-02-24 17:18:42 +01:00
filters [ filter _fields [ i ] ] = {
col : filter _fields [ i ] ,
filter _values : [ ]
} ;
}
// Get current filter values
2021-06-05 20:39:39 +02:00
let value = nm . getValue ( ) ;
2021-02-25 21:59:32 +01:00
if ( ! value || ! value . col _filter )
return false ;
2021-06-05 20:39:39 +02:00
for ( let field _filter of Object . values ( filters ) ) {
let val = value . col _filter [ field _filter . col ] ;
2021-06-14 10:35:54 +02:00
if ( val && ( typeof val == "string" && val . trim ( ) . length > 0 ) ) {
2021-02-25 21:59:32 +01:00
field _filter . filter _values . push ( val ) ;
2021-02-24 17:18:42 +01:00
}
2021-06-14 10:35:54 +02:00
else if ( val && typeof val == "object" && ! jQuery . isEmptyObject ( val ) ) {
field _filter . filter _values = field _filter . filter _values . concat ( Object . values ( val ) ) ;
}
2021-02-24 17:18:42 +01:00
}
2021-06-05 20:39:39 +02:00
// check filters against pushData.acl data
for ( let field _filter of Object . values ( filters ) ) {
2021-02-24 17:18:42 +01:00
// no filter set
if ( field _filter . filter _values . length == 0 )
2021-06-05 20:39:39 +02:00
continue ;
2021-02-24 17:18:42 +01:00
// acl value is a scalar (not array) --> check contained in filter
if ( pushData . acl && typeof pushData . acl [ field _filter . col ] !== 'object' ) {
if ( field _filter . filter _values . indexOf ( pushData . acl [ field _filter . col ] ) < 0 ) {
2021-06-05 20:39:39 +02:00
return false ;
2021-02-24 17:18:42 +01:00
}
2021-06-05 20:39:39 +02:00
continue ;
2021-02-24 17:18:42 +01:00
}
// acl value is an array (eg. tr_assigned) --> check intersection with filter
2021-06-05 20:39:39 +02:00
if ( ! field _filter . filter _values . filter ( account => pushData . acl [ field _filter . col ] . indexOf ( account ) >= 0 ) . length ) {
return false ;
2021-02-24 17:18:42 +01:00
}
2020-01-24 13:31:56 +01:00
}
2021-02-24 17:18:42 +01:00
return true ;
2021-06-05 20:39:39 +02:00
}
2020-01-24 13:31:56 +01:00
/ * *
* Get ( possible ) app - specific uid
*
* @ param { object } pushData see push method for individual attributes
* /
2021-06-05 20:39:39 +02:00
uid ( pushData ) {
2020-01-24 13:31:56 +01:00
return pushData . app + '::' + pushData . id ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Open an entry .
*
* Designed to be used with the action system as a callback
* eg : onExecute => app . < appname > . open
*
* @ param _action
* @ param _senders
* /
2021-06-05 20:39:39 +02:00
open ( _action , _senders ) {
2020-01-15 08:47:33 +01:00
var id _app = _senders [ 0 ] . id . split ( '::' ) ;
egw . open ( id _app [ 1 ] , this . appname ) ;
2021-06-05 20:39:39 +02:00
}
_do _action ( action _id , selected ) {
}
2020-01-15 08:47:33 +01:00
/ * *
* A generic method to action to server asynchronously
*
* Designed to be used with the action system as a callback .
* In the PHP side , set the action
* 'onExecute' => 'javaScript:app.<appname>.action' , and
* implement _do _action ( action _id , selected )
*
* @ param { egwAction } _action
* @ param { egwActionObject [ ] } _elems
* /
2021-06-05 20:39:39 +02:00
action ( _action , _elems ) {
2020-01-15 08:47:33 +01:00
// let user confirm select-all
var select _all = _action . getManager ( ) . getActionById ( "select_all" ) ;
var confirm _msg = ( _elems . length > 1 || select _all && select _all . checked ) &&
typeof _action . data . confirm _multiple != 'undefined' ?
_action . data . confirm _multiple : _action . data . confirm ;
if ( typeof confirm _msg != 'undefined' ) {
var that = this ;
var action _id = _action . id ;
2021-06-05 20:39:39 +02:00
et2 _dialog . show _dialog ( function ( button _id , value ) {
if ( button _id != et2 _dialog . NO _BUTTON ) {
2020-01-15 08:47:33 +01:00
that . _do _action ( action _id , _elems ) ;
}
2021-06-05 20:39:39 +02:00
} , confirm _msg , egw . lang ( 'Confirmation required' ) , null , et2 _dialog . BUTTONS _YES _NO , et2 _dialog . QUESTION _MESSAGE ) ;
2020-01-15 08:47:33 +01:00
}
else if ( typeof this . _do _action == 'function' ) {
this . _do _action ( _action . id , _elems ) ;
}
else {
// If this is a nextmatch action, do an ajax submit setting the action
var nm = null ;
var action = _action ;
while ( nm == null && action . parent != null ) {
if ( action . data . nextmatch )
nm = action . data . nextmatch ;
action = action . parent ;
}
if ( nm != null ) {
var value = { } ;
value [ nm . options . settings . action _var ] = _action . id ;
nm . set _value ( value ) ;
nm . getInstanceManager ( ) . submit ( ) ;
}
}
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Set the application ' s state to the given state .
*
* While not pretending to implement the history API , it is patterned similarly
* @ link http : //www.whatwg.org/specs/web-apps/current-work/multipage/history.html
*
* The default implementation works with the favorites to apply filters to a nextmatch .
*
*
* @ param { { name : string , state : object } | string } state Object ( or JSON string ) for a state .
* Only state is required , and its contents are application specific .
* @ param { string } template template name to check , instead of trying all templates of current app
* @ return { boolean } false - Returns false to stop event propagation
* /
2021-06-05 20:39:39 +02:00
setState ( state , template ) {
2020-02-09 12:27:39 +01:00
var _a ;
2020-01-15 08:47:33 +01:00
// State should be an object, not a string, but we'll parse
if ( typeof state == "string" ) {
if ( state . indexOf ( '{' ) != - 1 || state == 'null' ) {
state = JSON . parse ( state ) ;
}
}
if ( typeof state != "object" ) {
egw . debug ( 'error' , 'Unable to set state to %o, needs to be an object' , state ) ;
return ;
}
if ( state == null ) {
state = { } ;
}
// Check for egw.open() parameters
if ( state . state && state . state . id && state . state . app ) {
return egw . open ( state . state , undefined , undefined , { } , '_self' ) ;
}
// Try and find a nextmatch widget, and set its filters
var nextmatched = false ;
2021-06-05 20:39:39 +02:00
var et2 = template ? etemplate2 . getByTemplate ( template ) : etemplate2 . getByApplication ( this . appname ) ;
2020-01-15 08:47:33 +01:00
for ( var i = 0 ; i < et2 . length ; i ++ ) {
et2 [ i ] . widgetContainer . iterateOver ( function ( _widget ) {
// Firefox has trouble with spaces in search
if ( state . state && state . state . search )
state . state . search = unescape ( state . state . search ) ;
// Apply
if ( state . state && state . state . sort && state . state . sort . id ) {
_widget . sortBy ( state . state . sort . id , state . state . sort . asc , false ) ;
}
2020-11-16 18:57:04 +01:00
else {
// Not using resetSort() to avoid the extra applyFilters() call
_widget . sortBy ( undefined , undefined , false ) ;
}
2020-01-15 08:47:33 +01:00
if ( state . state && state . state . selectcols ) {
// Make sure it's a real array, not an object, then set cols
_widget . set _columns ( jQuery . extend ( [ ] , state . state . selectcols ) ) ;
}
_widget . applyFilters ( state . state || state . filter || { } ) ;
nextmatched = true ;
2021-06-05 20:39:39 +02:00
} , this , et2 _nextmatch ) ;
2020-01-15 08:47:33 +01:00
if ( nextmatched )
return false ;
}
// 'blank' is the special name for no filters, send that instead of the nice translated name
var safe _name = jQuery . isEmptyObject ( state ) || jQuery . isEmptyObject ( state . state || state . filter ) ? 'blank' : state . name . replace ( /[^A-Za-z0-9-_]/g , '_' ) ;
var url = '/' + this . appname + '/index.php' ;
// Try a redirect to list, if app defines a "list" value in registry
if ( egw . link _get _registry ( this . appname , 'list' ) ) {
url = egw . link ( '/index.php' , jQuery . extend ( { 'favorite' : safe _name } , egw . link _get _registry ( this . appname , 'list' ) ) ) ;
}
// if no list try index value from application
2020-02-09 12:27:39 +01:00
else if ( ( _a = egw . app ( this . appname ) ) === null || _a === void 0 ? void 0 : _a . index ) {
2020-01-15 08:47:33 +01:00
url = egw . link ( '/index.php' , 'menuaction=' + egw . app ( this . appname ) . index + '&favorite=' + safe _name ) ;
}
egw . open _link ( url , undefined , undefined , this . appname ) ;
return false ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Retrieve the current state of the application for future restoration
*
* The state can be anything , as long as it ' s an object . The contents are
* application specific . The default implementation finds a nextmatch and
* returns its value .
* The return value of this function cannot be passed directly to setState ( ) ,
* since setState is expecting an additional wrapper , eg :
* { name : 'something' , state : getState ( ) }
*
* @ return { object } Application specific map representing the current state
* /
2021-06-05 20:39:39 +02:00
getState ( ) {
2020-01-15 08:47:33 +01:00
var state = { } ;
// Try and find a nextmatch widget, and set its filters
2021-06-05 20:39:39 +02:00
var et2 = etemplate2 . getByApplication ( this . appname ) ;
2020-01-15 08:47:33 +01:00
for ( var i = 0 ; i < et2 . length ; i ++ ) {
et2 [ i ] . widgetContainer . iterateOver ( function ( _widget ) {
state = _widget . getValue ( ) ;
2021-06-05 20:39:39 +02:00
} , this , et2 _nextmatch ) ;
2020-01-15 08:47:33 +01:00
}
return state ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Function to load selected row from nm into a template view
*
* @ param { object } _action
* @ param { object } _senders
* @ param { boolean } _noEdit defines whether to set edit button or not default is false
* @ param { function } et2 _callback function to run after et2 is loaded
* /
2021-06-05 20:39:39 +02:00
viewEntry ( _action , _senders , _noEdit , et2 _callback ) {
2020-01-15 08:47:33 +01:00
//full id in nm
var id = _senders [ 0 ] . id ;
// flag for edit button
var noEdit = _noEdit || false ;
// nm row id
var rowID = '' ;
// content to feed to etemplate2
var content = { } ;
var self = this ;
if ( id ) {
var parts = id . split ( '::' ) ;
rowID = parts [ 1 ] ;
content = egw . dataGetUIDdata ( id ) ;
if ( content . data )
content = content . data ;
}
// create a new app object with just constructors for our new etemplate2 object
var app = { classes : window . app . classes } ;
/* destroy generated etemplate for view mode in DOM*/
var destroy = function ( ) {
self . viewContainer . remove ( ) ;
delete self . viewTemplate ;
delete self . viewContainer ;
delete self . et2 _view ;
// we need to reference back into parent context this
for ( var v in self ) {
this [ v ] = self [ v ] ;
}
app = null ;
} ;
// view container
this . viewContainer = jQuery ( document . createElement ( 'div' ) )
. addClass ( 'et2_mobile_view' )
. css ( {
"z-index" : 102 ,
width : "100%" ,
height : "100%" ,
background : "white" ,
display : 'block' ,
position : 'absolute' ,
left : 0 ,
bottom : 0 ,
right : 0 ,
overflow : 'auto'
} )
. attr ( 'id' , 'popupMainDiv' )
. appendTo ( 'body' ) ;
// close button
var close = jQuery ( document . createElement ( 'span' ) )
. addClass ( 'egw_fw_mobile_popup_close loaded' )
. click ( function ( ) {
destroy . call ( app [ self . appname ] ) ;
//disable selected actions after close
egw _globalObjectManager . setAllSelected ( false ) ;
} )
. appendTo ( this . viewContainer ) ;
if ( ! noEdit ) {
// edit button
var edit = jQuery ( document . createElement ( 'span' ) )
. addClass ( 'mobile-view-editBtn' )
. click ( function ( ) {
egw . open ( rowID , self . appname ) ;
} )
. appendTo ( this . viewContainer ) ;
}
// view template main container (content)
this . viewTemplate = jQuery ( document . createElement ( 'div' ) )
. attr ( 'id' , this . appname + '-view' )
. addClass ( 'et2_mobile-view-container popupMainDiv' )
. appendTo ( this . viewContainer ) ;
var mobileViewTemplate = ( _action . data . mobileViewTemplate || 'edit' ) . split ( '?' ) ;
var templateName = mobileViewTemplate [ 0 ] ;
var templateTimestamp = mobileViewTemplate [ 1 ] ;
var templateURL = egw . webserverUrl + '/' + this . appname + '/templates/mobile/' + templateName + '.xet' + '?' + templateTimestamp ;
var data = {
'content' : content ,
'readonlys' : { '__ALL__' : true , 'link_to' : false } ,
'currentapp' : this . appname ,
'langRequire' : this . et2 . getArrayMgr ( 'langRequire' ) . data ,
'sel_options' : this . et2 . getArrayMgr ( 'sel_options' ) . data ,
'modifications' : this . et2 . getArrayMgr ( 'modifications' ) . data ,
'validation_errors' : this . et2 . getArrayMgr ( 'validation_errors' ) . data
} ;
// etemplate2 object for view
2021-06-05 20:39:39 +02:00
this . et2 _view = new etemplate2 ( this . viewTemplate [ 0 ] , '' ) ;
2020-01-15 08:47:33 +01:00
framework . pushState ( 'view' ) ;
if ( templateName ) {
this . et2 _view . load ( this . appname + '.' + templateName , templateURL , data , typeof et2 _callback == 'function' ? et2 _callback : function ( ) { } , app ) ;
}
// define a global close function for view template
// in order to be able to destroy view on action
this . et2 _view . close = destroy ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Initializes actions and handlers on sidebox ( delete )
*
* @ param { jQuery } sidebox jQuery of DOM node
* /
2021-06-05 20:39:39 +02:00
_init _sidebox ( sidebox ) {
2020-01-15 08:47:33 +01:00
// Initialize egw tutorial sidebox, but only for non-popups, as calendar edit app.js has this.et2 set to tutorial et2 object
if ( ! this . egw . is _popup ( ) ) {
var egw _fw = egw _getFramework ( ) ;
var tutorial = jQuery ( '#egw_tutorial_' + this . appname + '_sidebox' , egw _fw ? egw _fw . sidemenuDiv : document ) ;
// _init_sidebox gets currently called multiple times, which needs to be fixed
if ( tutorial . length && ! this . tutorial _initialised ) {
this . egwTutorial _init ( tutorial [ 0 ] ) ;
this . tutorial _initialised = true ;
}
}
if ( sidebox . length ) {
var self = this ;
if ( this . sidebox )
this . sidebox . off ( ) ;
this . sidebox = sidebox ;
sidebox
. off ( )
// removed .on("mouse(enter|leave)" (wrapping trash icon), as it stalls delete in IE11
. on ( "click.sidebox" , "div.ui-icon-trash" , this , this . delete _favorite )
// need to install a favorite handler, as we switch original one off with .off()
. on ( 'click.sidebox' , 'li[data-id]' , this , function ( event ) {
var li = jQuery ( this ) ;
li . siblings ( ) . removeClass ( 'ui-state-highlight' ) ;
var state = { } ;
var pref = egw . preference ( 'favorite_' + this . dataset . id , self . appname ) ;
if ( pref ) {
// Extend, to prevent changing the preference by reference
jQuery . extend ( true , state , pref ) ;
}
if ( this . dataset . id != 'add' ) {
event . stopImmediatePropagation ( ) ;
self . setState . call ( self , state ) ;
return false ;
}
} )
. addClass ( "ui-helper-clearfix" ) ;
//Add Sortable handler to sideBox fav. menu
jQuery ( 'ul' , '#favorite_sidebox_' + this . appname ) . sortable ( {
items : 'li:not([data-id$="add"])' ,
placeholder : 'ui-fav-sortable-placeholder' ,
delay : 250 ,
helper : function ( event , item ) {
// We'll need to know which app this is for
item . attr ( 'data-appname' , self . appname ) ;
// Create custom helper so it can be dragged to Home
var h _parent = item . parent ( ) . parent ( ) . clone ( ) ;
h _parent . find ( 'li' ) . not ( '[data-id="' + item . attr ( 'data-id' ) + '"]' ) . remove ( ) ;
h _parent . appendTo ( 'body' ) ;
return h _parent ;
} ,
// @ts-ignore
refreshPositions : true ,
update : function ( event , ui ) {
// @ts-ignore
var favSortedList = jQuery ( this ) . sortable ( 'toArray' , { attribute : 'data-id' } ) ;
self . egw . set _preference ( self . appname , 'fav_sort_pref' , favSortedList ) ;
self . _refresh _fav _nm ( ) ;
}
} ) ;
// Bind favorite de-select
var egw _fw = egw _getFramework ( ) ;
if ( egw _fw && egw _fw . applications [ this . appname ] && egw _fw . applications [ this . appname ] . browser
&& egw _fw . applications [ this . appname ] . browser . baseDiv ) {
jQuery ( egw _fw . applications [ this . appname ] . browser . baseDiv )
. off ( '.sidebox' )
. on ( 'change.sidebox' , function ( ) {
self . highlight _favorite ( ) ;
} ) ;
}
return true ;
}
return false ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Add a new favorite
*
* Fetches the current state from the application , then opens a dialog to get the
* name and other settings . If user proceeds , the favorite is saved , and if possible
* the sidebox is directly updated to include the new favorite
*
* @ param { object } [ state ] State settings to be merged into the application state
* /
2021-06-05 20:39:39 +02:00
add _favorite ( state ) {
2020-01-15 08:47:33 +01:00
if ( typeof this . favorite _popup == "undefined" || // Create popup if it's not defined yet
( this . favorite _popup && typeof this . favorite _popup . group != "undefined"
&& ! this . favorite _popup . group . isAttached ( ) ) ) // recreate the favorite popup if the group selectbox is not attached (eg. after et2 submit)
{
this . _create _favorite _popup ( ) ;
}
// Get current state
this . favorite _popup . state = jQuery . extend ( { } , this . getState ( ) , state || { } ) ;
/ *
// Add in extras
for ( var extra in this . options . filters )
{
// Don't overwrite what nm has, chances are nm has more up-to-date value
if ( typeof this . popup . current _filters == 'undefined' )
{
this . popup . current _filters [ extra ] = this . nextmatch . options . settings [ extra ] ;
}
}
// Add in application's settings
if ( this . filters != true )
{
for ( var i = 0 ; i < this . filters . length ; i ++ )
{
this . popup . current _filters [ this . options . filters [ i ] ] = this . nextmatch . options . settings [ this . options . filters [ i ] ] ;
}
}
* /
// Make sure it's an object - deep copy to prevent references in sub-objects (col_filters)
this . favorite _popup . state = jQuery . extend ( true , { } , this . favorite _popup . state ) ;
// Update popup with current set filters (more for debug than user)
var filter _list = [ ] ;
var add _to _popup = function ( arr ) {
filter _list . push ( "<ul>" ) ;
jQuery . each ( arr , function ( index , filter ) {
2021-03-09 15:19:27 +01:00
filter _list . push ( "<li id='index'><span class='filter_id'>" + index . toString ( ) + "</span>" +
2020-01-15 08:47:33 +01:00
( typeof filter != "object" ? "<span class='filter_value'>" + filter + "</span>" : "" ) ) ;
if ( typeof filter == "object" && filter != null )
add _to _popup ( filter ) ;
filter _list . push ( "</li>" ) ;
} ) ;
filter _list . push ( "</ul>" ) ;
} ;
add _to _popup ( this . favorite _popup . state ) ;
jQuery ( "#" + this . appname + "_favorites_popup_state" , this . favorite _popup )
. replaceWith ( jQuery ( filter _list . join ( "" ) ) . attr ( "id" , this . appname + "_favorites_popup_state" ) ) ;
jQuery ( "#" + this . appname + "_favorites_popup_state" , this . favorite _popup )
. hide ( )
. siblings ( ".ui-icon-circle-plus" )
. removeClass ( "ui-icon-circle-minus" ) ;
// Popup
this . favorite _popup . dialog ( "open" ) ;
console . log ( this ) ;
// Stop the normal bubbling if this is called on click
return false ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Update favorite items in nm fav . menu
*
* /
2021-06-05 20:39:39 +02:00
_refresh _fav _nm ( ) {
2020-01-15 08:47:33 +01:00
var self = this ;
2021-06-05 20:39:39 +02:00
if ( etemplate2 && etemplate2 . getByApplication ) {
var et2 = etemplate2 . getByApplication ( self . appname ) ;
2020-01-15 08:47:33 +01:00
for ( var i = 0 ; i < et2 . length ; i ++ ) {
et2 [ i ] . widgetContainer . iterateOver ( function ( _widget ) {
_widget . stored _filters = _widget . load _favorites ( self . appname ) ;
_widget . init _filters ( _widget ) ;
2021-06-05 20:39:39 +02:00
} , self , et2 _favorites ) ;
2020-01-15 08:47:33 +01:00
}
}
else {
throw new Error ( "_refresh_fav_nm():Either et2 is not ready/ not there yet. Make sure that etemplate2 is ready before call this method." ) ;
}
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Create the "Add new" popup dialog
* /
2021-06-05 20:39:39 +02:00
_create _favorite _popup ( ) {
2020-01-15 08:47:33 +01:00
var self = this ;
var favorite _prefix = 'favorite_' ;
// Clear old, if existing
if ( this . favorite _popup && this . favorite _popup . group ) {
2020-05-15 17:50:38 +02:00
this . favorite _popup . group . destroy ( ) ;
2020-01-15 08:47:33 +01:00
delete this . favorite _popup ;
}
// Create popup
this . favorite _popup = jQuery ( '<div id="' + this . dom _id + '_nm_favorites_popup" title="' + egw ( ) . lang ( "New favorite" ) + ' " > \
< form > \
< label for = "name" > ' +
this . egw . lang ( "name" ) +
'</label>' +
' < input type = "text" name = "name" id = "name" / > \
< div id = "' + this.appname + '_favorites_popup_admin" / > \
< span > ' + this.egw.lang("Details") + ' < /span><span style="float:left;" class="ui-icon ui-icon-circle-plus ui-button" / > \
< ul id = "' + this.appname + '_favorites_popup_state" / > \
< / f o r m > \
< / d i v > ' ) . a p p e n d T o ( t h i s . e t 2 ? t h i s . e t 2 . g e t D O M N o d e ( ) : j Q u e r y ( ' b o d y ' ) ) ;
// @ts-ignore
jQuery ( ".ui-icon-circle-plus" , this . favorite _popup ) . prev ( ) . andSelf ( ) . click ( function ( ) {
var details = jQuery ( "#" + self . appname + "_favorites_popup_state" , self . favorite _popup )
. slideToggle ( )
. siblings ( ".ui-icon-circle-plus" )
. toggleClass ( "ui-icon-circle-minus" ) ;
} ) ;
// Add some controls if user is an admin
var apps = egw ( ) . user ( 'apps' ) ;
var is _admin = ( typeof apps [ 'admin' ] != "undefined" ) ;
if ( is _admin ) {
2021-06-05 20:39:39 +02:00
this . favorite _popup . group = et2 _createWidget ( "select-account" , {
2020-01-15 08:47:33 +01:00
id : "favorite[group]" ,
account _type : "groups" ,
empty _label : "Groups" ,
no _lang : true ,
parent _node : this . appname + '_favorites_popup_admin'
} , ( this . et2 || null ) ) ;
this . favorite _popup . group . loadingFinished ( ) ;
}
var buttons = { } ;
buttons [ 'save' ] = {
text : this . egw . lang ( 'save' ) ,
default : true ,
style : 'background-image: url(' + this . egw . image ( 'save' ) + ')' ,
click : function ( ) {
// Add a new favorite
var name = jQuery ( "#name" , this ) ;
if ( name . val ( ) ) {
// Add to the list
name . val ( name . val ( ) . replace ( /(<([^>]+)>)/ig , "" ) ) ;
var safe _name = name . val ( ) . replace ( /[^A-Za-z0-9-_]/g , "_" ) ;
var favorite = {
name : name . val ( ) ,
group : ( typeof self . favorite _popup . group != "undefined" &&
self . favorite _popup . group . get _value ( ) ? self . favorite _popup . group . get _value ( ) : false ) ,
state : self . favorite _popup . state
} ;
var favorite _pref = favorite _prefix + safe _name ;
// Save to preferences
if ( typeof self . favorite _popup . group != "undefined" && self . favorite _popup . group . getValue ( ) != '' ) {
// Admin stuff - save preference server side
self . egw . jsonq ( 'EGroupware\\Api\\Framework::ajax_set_favorite' , [
self . appname ,
name . val ( ) ,
"add" ,
self . favorite _popup . group . get _value ( ) ,
self . favorite _popup . state
] ) ;
self . favorite _popup . group . set _value ( '' ) ;
}
else {
// Normal user - just save to preferences client side
self . egw . set _preference ( self . appname , favorite _pref , favorite ) ;
}
// Add to list immediately
if ( self . sidebox ) {
// Remove any existing with that name
jQuery ( '[data-id="' + safe _name + '"]' , self . sidebox ) . remove ( ) ;
// Create new item
var html = "<li data-id='" + safe _name + "' data-group='" + favorite . group + "' class='ui-menu-item' role='menuitem'>\n" ;
var href = 'javascript:app.' + self . appname + '.setState(' + JSON . stringify ( favorite ) + ');' ;
html += "<a href='" + href + "' class='ui-corner-all' tabindex='-1'>" ;
html += "<div class='" + 'sideboxstar' + "'></div>" +
favorite . name ;
html += "<div class='ui-icon ui-icon-trash' title='" + egw . lang ( 'Delete' ) + "'/>" ;
html += "</a></li>\n" ;
jQuery ( html ) . insertBefore ( jQuery ( 'li' , self . sidebox ) . last ( ) ) ;
self . _init _sidebox ( self . sidebox ) ;
}
// Try to update nextmatch favorites too
self . _refresh _fav _nm ( ) ;
}
// Reset form
delete self . favorite _popup . state ;
name . val ( "" ) ;
jQuery ( "#filters" , self . favorite _popup ) . empty ( ) ;
jQuery ( this ) . dialog ( "close" ) ;
}
} ;
buttons [ 'cancel' ] = {
text : this . egw . lang ( "cancel" ) ,
style : 'background-image: url(' + this . egw . image ( 'cancel' ) + ')' ,
click : function ( ) {
if ( typeof self . favorite _popup . group !== 'undefined' && self . favorite _popup . group . set _value ) {
self . favorite _popup . group . set _value ( null ) ;
}
jQuery ( this ) . dialog ( "close" ) ;
}
} ;
this . favorite _popup . dialog ( {
autoOpen : false ,
modal : true ,
buttons : buttons ,
close : function ( ) {
}
} ) ;
// Bind handler for enter keypress
this . favorite _popup . off ( 'keydown' ) . on ( 'keydown' , jQuery . proxy ( function ( e ) {
var tagName = e . target . tagName . toLowerCase ( ) ;
tagName = ( tagName === 'input' && e . target . type === 'button' ) ? 'button' : tagName ;
if ( e . keyCode == jQuery . ui . keyCode . ENTER && tagName !== 'textarea' && tagName !== 'select' && tagName !== 'button' ) {
e . preventDefault ( ) ;
jQuery ( 'button[default]' , this . favorite _popup . parent ( ) ) . trigger ( 'click' ) ;
return false ;
}
} , this ) ) ;
return false ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Delete a favorite from the list and update preferences
* Registered as a handler on the delete icons
*
* @ param { jQuery . event } event event object
* /
2021-06-05 20:39:39 +02:00
delete _favorite ( event ) {
2020-01-15 08:47:33 +01:00
// Don't do the menu
event . stopImmediatePropagation ( ) ;
var app = event . data ;
var id = jQuery ( this ) . parentsUntil ( 'li' ) . parent ( ) . attr ( "data-id" ) ;
var group = jQuery ( this ) . parentsUntil ( 'li' ) . parent ( ) . attr ( "data-group" ) || '' ;
var line = jQuery ( 'li[data-id="' + id + '"]' , app . sidebox ) ;
var name = line . first ( ) . text ( ) ;
var trash = this ;
line . addClass ( 'loading' ) ;
// Make sure first
var do _delete = function ( button _id ) {
2021-06-05 20:39:39 +02:00
if ( button _id != et2 _dialog . YES _BUTTON ) {
2020-01-15 08:47:33 +01:00
line . removeClass ( 'loading' ) ;
return ;
}
// Hide the trash
jQuery ( trash ) . hide ( ) ;
// Delete preference server side
var request = egw . json ( "EGroupware\\Api\\Framework::ajax_set_favorite" , [ app . appname , id , "delete" , group , '' ] , function ( result ) {
// Got the full response from callback, which we don't want
if ( result . type )
return ;
if ( result && typeof result == 'boolean' ) {
// Remove line from list
line . slideUp ( "slow" , function ( ) { } ) ;
app . _refresh _fav _nm ( ) ;
}
else {
// Something went wrong server side
line . removeClass ( 'loading' ) . addClass ( 'ui-state-error' ) ;
}
} , jQuery ( trash ) . parentsUntil ( "li" ) . parent ( ) , true , jQuery ( trash ) . parentsUntil ( "li" ) . parent ( ) ) ;
request . sendRequest ( true ) ;
} ;
2021-06-05 20:39:39 +02:00
et2 _dialog . show _dialog ( do _delete , ( egw . lang ( "Delete" ) + " " + name + "?" ) , egw . lang ( "Delete" ) , null , et2 _dialog . BUTTONS _YES _NO , et2 _dialog . QUESTION _MESSAGE ) ;
2020-01-15 08:47:33 +01:00
return false ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Mark the favorite closest matching the current state
*
* Closest matching takes into account not set values , so we pick the favorite
* with the most matching values without a value that differs .
* /
2021-06-05 20:39:39 +02:00
highlight _favorite ( ) {
2020-01-15 08:47:33 +01:00
if ( ! this . sidebox )
return ;
var state = this . getState ( ) ;
var best _match = false ;
var best _count = 0 ;
var self = this ;
jQuery ( 'li[data-id]' , this . sidebox ) . removeClass ( 'ui-state-highlight' ) ;
jQuery ( 'li[data-id]' , this . sidebox ) . each ( function ( i , href ) {
var favorite = { } ;
if ( this . dataset . id && egw . preference ( 'favorite_' + this . dataset . id , self . appname ) ) {
favorite = egw . preference ( 'favorite_' + this . dataset . id , self . appname ) ;
}
if ( ! favorite || jQuery . isEmptyObject ( favorite ) )
return ;
// Handle old style by making it like new style
if ( favorite . filter && ! favorite . state ) {
favorite . state = favorite . filter ;
}
var match _count = 0 ;
var extra _keys = Object . keys ( favorite . state ) ;
for ( var state _key in state ) {
extra _keys . splice ( extra _keys . indexOf ( state _key ) , 1 ) ;
if ( typeof favorite . state != 'undefined' && typeof state [ state _key ] != 'undefined' && typeof favorite . state [ state _key ] != 'undefined' && ( state [ state _key ] == favorite . state [ state _key ] || ! state [ state _key ] && ! favorite . state [ state _key ] ) ) {
match _count ++ ;
}
2021-03-24 17:21:02 +01:00
else if ( state _key == 'selectcols' && typeof favorite . state . selectcols == "undefined" ) {
// Skip, not set in favorite
2020-01-15 08:47:33 +01:00
}
else if ( typeof state [ state _key ] != 'undefined' && state [ state _key ] && typeof state [ state _key ] === 'object'
&& typeof favorite . state != 'undefined' && typeof favorite . state [ state _key ] != 'undefined' && favorite . state [ state _key ] && typeof favorite . state [ state _key ] === 'object' ) {
if ( ( typeof state [ state _key ] . length !== 'undefined' || typeof state [ state _key ] . length !== 'undefined' )
&& ( state [ state _key ] . length || Object . keys ( state [ state _key ] ) . length ) != ( favorite . state [ state _key ] . length || Object . keys ( favorite . state [ state _key ] ) . length ) ) {
// State or favorite has a length, but the other does not
if ( ( state [ state _key ] . length === 0 || Object . keys ( state [ state _key ] ) . length === 0 ) &&
( favorite . state [ state _key ] . length == 0 || Object . keys ( favorite . state [ state _key ] ) . length === 0 ) ) {
// Just missing, or one is an array and the other is an object
continue ;
}
// One has a value and the other doesn't, no match
return ;
}
else if ( state [ state _key ] . length !== 'undefined' && typeof favorite . state [ state _key ] . length !== 'undefined' &&
state [ state _key ] . length === 0 && favorite . state [ state _key ] . length === 0 ) {
// Both set, but both empty
match _count ++ ;
continue ;
}
// Consider sub-objects (column filters) individually
for ( var sub _key in state [ state _key ] ) {
if ( state [ state _key ] [ sub _key ] == favorite . state [ state _key ] [ sub _key ] || ! state [ state _key ] [ sub _key ] && ! favorite . state [ state _key ] [ sub _key ] ) {
match _count ++ ;
}
else if ( state [ state _key ] [ sub _key ] && favorite . state [ state _key ] [ sub _key ] &&
typeof state [ state _key ] [ sub _key ] === 'object' && typeof favorite . state [ state _key ] [ sub _key ] === 'object' ) {
// Too deep to keep going, just string compare for perfect match
if ( JSON . stringify ( state [ state _key ] [ sub _key ] ) === JSON . stringify ( favorite . state [ state _key ] [ sub _key ] ) ) {
match _count ++ ;
}
}
else if ( typeof state [ state _key ] [ sub _key ] !== 'undefined' && state [ state _key ] [ sub _key ] != favorite . state [ state _key ] [ sub _key ] ) {
// Different values, do not match
return ;
}
}
}
else if ( typeof state [ state _key ] !== 'undefined'
&& typeof favorite . state != 'undefined' && typeof favorite . state [ state _key ] !== 'undefined'
&& state [ state _key ] != favorite . state [ state _key ] ) {
// Different values, do not match
return ;
}
}
// Check for anything set that the current one does not have
for ( var i = 0 ; i < extra _keys . length ; i ++ ) {
if ( favorite . state [ extra _keys [ i ] ] )
return ;
}
if ( match _count > best _count ) {
best _match = this . dataset . id ;
best _count = match _count ;
}
} ) ;
if ( best _match ) {
jQuery ( 'li[data-id="' + best _match + '"]' , this . sidebox ) . addClass ( 'ui-state-highlight' ) ;
}
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Fix scrolling iframe browsed by iPhone / iPod / iPad touch devices
* /
2021-06-05 20:39:39 +02:00
_fix _iFrameScrolling ( ) {
2020-01-15 08:47:33 +01:00
if ( /iPhone|iPod|iPad/ . test ( navigator . userAgent ) ) {
jQuery ( "iframe" ) . on ( {
load : function ( ) {
var body = this . contentWindow . document . body ;
var div = jQuery ( document . createElement ( "div" ) )
. css ( {
'height' : jQuery ( this . parentNode ) . height ( ) ,
'width' : jQuery ( this . parentNode ) . width ( ) ,
'overflow' : 'scroll'
} ) ;
while ( body . firstChild ) {
div . append ( body . firstChild ) ;
}
jQuery ( body ) . append ( div ) ;
}
} ) ;
}
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Set document title , uses getWindowTitle to get the correct title ,
* otherwise set it with uniqueID as default title
* /
2021-06-05 20:39:39 +02:00
_set _Window _title ( ) {
2020-01-15 08:47:33 +01:00
var title = this . getWindowTitle ( ) ;
if ( title ) {
document . title = this . et2 . _inst . uniqueId + ": " + title ;
}
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Window title getter function in order to set the window title
* this can be overridden on each application app . js file to customize the title value
*
* @ returns { string } window title
* /
2021-06-05 20:39:39 +02:00
getWindowTitle ( ) {
2020-01-15 08:47:33 +01:00
var titleWidget = this . et2 . getWidgetById ( 'title' ) ;
if ( titleWidget ) {
return titleWidget . options . value ;
}
else {
return this . et2 . _inst . uniqueId ;
}
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Handler for drag and drop when dragging nextmatch rows from mail app
* and dropped on a row in the current application . We copy the mail into
* the filemanager to link it since we can ' t link directly .
*
* This doesn ' t happen automatically . Each application must indicate that
* it will accept dropped mail by it ' s nextmatch actions :
*
* $actions [ 'info_drop_mail' ] = array (
* 'type' => 'drop' ,
* 'acceptedTypes' => 'mail' ,
* 'onExecute' => 'javaScript:app.infolog.handle_dropped_mail' ,
* 'hideOnDisabled' => true
* ) ;
*
* This action , when defined , will not affect the automatic linking between
* normal applications .
*
* @ param { egwAction } _action
* @ param { egwActionObject [ ] } _selected Dragged mail rows
* @ param { egwActionObject } _target Current application ' s nextmatch row the mail was dropped on
* /
2021-06-05 20:39:39 +02:00
handle _dropped _mail ( _action , _selected , _target ) {
2020-01-15 08:47:33 +01:00
/ * *
* Mail doesn ' t support link system , so we copy it to VFS
* /
var ids = _target . id . split ( "::" ) ;
if ( ids . length != 2 || ids [ 0 ] == 'mail' )
return ;
var vfs _path = "/apps/" + ids [ 0 ] + "/" + ids [ 1 ] ;
var mail _ids = [ ] ;
for ( var i = 0 ; i < _selected . length ; i ++ ) {
mail _ids . push ( _selected [ i ] . id ) ;
}
if ( mail _ids . length ) {
egw . message ( egw . lang ( "Please wait..." ) ) ;
this . egw . json ( 'filemanager.filemanager_ui.ajax_action' , [ 'mail' , mail _ids , vfs _path ] , function ( data ) {
// Trigger an update (minimal, no sorting changes) to display the new link
egw . refresh ( data . msg || '' , ids [ 0 ] , ids [ 1 ] , 'update' ) ;
} ) . sendRequest ( true ) ;
}
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Get json data for videos from the given url
*
* @ return { Promise , object } return Promise , json object as resolved result and error message in case of failure
* /
2021-06-05 20:39:39 +02:00
egwTutorialGetData ( ) {
2020-01-15 08:47:33 +01:00
var self = this ;
return new Promise ( function ( _resolve , _reject ) {
var resolve = _resolve ;
var reject = _reject ;
// delay the execution and let the rendering catches up. Seems only FF problem
window . setTimeout ( function ( ) {
self . egw . json ( 'EGroupware\\Api\\Framework\\Tutorial::ajax_data' , [ self . egw . app _name ( ) ] , function ( _data ) {
resolve ( _data ) ;
} ) . sendRequest ( ) ;
} , 0 ) ;
} ) ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Create and Render etemplate2 for egroupware tutorial
* sidebox option . The . xet file is stored in api / templates / default / e g w _ t u t o r i a l s
*
* @ description tutorials json object should have the following structure :
* object :
* {
* [ app name ] : {
* [ language tag ] : [
* { src : "" , thumbnail : "" , title : "" , desc : "" }
* ]
* }
* }
*
* * Note : "desc" and "title" are optional attributes , which "desc" would appears as tooltip for the video .
*
* example :
* {
* "mail" : {
* "en" : [
* { src : "https://www.youtube.com/embed/mCDJndpjO40" , thumbnail : "http://img.youtube.com/vi/mCDJndpjO40/0.jpg" , "title" : "PGP Encryption" , "desc" : "" } ,
* { src : "https://www.youtube.com/embed/mCDJndpjO" , thumbnail : "http://img.youtube.com/vi/mCDJndpjO/0.jpg" , "title" : "Subscription" , "desc" : "" } ,
* ] ,
* "de" : [
* { src : "https://www.youtube.com/embed/m40" , thumbnail : "http://img.youtube.com/vi/m40/0.jpg" , "title" : "PGP Verschlüsselung" , "desc" : "" } ,
* { src : "https://www.youtube.com/embed/mpjO" , thumbnail : "http://img.youtube.com/vi/mpjO/0.jpg" , "title" : "Ordner Abonnieren" , "desc" : "" } ,
* ]
* }
* }
*
* @ param { DOMNode } div
* /
2021-06-05 20:39:39 +02:00
egwTutorial _init ( div ) {
2020-01-15 08:47:33 +01:00
// et2 object
2021-06-05 20:39:39 +02:00
var etemplate = new etemplate2 ( div , '' ) ;
2020-01-15 08:47:33 +01:00
var template = egw . webserverUrl + '/api/templates/default/egw_tutorial.xet?1' ;
this . egwTutorialGetData ( ) . then ( function ( _data ) {
var lang = egw . preference ( 'lang' ) ;
var content = { content : { list : [ ] } } ;
if ( _data && _data [ egw . app _name ( ) ] ) {
if ( ! _data [ egw . app _name ( ) ] [ lang ] )
lang = 'en' ;
if ( typeof _data [ egw . app _name ( ) ] [ lang ] != 'undefined'
&& _data [ egw . app _name ( ) ] [ lang ] . length > 0 ) {
for ( var i = 0 ; i < _data [ egw . app _name ( ) ] [ lang ] . length ; i ++ ) {
var tuid = egw . app _name ( ) + '-' + lang + '-' + i ;
_data [ egw . app _name ( ) ] [ lang ] [ i ] [ 'onclick' ] = 'app.' + egw . app _name ( ) + '.egwTutorialPopup("' + tuid + '")' ;
}
content . content . list = _data [ egw . app _name ( ) ] [ lang ] ;
if ( template . indexOf ( '.xet' ) > 0 ) {
etemplate . load ( '' , template , content , function ( ) { } ) ;
}
else {
etemplate . load ( template , '' , content ) ;
}
}
}
} , function ( _err ) {
console . log ( _err ) ;
} ) ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Open popup to show given tutorial id
* @ param { string } _tuid tutorial object id
* - tuid : appname - lang - index
* /
2021-06-05 20:39:39 +02:00
egwTutorialPopup ( _tuid ) {
2020-01-15 08:47:33 +01:00
var url = egw . link ( '/index.php' , 'menuaction=api.EGroupware\\Api\\Framework\\Tutorial.popup&tuid=' + _tuid ) ;
egw . open _link ( url , '_blank' , '960x580' ) ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Function to set video iframe base on selected tutorial from tutorials box
*
* @ param { string } _url
* /
2021-06-05 20:39:39 +02:00
tutorial _videoOnClick ( _url ) {
var frame = etemplate2 . getByApplication ( 'api' ) [ 0 ] . widgetContainer . getWidgetById ( 'src' ) ;
2020-01-15 08:47:33 +01:00
if ( frame ) {
frame . set _value ( _url ) ;
}
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Function calls on discard checkbox and will set
* the egw _tutorial _noautoload preference
*
* @ param { type } egw
* @ param { type } widget
* /
2021-06-05 20:39:39 +02:00
tutorial _autoloadDiscard ( egw , widget ) {
2020-01-15 08:47:33 +01:00
if ( widget ) {
this . egw . set _preference ( 'common' , 'egw_tutorial_noautoload' , widget . get _value ( ) ) ;
}
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Check if Mailvelope is available , open ( or create ) "egroupware" keyring and call callback with it
*
* @ param { function } _callback called if and only if mailvelope is available ( context is this ! )
* /
2021-06-05 20:39:39 +02:00
mailvelopeAvailable ( _callback ) {
2020-01-15 08:47:33 +01:00
var self = this ;
var callback = jQuery . proxy ( _callback , this ) ;
if ( typeof mailvelope !== 'undefined' ) {
this . mailvelopeOpenKeyring ( ) . then ( callback ) ;
}
else {
jQuery ( window ) . on ( 'mailvelope' , function ( ) {
self . mailvelopeOpenKeyring ( ) . then ( callback ) ;
} ) ;
}
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* mailvelope object contains SyncHandlers
*
* @ property { function } descriptionuploadSync function called by Mailvelope to upload encrypted private key backup
* @ property { function } downloadSync function called by Mailvelope to download encrypted private key backup
* @ property { function } backup function called by Mailvelope to upload a public keyring backup
* @ property { function } restore function called by Mailvelope to restore a public keyring backup
* /
2021-06-05 20:39:39 +02:00
mailvelopeSyncHandler ( ) {
2020-01-15 08:47:33 +01:00
return {
/ * *
* function called by Mailvelope to upload a public keyring
* @ param { UploadSyncHandler } _uploadObj
* @ property { string } etag entity tag for the uploaded encrypted keyring , or null if initial upload
* @ property { AsciiArmored } keyringMsg encrypted keyring as PGP armored message
* @ returns { Promise . < UploadSyncReply , Error > }
* /
uploadSync : function ( _uploadObj ) {
return new Promise ( function ( _resolve , _reject ) { } ) ;
} ,
/ * *
* function called by Mailvelope to download a public keyring
*
* @ param { object } _downloadObj
* @ property { string } etag entity tag for the current local keyring , or null if no local eTag
* @ returns { Promise . < DownloadSyncReply , Error > }
* /
downloadSync : function ( _downloadObj ) {
return new Promise ( function ( _resolve , _reject ) { } ) ;
} ,
/ * *
* function called by Mailvelope to upload an encrypted private key backup
*
* @ param { BackupSyncPacket } _backup
* @ property { AsciiArmored } backup an encrypted private key as PGP armored message
* @ returns { Promise . < undefined , Error > }
* /
backup : function ( _backup ) {
return new Promise ( function ( _resolve , _reject ) {
// Store backup sync packet into .PGP-Key-Backup file in user directory
jQuery . ajax ( {
method : 'PUT' ,
url : egw . webserverUrl + '/webdav.php/home/' + egw . user ( 'account_lid' ) + '/.PGP-Key-Backup' ,
contentType : 'application/json' ,
data : JSON . stringify ( _backup ) ,
success : function ( ) {
_resolve ( _backup ) ;
} ,
error : function ( _err ) {
_reject ( _err ) ;
}
} ) ;
} ) ;
} ,
/ * *
* function called by Mailvelope to restore an encrypted private key backup
*
* @ returns { Promise . < BackupSyncPacket , Error > }
* @ todo
* /
restore : function ( ) {
return new Promise ( function ( _resolve , _reject ) {
var resolve = _resolve ;
var reject = _reject ;
jQuery . ajax ( {
url : egw . webserverUrl + '/webdav.php/home/' + egw . user ( 'account_lid' ) + '/.PGP-Key-Backup' ,
method : 'GET' ,
success : function ( _backup ) {
resolve ( JSON . parse ( _backup ) ) ;
egw . message ( 'Your key has been restored successfully.' ) ;
} ,
error : function ( _err ) {
//Try with old back file name
if ( _err . status == 404 ) {
jQuery . ajax ( {
method : 'GET' ,
url : egw . webserverUrl + '/webdav.php/home/' + egw . user ( 'account_lid' ) + '/.PK_PGP' ,
success : function ( _backup ) {
resolve ( JSON . parse ( _backup ) ) ;
egw . message ( 'Your key has been restored successfully.' ) ;
} ,
error : function ( _err ) {
_reject ( _err ) ;
}
} ) ;
}
else {
_reject ( _err ) ;
}
}
} ) ;
} ) ;
}
} ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Function for backup file operations
*
* @ param { type } _url Url of the backup file
* @ param { type } _cmd command to operate
* - PUT : to store backup file
* - GET : to read backup file
* - DELETE : to delete backup file
*
* @ param { type } _successCallback function called when the operation is successful
* @ param { type } _errorCallback function called when the operation fails
* @ param { type } _data data which needs to be stored in file via PUT command
* /
2021-06-05 20:39:39 +02:00
_mailvelopeBackupFileOperator ( _url , _cmd , _successCallback , _errorCallback , _data ) {
2020-01-15 08:47:33 +01:00
var ajaxObj = {
url : _url || egw . webserverUrl + '/webdav.php/home/' + egw . user ( 'account_lid' ) + '/.PGP-Key-Backup' ,
method : _cmd ,
success : _successCallback ,
error : _errorCallback
} ;
switch ( _cmd ) {
case 'PUT' :
jQuery . extend ( { } , ajaxObj , {
data : JSON . stringify ( _data ) ,
contentType : 'application/json'
} ) ;
break ;
case 'GET' :
jQuery . extend ( { } , ajaxObj , {
dataType : 'json'
} ) ;
break ;
case 'DELETE' :
break ;
}
jQuery . ajax ( ajaxObj ) ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Create backup dialog
* @ param { string } _selector DOM selector to attach backupDialog
* @ param { boolean } _initSetup determine wheter it ' s an initialization backup or restore backup
*
* @ returns { Promise . < backupPopupId , Error > }
* /
2021-06-05 20:39:39 +02:00
mailvelopeCreateBackupDialog ( _selector , _initSetup ) {
2020-01-15 08:47:33 +01:00
var self = this ;
var selector = _selector || 'body' ;
var initSetup = _initSetup ;
jQuery ( 'iframe[src^="chrome-extension"],iframe[src^="about:blank?mvelo"]' ) . remove ( ) ;
return new Promise ( function ( _resolve , _reject ) {
var resolve = _resolve ;
var reject = _reject ;
mailvelope . getKeyring ( 'egroupware' ) . then ( function ( _keyring ) {
_keyring . addSyncHandler ( self . mailvelopeSyncHandlerObj ) ;
var options = {
initialSetup : initSetup
} ;
_keyring . createKeyBackupContainer ( selector , options ) . then ( function ( _popupId ) {
var $backup _selector = jQuery ( 'iframe[src^="chrome-extension"],iframe[src^="about:blank?mvelo"]' ) ;
$backup _selector . css ( { position : 'absolute' , "z-index" : 1 } ) ;
_popupId . isReady ( ) . then ( function ( result ) {
egw . message ( 'Your key has been backedup into .PGP-Key-Backup successfully.' ) ;
jQuery ( selector ) . empty ( ) ;
} ) ;
resolve ( _popupId ) ;
} , function ( _err ) {
reject ( _err ) ;
} ) ;
} , function ( _err ) {
reject ( _err ) ;
} ) ;
} ) ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Delete backup key from filesystem
* /
2021-06-05 20:39:39 +02:00
mailvelopeDeleteBackup ( ) {
2020-01-15 08:47:33 +01:00
var self = this ;
2021-06-05 20:39:39 +02:00
et2 _dialog . show _dialog ( function ( _button _id ) {
if ( _button _id == et2 _dialog . YES _BUTTON ) {
2020-01-15 08:47:33 +01:00
self . _mailvelopeBackupFileOperator ( undefined , 'DELETE' , function ( ) {
self . egw . message ( self . egw . lang ( 'The backup key has been deleted.' ) ) ;
} , function ( _err ) {
self . egw . message ( self . egw . lang ( 'Was not able to delete the backup key because %1' , _err ) ) ;
} ) ;
}
2021-06-05 20:39:39 +02:00
} , self . egw . lang ( 'Are you sure, you would like to delete the backup key?' ) , self . egw . lang ( 'Delete backup key' ) , { } , et2 _dialog . BUTTONS _YES _NO _CANCEL , et2 _dialog . QUESTION _MESSAGE , undefined , self . egw ) ;
}
2020-01-15 08:47:33 +01:00
/ * *
* Create mailvelope restore dialog
* @ param { string } _selector DOM selector to attach restorDialog
* @ param { boolean } _restorePassword if true , will restore key password too
*
* @ returns { Promise }
* /
2021-06-05 20:39:39 +02:00
mailvelopeCreateRestoreDialog ( _selector , _restorePassword ) {
2020-01-15 08:47:33 +01:00
var self = this ;
var restorePassword = _restorePassword ;
var selector = _selector || 'body' ;
//Clear the
jQuery ( 'iframe[src^="chrome-extension"],iframe[src^="about:blank?mvelo"]' ) . remove ( ) ;
return new Promise ( function ( _resolve , _reject ) {
var resolve = _resolve ;
var reject = _reject ;
mailvelope . getKeyring ( 'egroupware' ) . then ( function ( _keyring ) {
_keyring . addSyncHandler ( self . mailvelopeSyncHandlerObj ) ;
var options = {
restorePassword : restorePassword
} ;
_keyring . restoreBackupContainer ( selector , options ) . then ( function ( _restoreId ) {
var $restore _selector = jQuery ( 'iframe[src^="chrome-extension"],iframe[src^="about:blank?mvelo"]' ) ;
$restore _selector . css ( { position : 'absolute' , "z-index" : 1 } ) ;
resolve ( _restoreId ) ;
} , function ( _err ) {
reject ( _err ) ;
} ) ;
} , function ( _err ) {
reject ( _err ) ;
} ) ;
} ) ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Create a dialog to show all backup / restore options
*
* @ returns { undefined }
* /
2021-06-05 20:39:39 +02:00
mailvelopeCreateBackupRestoreDialog ( ) {
2020-01-15 08:47:33 +01:00
var self = this ;
var appname = egw . app _name ( ) ;
var menu = [
// Header row should be empty item 0
{ } ,
// Restore Keyring item 1
{ label : "Restore key" , image : "lock" , onclick : "app." + appname + ".mailvelopeCreateRestoreDialog('#_mvelo')" } ,
// Restore pass phrase item 2
{ label : "Restore password" , image : "password" , onclick : "app." + appname + ".mailvelopeCreateRestoreDialog('#_mvelo', true)" } ,
// Delete backup Key item 3
{ label : "Delete backup" , image : "delete" , onclick : "app." + appname + ".mailvelopeDeleteBackup" } ,
// Backup Key item 4
{ label : "Backup Key" , image : "save" , onclick : "app." + appname + ".mailvelopeCreateBackupDialog('#_mvelo', false)" }
] ;
var dialog = function ( _content , _callback ) {
2021-06-05 20:39:39 +02:00
return et2 _createWidget ( "dialog" , {
2020-01-15 08:47:33 +01:00
callback : function ( _button _id , _value ) {
if ( typeof _callback == "function" ) {
_callback . call ( this , _button _id , _value . value ) ;
}
} ,
title : egw . lang ( 'Backup/Restore' ) ,
buttons : [ { "button_id" : 'close' , "text" : egw . lang ( 'Close' ) , id : 'dialog[close]' , image : 'cancelled' , "default" : true } ] ,
value : {
content : {
menu : _content
}
} ,
template : egw . webserverUrl + '/api/templates/default/pgp_backup_restore.xet' ,
class : "pgp_backup_restore" ,
modal : true
} ) ;
} ;
if ( typeof mailvelope != 'undefined' ) {
mailvelope . getKeyring ( 'egroupware' ) . then ( function ( _keyring ) {
self . _mailvelopeBackupFileOperator ( undefined , 'GET' , function ( _data ) {
dialog ( menu ) ;
} , function ( ) {
// Remove delete item
menu . splice ( 3 , 1 ) ;
menu [ 3 ] [ 'onclick' ] = "app." + appname + ".mailvelopeCreateBackupDialog('#_mvelo', true)" ;
dialog ( menu ) ;
} ) ;
} , function ( ) {
mailvelope . createKeyring ( 'egroupware' ) . then ( function ( ) { dialog ( menu ) ; } ) ;
} ) ;
}
else {
this . mailvelopeInstallationOffer ( ) ;
}
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Create a dialog and offers installation option for installing mailvelope plugin
* plus it offers a video tutorials to get the user morte familiar with mailvelope
* /
2021-06-05 20:39:39 +02:00
mailvelopeInstallationOffer ( ) {
2020-01-15 08:47:33 +01:00
var buttons = [
{ "text" : egw . lang ( 'Install' ) , id : 'install' , image : 'check' , "default" : true } ,
{ "text" : egw . lang ( 'Close' ) , id : 'close' , image : 'cancelled' }
] ;
var dialog = function ( _content , _callback ) {
2021-06-05 20:39:39 +02:00
return et2 _createWidget ( "dialog" , {
2020-01-15 08:47:33 +01:00
callback : function ( _button _id , _value ) {
if ( typeof _callback == "function" ) {
_callback . call ( this , _button _id , _value . value ) ;
}
} ,
title : egw . lang ( 'PGP Encryption Installation' ) ,
buttons : buttons ,
dialog _type : 'info' ,
value : {
content : _content
} ,
template : egw . webserverUrl + '/api/templates/default/pgp_installation.xet' ,
class : "pgp_installation" ,
modal : true
//resizable:false,
} ) ;
} ;
var content = [
// Header row should be empty item 0
{ } ,
{ domain : this . egw . lang ( 'Add your domain as "%1" in options to list of email providers and enable API.' , '*.' + this . _mailvelopeDomain ( ) ) , video : "test" , control : "true" }
] ;
dialog ( content , function ( _button ) {
if ( _button == 'install' ) {
if ( typeof chrome != 'undefined' ) {
// ATM we are not able to trigger mailvelope installation directly
// since the installation should be triggered from the extension
// owner validate website (mailvelope.com), therefore, we just redirect
// user to chrome webstore to install mailvelope from there.
window . open ( 'https://chrome.google.com/webstore/detail/mailvelope/kajibbejlbohfaggdiogboambcijhkke' ) ;
}
else if ( typeof InstallTrigger != 'undefined' && InstallTrigger . enabled ( ) ) {
InstallTrigger . install ( { mailvelope : "https://download.mailvelope.com/releases/latest/mailvelope.firefox.xpi" } , function ( _url , _status ) {
if ( _status == 0 ) {
2021-06-05 20:39:39 +02:00
et2 _dialog . alert ( egw . lang ( 'Mailvelope addon installation succeded. Now you may configure the options.' ) ) ;
2020-01-15 08:47:33 +01:00
return ;
}
else {
2021-06-05 20:39:39 +02:00
et2 _dialog . alert ( egw . lang ( 'Mailvelope addon installation failed! Please try again.' ) ) ;
2020-01-15 08:47:33 +01:00
}
} ) ;
}
}
} ) ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Open ( or create ) "egroupware" keyring and call callback with it
*
* @ returns { Promise . < Keyring , Error > } Keyring or Error with message
* /
2021-06-05 20:39:39 +02:00
mailvelopeOpenKeyring ( ) {
2020-01-15 08:47:33 +01:00
var self = this ;
return new Promise ( function ( _resolve , _reject ) {
if ( self . mailvelope _keyring )
_resolve ( self . mailvelope _keyring ) ;
var resolve = _resolve ;
var reject = _reject ;
mailvelope . getKeyring ( 'egroupware' ) . then ( function ( _keyring ) {
self . mailvelope _keyring = _keyring ;
resolve ( _keyring ) ;
} , function ( _err ) {
mailvelope . createKeyring ( 'egroupware' ) . then ( function ( _keyring ) {
self . mailvelope _keyring = _keyring ;
var mvelo _settings _selector = self . mailvelope _iframe _selector
. split ( ',' ) . map ( function ( _val ) { return 'body>' + _val ; } ) . join ( ',' ) ;
mailvelope . createSettingsContainer ( 'body' , _keyring , {
email : self . egw . user ( 'account_email' ) ,
fullName : self . egw . user ( 'account_fullname' )
} ) . then ( function ( ) {
// make only Mailvelope settings dialog visible
jQuery ( mvelo _settings _selector ) . css ( { position : 'absolute' , top : 0 } ) ;
// add a close button, so we know when to offer storing public key to AB
jQuery ( '<button class="et2_button et2_button_text" id="mailvelope_close_settings">' + self . egw . lang ( 'Close' ) + '</button>' )
. css ( { position : 'absolute' , top : 8 , right : 8 , "z-index" : 2 } )
. click ( function ( ) {
// try fetching public key, to check user created onw
self . mailvelope _keyring . exportOwnPublicKey ( self . egw . user ( 'account_email' ) ) . then ( function ( _pubKey ) {
// CreateBackupDialog
self . mailvelopeCreateBackupDialog ( ) . then ( function ( _popupId ) {
jQuery ( 'iframe[src^="chrome-extension"],iframe[src^="about:blank?mvelo"]' ) . css ( { position : 'absolute' , "z-index" : 1 } ) ;
} , function ( _err ) {
egw . message ( _err ) ;
} ) ;
// if yes, hide settings dialog
jQuery ( mvelo _settings _selector ) . each ( function ( index , item ) {
if ( ! item . src . match ( /keyBackupDialog.html/ , 'ig' ) )
item . remove ( ) ;
} ) ;
jQuery ( 'button#mailvelope_close_settings' ) . remove ( ) ;
// offer user to store his public key to AB for other users to find
var buttons = [
{ button _id : 2 , text : 'Yes' , id : 'dialog[yes]' , image : 'check' , default : true } ,
{ button _id : 3 , text : 'No' , id : 'dialog[no]' , image : 'cancelled' }
] ;
if ( egw . user ( 'apps' ) . admin ) {
buttons . unshift ( {
button _id : 5 , text : 'Yes and allow non-admin users to do that too (recommended)' ,
id : 'dialog[yes_allow]' , image : 'check' , default : true
} ) ;
delete buttons [ 1 ] . default ;
}
2021-06-05 20:39:39 +02:00
et2 _dialog . show _dialog ( function ( _button _id ) {
if ( _button _id != et2 _dialog . NO _BUTTON ) {
2020-01-15 08:47:33 +01:00
var keys = { } ;
keys [ self . egw . user ( 'account_id' ) ] = _pubKey ;
2021-06-05 20:39:39 +02:00
self . egw . json ( 'addressbook.addressbook_bo.ajax_set_pgp_keys' , [ keys , _button _id != et2 _dialog . YES _BUTTON ? true : undefined ] ) . sendRequest ( )
2020-01-15 08:47:33 +01:00
. then ( function ( _data ) {
self . egw . message ( _data . response [ '0' ] . data ) ;
} ) ;
}
2021-06-05 20:39:39 +02:00
} , self . egw . lang ( 'It is recommended to store your public key in addressbook, so other users can write you encrypted mails.' ) , self . egw . lang ( 'Store your public key in Addressbook?' ) , { } , buttons , et2 _dialog . QUESTION _MESSAGE , undefined , self . egw ) ;
2020-01-15 08:47:33 +01:00
} , function ( _err ) {
self . egw . message ( _err . message + "\n\n" +
self . egw . lang ( "You will NOT be able to send or receive encrypted mails before completing that step!" ) , 'error' ) ;
} ) ;
} )
. appendTo ( 'body' ) ;
} ) ;
resolve ( _keyring ) ;
} , function ( _err ) {
reject ( _err ) ;
} ) ;
} ) ;
} ) ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Mailvelope uses Domain without first part : eg . "stylite.de" for "egw.stylite.de"
*
* @ returns { string }
* /
2021-06-05 20:39:39 +02:00
_mailvelopeDomain ( ) {
2020-01-15 08:47:33 +01:00
var parts = document . location . hostname . split ( '.' ) ;
if ( parts . length > 1 )
parts . shift ( ) ;
return parts . join ( '.' ) ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Check if we have a key for all recipients
*
* @ param { Array } _recipients
* @ returns { Promise . < Array , Error > } Array of recipients or Error with recipients without key
* /
2021-06-05 20:39:39 +02:00
mailvelopeGetCheckRecipients ( _recipients ) {
2020-01-15 08:47:33 +01:00
// replace rfc822 addresses with raw email, as Mailvelop does not like them and lowercase all email
var rfc822 _preg = /<([^'" <>]+)>$/ ;
var recipients = _recipients . map ( function ( _recipient ) {
var matches = _recipient . match ( rfc822 _preg ) ;
return matches ? matches [ 1 ] . toLowerCase ( ) : _recipient . toLowerCase ( ) ;
} ) ;
// check if we have keys for all recipients
var self = this ;
return new Promise ( function ( _resolve , _reject ) {
var resolve = _resolve ;
var reject = _reject ;
self . mailvelopeOpenKeyring ( ) . then ( function ( _keyring ) {
var keyring = _keyring ;
_keyring . validKeyForAddress ( recipients ) . then ( function ( _status ) {
var no _key = [ ] ;
for ( var email in _status ) {
if ( ! _status [ email ] )
no _key . push ( email ) ;
}
if ( no _key . length ) {
// server addressbook on server for missing public keys
self . egw . json ( 'addressbook.addressbook_bo.ajax_get_pgp_keys' , [ no _key ] ) . sendRequest ( ) . then ( function ( _data ) {
var data = _data . response [ '0' ] . data ;
var promises = [ ] ;
for ( var email in data ) {
promises . push ( keyring . importPublicKey ( data [ email ] ) . then ( function ( _result ) {
if ( _result == 'IMPORTED' || _result == 'UPDATED' ) {
no _key . splice ( no _key . indexOf ( email ) , 1 ) ;
}
} ) ) ;
}
Promise . all ( promises ) . then ( function ( ) {
if ( no _key . length ) {
reject ( new Error ( self . egw . lang ( 'No key for recipient:' ) + ' ' + no _key . join ( ', ' ) ) ) ;
}
else {
resolve ( recipients ) ;
}
} ) ;
} ) ;
}
else {
resolve ( recipients ) ;
}
} ) ;
} , function ( _err ) {
reject ( _err ) ;
} ) ;
} ) ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Check if the share action is enabled for this entry
*
* @ param { egwAction } _action
* @ param { egwActionObject [ ] } _entries
* @ param { egwActionObject } _target
* @ returns { boolean } if action is enabled
* /
2021-06-05 20:39:39 +02:00
is _share _enabled ( _action , _entries , _target ) {
2020-01-15 08:47:33 +01:00
return true ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* create a share - link for the given entry
*
* @ param { egwAction } _action egw actions
* @ param { egwActionObject [ ] } _senders selected nm row
* @ param { egwActionObject } _target Drag source . Not used here .
* @ param { Boolean } _writable Allow edit access from the share .
* @ param { Boolean } _files Allow access to files from the share .
* @ param { Function } _callback Callback with results
2020-03-25 03:34:04 +01:00
* @ param { Object } _extra Additional ( app - specific or special ) parameters
2020-01-15 08:47:33 +01:00
* @ returns { Boolean } returns false if not successful
* /
2021-06-05 20:39:39 +02:00
share _link ( _action , _senders , _target , _writable , _files , _callback , _extra ) {
2020-01-15 08:47:33 +01:00
var path = _senders [ 0 ] . id ;
if ( ! path ) {
return this . egw . message ( this . egw . lang ( 'Missing share path. Unable to create share.' ) , 'error' ) ;
}
switch ( _action . id ) {
case 'shareFilemanager' :
// Sharing a link to just files in filemanager
var id = path . split ( '::' ) ;
path = '/apps/' + id [ 0 ] + '/' + id [ 1 ] ;
}
if ( typeof _writable === 'undefined' && _action . parent && _action . parent . getActionById ( 'shareWritable' ) ) {
_writable = _action . parent . getActionById ( 'shareWritable' ) . checked || false ;
}
if ( typeof _files === 'undefined' && _action . parent && _action . parent . getActionById ( 'shareFiles' ) ) {
_files = _action . parent . getActionById ( 'shareFiles' ) . checked || false ;
}
2020-03-31 18:21:26 +02:00
if ( typeof _extra === 'undefined' ) {
_extra = { } ;
}
2020-03-25 03:34:04 +01:00
return egw . json ( 'EGroupware\\Api\\Sharing::ajax_create' , [ _action . id , path , _writable , _files , _extra ] , _callback ? _callback : this . _share _link _callback , this , true , this ) . sendRequest ( ) ;
2021-06-05 20:39:39 +02:00
}
share _merge ( _action , _senders , _target ) {
2020-01-15 08:47:33 +01:00
var parent = _action . parent . parent ;
var _writable = false ;
var _files = false ;
if ( parent && parent . getActionById ( 'shareWritable' ) ) {
_writable = parent . getActionById ( 'shareWritable' ) . checked || false ;
}
if ( parent && parent . getActionById ( 'shareFiles' ) ) {
_files = parent . getActionById ( 'shareFiles' ) . checked || false ;
}
// Share only works on one at a time
var promises = [ ] ;
for ( var i = 0 ; i < _senders . length ; i ++ ) {
promises . push ( new Promise ( function ( resolve , reject ) {
this . share _link ( _action , [ _senders [ i ] ] , _target , _writable , _files , resolve ) ;
} . bind ( this ) ) ) ;
}
// But merge into email can handle several
Promise . all ( promises . map ( function ( p ) { p . catch ( function ( e ) { console . log ( e ) ; } ) ; } ) )
. then ( function ( values ) {
// Process document after all shares created
return nm _action ( _action , _senders , _target ) ;
} ) ;
2021-06-05 20:39:39 +02:00
}
2020-01-15 08:47:33 +01:00
/ * *
* Share - link callback
* @ param { object } _data
* /
2021-06-05 20:39:39 +02:00
_share _link _callback ( _data ) {
2020-01-15 08:47:33 +01:00
if ( _data . msg || _data . share _link )
window . egw _refresh ( _data . msg , this . appname ) ;
var copy _link _to _clipboard = function ( evt ) {
var $target = jQuery ( evt . target ) ;
$target . select ( ) ;
try {
var successful = document . execCommand ( 'copy' ) ;
if ( successful ) {
egw . message ( 'Share link copied into clipboard' ) ;
return true ;
}
}
catch ( e ) { }
egw . message ( 'Failed to copy the link!' ) ;
} ;
jQuery ( "body" ) . on ( "click" , "[name=share_link]" , copy _link _to _clipboard ) ;
2021-06-05 20:39:39 +02:00
et2 _createWidget ( "dialog" , {
2020-01-15 08:47:33 +01:00
callback : function ( button _id , value ) {
jQuery ( "body" ) . off ( "click" , "[name=share_link]" , copy _link _to _clipboard ) ;
return true ;
} ,
title : _data . title ? _data . title : egw . lang ( "%1 Share Link" , _data . writable ? egw . lang ( "Writable" ) : egw . lang ( "Readonly" ) ) ,
template : _data . template ,
width : 450 ,
value : { content : { "share_link" : _data . share _link } }
} ) ;
2021-06-05 20:39:39 +02:00
}
2020-04-21 23:36:17 +02:00
/ * *
* Keep a list of all EgwApp instances
*
* This is not just the globals available in window . app , it also includes private instances as well
*
* @ private
* @ param app _obj
* /
2021-06-05 20:39:39 +02:00
static _register _instance ( app _obj ) {
2020-04-21 23:36:17 +02:00
// Reject improper objects
if ( ! app _obj . appname )
return ;
EgwApp . _instances . push ( app _obj ) ;
2021-06-05 20:39:39 +02:00
}
2020-04-21 23:36:17 +02:00
/ * *
* Iterator over all app instances
*
* Use for ( const app of EgwApp ) { ... } to iterate over all app objects .
* /
2021-06-05 20:39:39 +02:00
static [ Symbol . iterator ] ( ) {
2020-04-21 23:36:17 +02:00
return EgwApp . _instances [ Symbol . iterator ] ( ) ;
2021-06-05 20:39:39 +02:00
}
}
/ * *
* In some cases ( CRM ) a private , disconnected app instance is created instead of
* using the global . We want to be able to access them for observer ( ) & push ( ) , so
* we track all instances .
* /
EgwApp . _instances = [ ] ;
2021-06-11 15:21:18 +02:00
// EgwApp need to be global on window, as it's used to iterate through all EgwApp instances
window . EgwApp = EgwApp ;
2020-01-24 13:31:56 +01:00
//# sourceMappingURL=egw_app.js.map