2016-04-01 13:17:09 +00:00
/ *
*
2019-01-17 13:05:03 +00:00
* ( c ) Copyright Ascensio System SIA 2010 - 2019
2016-04-01 13:17:09 +00:00
*
* This program is a free software product . You can redistribute it and / or
* modify it under the terms of the GNU Affero General Public License ( AGPL )
* version 3 as published by the Free Software Foundation . In accordance with
* Section 7 ( a ) of the GNU AGPL its Section 15 shall be amended to the effect
* that Ascensio System SIA expressly excludes the warranty of non - infringement
* of any third - party rights .
*
* This program is distributed WITHOUT ANY WARRANTY ; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . For
* details , see the GNU AGPL at : http : //www.gnu.org/licenses/agpl-3.0.html
*
2019-01-17 13:00:34 +00:00
* You can contact Ascensio System SIA at 20 A - 12 Ernesta Birznieka - Upisha
* street , Riga , Latvia , EU , LV - 1050.
2016-04-01 13:17:09 +00:00
*
* The interactive user interfaces in modified source and object code versions
* of the Program must display Appropriate Legal Notices , as required under
* Section 5 of the GNU AGPL version 3.
*
* Pursuant to Section 7 ( b ) of the License you must retain the original Product
* logo when distributing the program . Pursuant to Section 7 ( e ) we decline to
* grant you any rights under trademark law for use of our trademarks .
*
* All the Product ' s GUI elements , including illustrations and icon sets , as
* well as technical writing content are licensed under the terms of the
* Creative Commons Attribution - ShareAlike 4.0 International . See the License
* terms at http : //creativecommons.org/licenses/by-sa/4.0/legalcode
*
* /
2016-03-11 00:48:53 +00:00
/ * *
* Header . js
*
* Created by Alexander Yuzhin on 2 / 14 / 14
2018-03-01 12:16:38 +00:00
* Copyright ( c ) 2018 Ascensio System SIA . All rights reserved .
2016-03-11 00:48:53 +00:00
*
* /
if ( Common === undefined )
var Common = { } ;
Common . Views = Common . Views || { } ;
define ( [
'backbone' ,
'text!common/main/lib/template/Header.template' ,
2016-09-26 10:54:25 +00:00
'core' ,
'common/main/lib/view/RenameDialog'
2016-03-11 00:48:53 +00:00
] , function ( Backbone , headerTemplate ) { 'use strict' ;
2017-03-01 13:33:12 +00:00
Common . Views . Header = Backbone . View . extend ( _ . extend ( function ( ) {
2017-03-16 10:20:07 +00:00
var storeUsers , appConfig ;
2022-02-04 11:51:13 +00:00
var $userList , $panelUsers , $btnUsers , $btnUserName ;
2019-04-08 15:40:08 +00:00
var _readonlyRights = false ;
2017-03-13 16:29:05 +00:00
var templateUserItem =
2018-03-02 14:48:24 +00:00
'<li id="<%= user.get("iid") %>" class="<% if (!user.get("online")) { %> offline <% } if (user.get("view")) {%> viewmode <% } %>">' +
2018-06-05 08:54:49 +00:00
'<div class="user-name">' +
'<div class="color" style="background-color: <%= user.get("color") %>;"></div>' +
'<label><%= fnEncode(user.get("username")) %></label>' +
'<% if (len>1) { %><label style="margin-left:3px;">(<%=len%>)</label><% } %>' +
'</div>' +
2017-03-13 16:29:05 +00:00
'</li>' ;
var templateUserList = _ . template (
'<ul>' +
2018-06-05 08:54:49 +00:00
'<% for (originalId in users) { %>' +
'<%= usertpl({user: users[originalId][0], fnEncode: fnEncode, len: users[originalId].length}) %>' +
'<% } %>' +
2017-03-13 16:29:05 +00:00
'</ul>' ) ;
var templateRightBox = '<section>' +
2017-08-15 11:44:59 +00:00
'<section id="box-doc-name">' +
2020-06-08 11:02:48 +00:00
// '<input type="text" id="rib-doc-name" spellcheck="false" data-can-copy="false" style="pointer-events: none;" disabled="disabled">' +
2022-02-03 12:06:09 +00:00
//'<label id="rib-doc-name" />' +
'<input id="rib-doc-name" autofill="off" autocomplete="off"/></input>' +
2017-08-15 11:44:59 +00:00
'</section>' +
2020-07-29 19:50:07 +00:00
'<section style="display: inherit;">' +
'<div class="hedset">' +
'<div class="btn-slot" id="slot-hbtn-edit"></div>' +
'<div class="btn-slot" id="slot-hbtn-print"></div>' +
'<div class="btn-slot" id="slot-hbtn-download"></div>' +
'</div>' +
2021-11-02 14:24:13 +00:00
'<div class="hedset" data-layout-name="header-users">' +
2020-07-29 19:50:07 +00:00
// '<span class="btn-slot text" id="slot-btn-users"></span>' +
2022-02-02 21:00:46 +00:00
'<section id="tlb-box-users" class="box-cousers dropdown">' +
'<div class="btn-users dropdown-toggle" data-toggle="dropdown" data-hint="0" data-hint-direction="bottom" data-hint-offset="big">' +
'<div class="inner-box-icon">' +
'<svg class=""><use xlink:href="#svg-icon-users"></use></svg>' +
'</div>' +
'<label class="caption"></label>' +
2020-07-29 19:50:07 +00:00
'</div>' +
'<div class="cousers-menu dropdown-menu">' +
'<label id="tlb-users-menu-descr"><%= tipUsers %></label>' +
'<div class="cousers-list"></div>' +
'</div>' +
'</section>' +
'</div>' +
2022-02-03 10:51:38 +00:00
'<div class="hedset">' +
2022-02-04 11:51:13 +00:00
'<div class="btn-slot" id="slot-btn-share"></div>' +
2020-07-29 19:50:07 +00:00
'</div>' +
'<div class="hedset">' +
2021-07-02 13:15:54 +00:00
'<div class="btn-slot" id="slot-btn-mode"></div>' +
2020-07-29 19:50:07 +00:00
'<div class="btn-slot" id="slot-btn-back"></div>' +
2020-12-14 19:45:30 +00:00
'<div class="btn-slot" id="slot-btn-favorite"></div>' +
2020-07-29 19:50:07 +00:00
'</div>' +
2020-12-29 07:39:00 +00:00
'<div class="hedset">' +
2022-02-04 09:13:13 +00:00
// '<div class="btn-slot slot-btn-user-name"></div>' +
'<button type="button" class="btn btn-header slot-btn-user-name hidden">' +
'<div class="color-user-name"></div>' +
'</button>' +
2022-02-04 18:14:12 +00:00
'<div class="btn-current-user hidden">' +
2022-02-04 09:13:13 +00:00
'<div class="color-user-name"></div>' +
2020-12-29 07:39:00 +00:00
'</div>' +
'</div>' +
2017-08-15 11:44:59 +00:00
'</section>' +
2017-03-01 13:33:12 +00:00
'</section>' ;
2017-04-22 14:51:12 +00:00
var templateLeftBox = '<section class="logo">' +
2020-06-05 13:10:48 +00:00
'<div id="header-logo"><i></i></div>' +
2017-03-13 16:29:05 +00:00
'</section>' ;
2018-02-03 13:10:38 +00:00
var templateTitleBox = '<section id="box-document-title">' +
2018-09-25 10:46:28 +00:00
'<div class="extra"></div>' +
2020-07-29 19:50:07 +00:00
'<div class="hedset">' +
2021-11-18 14:46:14 +00:00
'<div class="btn-slot" id="slot-btn-dt-save" data-layout-name="header-save"></div>' +
2018-02-03 13:10:38 +00:00
'<div class="btn-slot" id="slot-btn-dt-print"></div>' +
'<div class="btn-slot" id="slot-btn-dt-undo"></div>' +
'<div class="btn-slot" id="slot-btn-dt-redo"></div>' +
'</div>' +
2020-06-08 11:02:48 +00:00
'<div class="lr-separator" id="id-box-doc-name">' +
2022-01-11 00:35:54 +00:00
// '<label id="title-doc-name" /></label>' +
2022-01-13 23:25:47 +00:00
'<input id="title-doc-name" autofill="off" autocomplete="off"/></input>' +
2020-04-24 21:58:40 +00:00
'</div>' +
2022-02-03 20:14:59 +00:00
'<div class="hedset">' +
2022-02-04 09:13:13 +00:00
// '<div class="btn-slot slot-btn-user-name"></div>' +
'<button type="button" class="btn btn-header slot-btn-user-name hidden">' +
'<div class="color-user-name"></div>' +
'</button>' +
2022-02-04 18:14:12 +00:00
'<div class="btn-current-user hidden">' +
2022-02-04 09:13:13 +00:00
'<div class="color-user-name"></div>' +
2022-02-03 20:14:59 +00:00
'</div>' +
'</div>' +
2018-02-03 13:10:38 +00:00
'</section>' ;
2017-03-13 16:29:05 +00:00
function onResetUsers ( collection , opts ) {
2021-11-06 17:41:10 +00:00
var usercount = collection . getVisibleEditingCount ( ) ;
2017-03-13 16:29:05 +00:00
if ( $userList ) {
2022-02-02 21:46:51 +00:00
if ( usercount > 1 && appConfig && ( appConfig . isEdit || appConfig . isRestrictedEdit ) ) {
2017-03-13 16:29:05 +00:00
$userList . html ( templateUserList ( {
2021-11-06 17:41:10 +00:00
users : collection . chain ( ) . filter ( function ( item ) { return item . get ( 'online' ) && ! item . get ( 'view' ) && ! item . get ( 'hidden' ) } ) . groupBy ( function ( item ) { return item . get ( 'idOriginal' ) ; } ) . value ( ) ,
2017-03-13 16:29:05 +00:00
usertpl : _ . template ( templateUserItem ) ,
2020-08-04 19:20:20 +00:00
fnEncode : function ( username ) {
2021-03-26 11:29:16 +00:00
return Common . Utils . String . htmlEncode ( AscCommon . UserInfoParser . getParsedName ( username ) ) ;
2020-08-04 19:20:20 +00:00
}
2017-03-13 16:29:05 +00:00
} ) ) ;
2019-04-23 10:20:39 +00:00
$userList . scroller = new Common . UI . Scroller ( {
el : $userList . find ( 'ul' ) ,
useKeyboard : true ,
minScrollbarLength : 40 ,
alwaysVisibleY : true
} ) ;
2018-06-05 08:54:49 +00:00
$userList . scroller . update ( { minScrollbarLength : 40 , alwaysVisibleY : true } ) ;
2017-03-13 16:29:05 +00:00
} else {
$userList . empty ( ) ;
}
}
2021-11-06 17:41:10 +00:00
applyUsers ( usercount , collection . getVisibleEditingOriginalCount ( ) ) ;
2018-06-05 08:54:49 +00:00
} ;
function onUsersChanged ( model ) {
onResetUsers ( model . collection ) ;
2017-03-13 16:29:05 +00:00
} ;
2018-06-05 08:54:49 +00:00
function applyUsers ( count , originalCount ) {
2019-01-11 11:02:28 +00:00
if ( ! $btnUsers ) return ;
2022-02-02 21:46:51 +00:00
var has _edit _users = count > 1 && appConfig && ( appConfig . isEdit || appConfig . isRestrictedEdit ) ; // has other user(s) who edit document
2018-04-20 08:23:58 +00:00
if ( has _edit _users ) {
2017-03-13 16:29:05 +00:00
$panelUsers [ 'show' ] ( ) ;
2022-02-03 10:30:01 +00:00
$btnUsers . find ( '.caption' ) . html ( originalCount ) ;
2017-03-13 16:29:05 +00:00
} else {
2022-02-02 21:00:46 +00:00
$panelUsers [ 'hide' ] ( ) ;
2017-03-13 16:29:05 +00:00
}
}
2019-04-08 15:40:08 +00:00
function onLostEditRights ( ) {
_readonlyRights = true ;
2022-02-04 11:51:13 +00:00
this . btnShare && this . btnShare . setVisible ( false ) ;
2019-04-08 15:40:08 +00:00
}
2017-03-13 16:29:05 +00:00
function onUsersClick ( e ) {
2022-02-02 21:00:46 +00:00
var usertip = $btnUsers . data ( 'bs.tooltip' ) ;
if ( usertip ) {
if ( usertip . dontShow === undefined )
usertip . dontShow = true ;
2017-03-13 16:29:05 +00:00
2022-02-02 21:00:46 +00:00
usertip . hide ( ) ;
2017-03-13 16:29:05 +00:00
}
}
2020-04-24 21:58:40 +00:00
function onAppShowed ( config ) {
2021-12-28 00:00:54 +00:00
//config.isCrypted =true; //delete fore merge!
2020-07-28 20:24:51 +00:00
if ( this . labelDocName ) {
if ( config . isCrypted ) {
2022-01-13 00:29:02 +00:00
this . labelDocName . attr ( { 'style' : 'text-align: left;' } ) ;
2020-07-28 20:24:51 +00:00
this . labelDocName . before (
'<div class="inner-box-icon crypted">' +
'<svg class="icon"><use xlink:href="#svg-icon-crypted"></use></svg>' +
'</div>' ) ;
2022-01-13 00:29:02 +00:00
this . imgCrypted = this . labelDocName . parent ( ) . find ( '.crypted' ) ;
2020-07-28 20:24:51 +00:00
}
2021-04-12 16:13:31 +00:00
if ( ! config . isEdit || ! config . customization || ! config . customization . compactHeader ) {
var $parent = this . labelDocName . parent ( ) ;
var _left _width = $parent . position ( ) . left ,
_right _width = $parent . next ( ) . outerWidth ( ) ;
if ( _left _width < _right _width )
this . labelDocName . parent ( ) . css ( 'padding-left' , _right _width - _left _width ) ;
else this . labelDocName . parent ( ) . css ( 'padding-right' , _left _width - _right _width ) ;
}
2020-04-24 21:58:40 +00:00
}
}
2018-03-21 11:11:03 +00:00
2017-03-16 10:24:03 +00:00
function onAppReady ( mode ) {
appConfig = mode ;
var me = this ;
me . btnGoBack . on ( 'click' , function ( e ) {
2018-02-14 14:11:28 +00:00
Common . NotificationCenter . trigger ( 'goback' ) ;
2017-03-16 10:24:03 +00:00
} ) ;
2020-12-14 19:45:30 +00:00
me . btnFavorite . on ( 'click' , function ( e ) {
2020-12-17 09:50:40 +00:00
// wait for setFavorite method
// me.options.favorite = !me.options.favorite;
// me.btnFavorite.changeIcon(me.options.favorite ? {next: 'btn-in-favorite'} : {curr: 'btn-in-favorite'});
// me.btnFavorite.updateHint(!me.options.favorite ? me.textAddFavorite : me.textRemoveFavorite);
Common . NotificationCenter . trigger ( 'markfavorite' , ! me . options . favorite ) ;
2020-12-14 19:45:30 +00:00
} ) ;
2022-02-04 11:51:13 +00:00
if ( me . btnShare ) {
me . btnShare . on ( 'click' , function ( e ) {
Common . NotificationCenter . trigger ( 'collaboration:sharing' ) ;
} ) ;
me . btnShare . updateHint ( me . tipAccessRights ) ;
me . btnShare . setVisible ( ! _readonlyRights && appConfig && ( appConfig . sharingSettingsUrl && appConfig . sharingSettingsUrl . length || appConfig . canRequestSharingSettings ) ) ;
}
2017-05-04 12:36:42 +00:00
if ( me . logo )
2017-09-19 12:42:44 +00:00
me . logo . children ( 0 ) . on ( 'click' , function ( e ) {
2018-11-13 08:40:35 +00:00
var _url = ! ! me . branding && ! ! me . branding . logo && ( me . branding . logo . url !== undefined ) ?
2019-02-22 12:52:33 +00:00
me . branding . logo . url : '{{PUBLISHER_URL}}' ;
2018-11-13 08:40:35 +00:00
if ( _url ) {
var newDocumentPage = window . open ( _url ) ;
newDocumentPage && newDocumentPage . focus ( ) ;
}
2017-05-04 12:36:42 +00:00
} ) ;
2017-03-16 10:24:03 +00:00
2020-07-29 19:50:07 +00:00
if ( $panelUsers ) {
onResetUsers ( storeUsers ) ;
2017-03-16 10:24:03 +00:00
2020-07-29 19:50:07 +00:00
$panelUsers . on ( 'shown.bs.dropdown' , function ( ) {
$userList . scroller && $userList . scroller . update ( { minScrollbarLength : 40 , alwaysVisibleY : true } ) ;
} ) ;
2017-03-16 10:24:03 +00:00
2020-07-29 19:50:07 +00:00
$panelUsers . find ( '.cousers-menu' )
. on ( 'click' , function ( e ) { return false ; } ) ;
2017-03-16 10:24:03 +00:00
2021-11-06 17:41:10 +00:00
var editingUsers = storeUsers . getVisibleEditingCount ( ) ;
2020-07-29 19:50:07 +00:00
$btnUsers . tooltip ( {
2022-02-08 21:35:13 +00:00
title : me . tipUsers ,
2020-07-29 19:50:07 +00:00
placement : 'bottom' ,
html : true
} ) ;
$btnUsers . on ( 'click' , onUsersClick . bind ( me ) ) ;
2022-02-02 21:46:51 +00:00
$panelUsers [ ( editingUsers > 1 && appConfig && ( appConfig . isEdit || appConfig . isRestrictedEdit ) ) ? 'show' : 'hide' ] ( ) ;
2017-03-16 10:24:03 +00:00
}
2017-07-13 09:43:40 +00:00
2020-12-29 20:48:47 +00:00
if ( appConfig . user . guest && appConfig . canRenameAnonymous ) {
2022-02-03 20:14:59 +00:00
if ( me . btnUserName ) {
2020-12-29 07:39:00 +00:00
me . btnUserName . on ( 'click' , function ( e ) {
Common . NotificationCenter . trigger ( 'user:rename' ) ;
} ) ;
}
2020-12-11 10:08:13 +00:00
}
2018-01-24 10:57:58 +00:00
if ( me . btnPrint ) {
me . btnPrint . updateHint ( me . tipPrint + Common . Utils . String . platformKey ( 'Ctrl+P' ) ) ;
me . btnPrint . on ( 'click' , function ( e ) {
me . fireEvent ( 'print' , me ) ;
} ) ;
}
if ( me . btnSave ) {
me . btnSave . updateHint ( me . tipSave + Common . Utils . String . platformKey ( 'Ctrl+S' ) ) ;
me . btnSave . on ( 'click' , function ( e ) {
me . fireEvent ( 'save' , me ) ;
} ) ;
}
if ( me . btnUndo ) {
me . btnUndo . updateHint ( me . tipUndo + Common . Utils . String . platformKey ( 'Ctrl+Z' ) ) ;
me . btnUndo . on ( 'click' , function ( e ) {
me . fireEvent ( 'undo' , me ) ;
} ) ;
}
if ( me . btnRedo ) {
me . btnRedo . updateHint ( me . tipRedo + Common . Utils . String . platformKey ( 'Ctrl+Y' ) ) ;
me . btnRedo . on ( 'click' , function ( e ) {
me . fireEvent ( 'redo' , me ) ;
} ) ;
}
2017-07-13 09:43:40 +00:00
if ( ! mode . isEdit ) {
if ( me . btnDownload ) {
2017-08-07 14:10:03 +00:00
me . btnDownload . updateHint ( me . tipDownload ) ;
2017-07-13 09:43:40 +00:00
me . btnDownload . on ( 'click' , function ( e ) {
me . fireEvent ( 'downloadas' , [ 'original' ] ) ;
} ) ;
}
2017-07-21 10:41:56 +00:00
if ( me . btnEdit ) {
2017-07-21 10:44:07 +00:00
me . btnEdit . updateHint ( me . tipGoEdit ) ;
2017-07-21 10:41:56 +00:00
me . btnEdit . on ( 'click' , function ( e ) {
me . fireEvent ( 'go:editor' , me ) ;
} ) ;
}
2017-07-13 09:43:40 +00:00
}
2017-03-16 10:24:03 +00:00
}
2022-01-11 00:35:54 +00:00
function onFocusDocName ( e ) {
var me = this ;
2022-01-13 00:29:02 +00:00
me . imgCrypted && me . imgCrypted . attr ( 'hidden' , true ) ;
me . isSaveDocName = false ;
if ( me . withoutExt ) return ;
var name = me . cutDocName ( me . labelDocName . val ( ) ) ;
2022-01-11 00:35:54 +00:00
_ . delay ( function ( ) {
me . labelDocName . val ( name ) ;
} , 100 ) ;
2022-01-13 00:29:02 +00:00
me . withoutExt = true ;
2022-01-11 00:35:54 +00:00
}
2017-08-01 12:14:19 +00:00
function onDocNameKeyDown ( e ) {
var me = this ;
var name = me . labelDocName . val ( ) ;
if ( e . keyCode == Common . UI . Keys . RETURN ) {
name = name . trim ( ) ;
2022-01-13 00:29:02 +00:00
me . isSaveDocName = true ;
if ( ! _ . isEmpty ( name ) && me . cutDocName ( me . documentCaption ) !== name ) {
2017-08-01 12:14:19 +00:00
if ( /[\t*\+:\"<>?|\\\\/]/gim . test ( name ) ) {
_ . defer ( function ( ) {
Common . UI . error ( {
msg : ( new Common . Views . RenameDialog ) . txtInvalidName + "*+:\"<>?|\/"
, callback : function ( ) {
_ . delay ( function ( ) {
me . labelDocName . focus ( ) ;
2022-01-13 00:29:02 +00:00
me . isSaveDocName = true ;
2017-08-01 12:14:19 +00:00
} , 50 ) ;
}
} ) ;
2022-01-13 00:29:02 +00:00
//me.labelDocName.blur();
2017-08-01 12:14:19 +00:00
} )
2022-01-13 00:29:02 +00:00
} else
if ( me . withoutExt ) {
2022-01-13 16:44:46 +00:00
name = me . cutDocName ( name ) ;
2022-01-11 23:10:08 +00:00
me . options . wopi ? me . api . asc _wopi _renameFile ( name ) : Common . Gateway . requestRename ( name ) ;
2022-01-13 00:29:02 +00:00
name += me . fileExtention ;
me . labelDocName . val ( name ) ;
me . withoutExt = false ;
2017-08-01 12:14:19 +00:00
Common . NotificationCenter . trigger ( 'edit:complete' , me ) ;
}
2022-01-13 00:29:02 +00:00
2022-01-11 23:10:08 +00:00
} else {
2022-01-13 00:29:02 +00:00
2022-01-11 23:10:08 +00:00
Common . NotificationCenter . trigger ( 'edit:complete' , me ) ;
2017-08-01 12:14:19 +00:00
}
} else
if ( e . keyCode == Common . UI . Keys . ESC ) {
Common . NotificationCenter . trigger ( 'edit:complete' , this ) ;
} else {
2022-01-13 00:29:02 +00:00
me . labelDocName . attr ( 'size' , name . length + me . fileExtention . length > 10 ? name . length + me . fileExtention . length : 10 ) ;
2017-08-01 12:14:19 +00:00
}
}
2017-03-01 13:33:12 +00:00
return {
options : {
branding : { } ,
documentCaption : '' ,
canBack : false
} ,
el : '#header' ,
// Compile our stats template
template : _ . template ( headerTemplate ) ,
// Delegated events for creating new items, and clearing completed ones.
events : {
// 'click #header-logo': function (e) {}
} ,
initialize : function ( options ) {
var me = this ;
2018-03-28 10:37:41 +00:00
this . options = this . options ? _ . extend ( this . options , options ) : options ;
2017-03-01 13:33:12 +00:00
this . documentCaption = this . options . documentCaption ;
this . branding = this . options . customization ;
2017-04-12 08:55:46 +00:00
this . isModified = false ;
2017-03-01 13:33:12 +00:00
me . btnGoBack = new Common . UI . Button ( {
id : 'btn-goback' ,
2017-07-13 09:37:22 +00:00
cls : 'btn-header' ,
2019-11-27 16:43:44 +00:00
iconCls : 'toolbar__icon icon--inverse btn-goback' ,
2021-06-01 17:07:24 +00:00
dataHint : '0' ,
dataHintDirection : 'bottom' ,
dataHintOffset : 'big'
2017-03-01 13:33:12 +00:00
} ) ;
2016-03-11 00:48:53 +00:00
2017-03-29 09:01:34 +00:00
storeUsers = this . options . storeUsers ;
2017-03-13 16:29:05 +00:00
storeUsers . bind ( {
2018-06-05 08:54:49 +00:00
add : onUsersChanged ,
2017-03-13 16:29:05 +00:00
change : onUsersChanged ,
reset : onResetUsers
2017-03-01 13:33:12 +00:00
} ) ;
2020-12-14 19:45:30 +00:00
me . btnFavorite = new Common . UI . Button ( {
id : 'btn-favorite' ,
cls : 'btn-header' ,
2021-07-06 15:42:13 +00:00
iconCls : 'toolbar__icon icon--inverse btn-favorite' ,
dataHint : '0' ,
dataHintDirection : 'bottom' ,
dataHintOffset : 'big'
2020-12-14 19:45:30 +00:00
} ) ;
2019-05-13 11:45:17 +00:00
Common . NotificationCenter . on ( {
'app:ready' : function ( mode ) { Common . Utils . asyncCall ( onAppReady , me , mode ) ; } ,
2022-02-04 11:51:13 +00:00
'app:face' : function ( mode ) { Common . Utils . asyncCall ( onAppShowed , me , mode ) ; } ,
'collaboration:sharingdeny' : function ( mode ) { Common . Utils . asyncCall ( onLostEditRights , me , mode ) ; }
2018-03-21 11:11:03 +00:00
} ) ;
2021-10-04 15:42:14 +00:00
Common . NotificationCenter . on ( 'uitheme:changed' , this . changeLogo . bind ( this ) ) ;
2017-03-01 13:33:12 +00:00
} ,
render : function ( el , role ) {
$ ( el ) . html ( this . getPanel ( role ) ) ;
return this ;
} ,
2017-05-04 12:36:42 +00:00
getPanel : function ( role , config ) {
2018-02-03 13:10:38 +00:00
var me = this ;
2021-11-11 18:00:38 +00:00
function createTitleButton ( iconid , slot , disabled , hintDirection , hintOffset , hintTitle ) {
2018-02-03 13:10:38 +00:00
return ( new Common . UI . Button ( {
cls : 'btn-header' ,
2019-11-27 16:43:44 +00:00
iconCls : iconid ,
2021-05-20 17:02:11 +00:00
disabled : disabled === true ,
dataHint : '0' ,
2021-11-15 12:49:01 +00:00
dataHintDirection : hintDirection ? hintDirection : ( config . isDesktopApp ? 'right' : 'left' ) ,
dataHintOffset : hintOffset ? hintOffset : ( config . isDesktopApp ? '10, -10' : '10, 10' ) ,
2021-11-11 18:00:38 +00:00
dataHintTitle : hintTitle
2018-02-03 13:10:38 +00:00
} ) ) . render ( slot ) ;
}
2017-05-04 12:36:42 +00:00
if ( role == 'left' && ( ! config || ! config . isDesktopApp ) ) {
2017-03-13 16:29:05 +00:00
$html = $ ( templateLeftBox ) ;
2017-03-01 13:33:12 +00:00
this . logo = $html . find ( '#header-logo' ) ;
2017-09-18 11:38:24 +00:00
2021-10-04 15:42:14 +00:00
if ( this . branding && this . branding . logo && ( this . branding . logo . image || this . branding . logo . imageDark ) && this . logo ) {
var image = Common . UI . Themes . isDarkTheme ( ) ? ( this . branding . logo . imageDark || this . branding . logo . image ) : ( this . branding . logo . image || this . branding . logo . imageDark ) ;
this . logo . html ( '<img src="' + image + '" style="max-width:100px; max-height:20px; margin: 0;"/>' ) ;
2017-09-18 11:38:24 +00:00
this . logo . css ( { 'background-image' : 'none' , width : 'auto' } ) ;
2018-11-13 08:40:35 +00:00
( this . branding . logo . url || this . branding . logo . url === undefined ) && this . logo . addClass ( 'link' ) ;
2017-09-18 11:38:24 +00:00
}
2017-03-01 13:33:12 +00:00
return $html ;
} else
if ( role == 'right' ) {
2017-04-21 08:15:04 +00:00
var $html = $ ( _ . template ( templateRightBox ) ( {
2017-03-13 16:29:05 +00:00
tipUsers : this . labelCoUsersDescr ,
2022-02-02 21:00:46 +00:00
textShare : this . textShare
2017-03-13 16:29:05 +00:00
} ) ) ;
2017-03-01 13:33:12 +00:00
2018-02-03 13:10:38 +00:00
if ( ! me . labelDocName ) {
me . labelDocName = $html . find ( '#rib-doc-name' ) ;
if ( me . documentCaption ) {
2021-12-28 00:00:54 +00:00
me . labelDocName . val ( me . documentCaption ) ;
2018-02-03 13:10:38 +00:00
}
2020-04-22 16:46:53 +00:00
} else {
$html . find ( '#rib-doc-name' ) . hide ( ) ;
2017-08-02 11:52:52 +00:00
}
if ( ! _ . isUndefined ( this . options . canRename ) ) {
this . setCanRename ( this . options . canRename ) ;
}
2018-02-03 13:10:38 +00:00
if ( this . options . canBack === true ) {
me . btnGoBack . render ( $html . find ( '#slot-btn-back' ) ) ;
} else {
$html . find ( '#slot-btn-back' ) . hide ( ) ;
2018-01-24 10:57:58 +00:00
}
2017-03-01 13:33:12 +00:00
2020-12-15 14:28:12 +00:00
if ( this . options . favorite !== undefined && this . options . favorite !== null ) {
2020-12-14 19:45:30 +00:00
me . btnFavorite . render ( $html . find ( '#slot-btn-favorite' ) ) ;
me . btnFavorite . changeIcon ( ! ! me . options . favorite ? { next : 'btn-in-favorite' } : { curr : 'btn-in-favorite' } ) ;
me . btnFavorite . updateHint ( ! me . options . favorite ? me . textAddFavorite : me . textRemoveFavorite ) ;
} else {
$html . find ( '#slot-btn-favorite' ) . hide ( ) ;
}
2017-07-13 09:43:40 +00:00
if ( ! config . isEdit ) {
2018-02-03 13:10:38 +00:00
if ( ( config . canDownload || config . canDownloadOrigin ) && ! config . isOffline )
2021-10-08 17:00:17 +00:00
this . btnDownload = createTitleButton ( 'toolbar__icon icon--inverse btn-download' , $html . findById ( '#slot-hbtn-download' ) , undefined , 'bottom' , 'big' ) ;
2017-07-13 09:43:40 +00:00
2018-02-03 13:10:38 +00:00
if ( config . canPrint )
2021-11-11 18:00:38 +00:00
this . btnPrint = createTitleButton ( 'toolbar__icon icon--inverse btn-print' , $html . findById ( '#slot-hbtn-print' ) , undefined , 'bottom' , 'big' , 'P' ) ;
2017-07-21 10:41:56 +00:00
2018-02-03 13:10:38 +00:00
if ( config . canEdit && config . canRequestEditRights )
2021-10-08 17:00:17 +00:00
this . btnEdit = createTitleButton ( 'toolbar__icon icon--inverse btn-edit' , $html . findById ( '#slot-hbtn-edit' ) , undefined , 'bottom' , 'big' ) ;
2017-07-13 09:43:40 +00:00
}
2020-12-29 07:39:00 +00:00
if ( ! config . isEdit || config . customization && ! ! config . customization . compactHeader ) {
2022-02-04 09:13:13 +00:00
if ( config . user . guest && config . canRenameAnonymous ) {
me . btnUserName = new Common . UI . Button ( {
el : $html . findById ( '.slot-btn-user-name' ) ,
cls : 'btn-header' ,
dataHint : '0' ,
dataHintDirection : 'bottom' ,
dataHintOffset : 'big' ,
visible : true
} ) ;
me . btnUserName . cmpEl . removeClass ( 'hidden' ) ;
} else {
2020-12-29 07:39:00 +00:00
me . elUserName = $html . find ( '.btn-current-user' ) ;
me . elUserName . removeClass ( 'hidden' ) ;
}
2022-02-04 09:13:13 +00:00
$btnUserName = $html . find ( '.color-user-name' ) ;
2020-12-29 07:39:00 +00:00
me . setUserName ( me . options . userName ) ;
}
2022-02-04 11:51:13 +00:00
if ( ! _readonlyRights && config && ( config . sharingSettingsUrl && config . sharingSettingsUrl . length || config . canRequestSharingSettings ) ) {
me . btnShare = new Common . UI . Button ( {
cls : 'btn-header btn-header-share' ,
iconCls : 'toolbar__icon icon--inverse btn-users-share' ,
caption : me . textShare ,
dataHint : '0' ,
dataHintDirection : 'bottom' ,
dataHintOffset : 'big'
} ) ;
me . btnShare . render ( $html . find ( '#slot-btn-share' ) ) ;
} else {
$html . find ( '#slot-btn-share' ) . hide ( ) ;
}
2017-03-13 16:29:05 +00:00
$userList = $html . find ( '.cousers-list' ) ;
$panelUsers = $html . find ( '.box-cousers' ) ;
2022-02-02 21:00:46 +00:00
$btnUsers = $panelUsers . find ( '> .btn-users' ) ;
2017-03-13 16:29:05 +00:00
$panelUsers . hide ( ) ;
2018-02-03 13:10:38 +00:00
return $html ;
} else
if ( role == 'title' ) {
var $html = $ ( _ . template ( templateTitleBox ) ( ) ) ;
! ! me . labelDocName && me . labelDocName . hide ( ) . off ( ) ; // hide document title if it was created in right box
2020-04-27 16:12:08 +00:00
me . labelDocName = $html . find ( '#title-doc-name' ) ;
2021-12-28 00:00:54 +00:00
me . labelDocName . val ( me . documentCaption ) ;
2022-01-13 00:29:02 +00:00
me . options . wopi && me . labelDocName . attr ( 'maxlength' , me . options . wopi . FileNameMaxLength ) ;
2018-02-03 13:10:38 +00:00
2022-02-04 09:13:13 +00:00
if ( config . user . guest && config . canRenameAnonymous ) {
me . btnUserName = new Common . UI . Button ( {
el : $html . findById ( '.slot-btn-user-name' ) ,
cls : 'btn-header' ,
dataHint : '0' ,
dataHintDirection : 'bottom' ,
dataHintOffset : 'big' ,
visible : true
} ) ;
me . btnUserName . cmpEl . removeClass ( 'hidden' ) ;
}
2022-02-03 20:14:59 +00:00
else {
me . elUserName = $html . find ( '.btn-current-user' ) ;
me . elUserName . removeClass ( 'hidden' ) ;
}
2022-02-04 09:13:13 +00:00
$btnUserName = $html . find ( '.color-user-name' ) ;
2018-02-03 13:10:38 +00:00
me . setUserName ( me . options . userName ) ;
if ( config . canPrint && config . isEdit ) {
2021-11-11 18:00:38 +00:00
me . btnPrint = createTitleButton ( 'toolbar__icon icon--inverse btn-print' , $html . findById ( '#slot-btn-dt-print' ) , true , undefined , undefined , 'P' ) ;
2018-02-03 13:10:38 +00:00
}
2021-11-11 18:00:38 +00:00
me . btnSave = createTitleButton ( 'toolbar__icon icon--inverse btn-save' , $html . findById ( '#slot-btn-dt-save' ) , true , undefined , undefined , 'S' ) ;
me . btnUndo = createTitleButton ( 'toolbar__icon icon--inverse btn-undo' , $html . findById ( '#slot-btn-dt-undo' ) , true , undefined , undefined , 'Z' ) ;
me . btnRedo = createTitleButton ( 'toolbar__icon icon--inverse btn-redo' , $html . findById ( '#slot-btn-dt-redo' ) , true , undefined , undefined , 'Y' ) ;
2018-02-03 13:10:38 +00:00
2018-02-28 13:24:48 +00:00
if ( me . btnSave . $icon . is ( 'svg' ) ) {
2019-11-05 12:28:33 +00:00
me . btnSave . $icon . addClass ( 'icon-save btn-save' ) ;
2018-02-28 13:24:48 +00:00
var _create _use = function ( extid , intid ) {
var _use = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'use' ) ;
_use . setAttributeNS ( 'http://www.w3.org/1999/xlink' , 'xlink:href' , extid ) ;
_use . setAttribute ( 'id' , intid ) ;
return $ ( _use ) ;
} ;
_create _use ( '#svg-btn-save-coauth' , 'coauth' ) . appendTo ( me . btnSave . $icon ) ;
_create _use ( '#svg-btn-save-sync' , 'sync' ) . appendTo ( me . btnSave . $icon ) ;
}
2017-03-01 13:33:12 +00:00
return $html ;
}
} ,
setVisible : function ( visible ) {
// visible
// ? this.show()
// : this.hide();
} ,
setBranding : function ( value ) {
var element ;
this . branding = value ;
2018-02-07 08:11:58 +00:00
if ( value ) {
2021-10-04 15:42:14 +00:00
if ( value . logo && ( value . logo . image || value . logo . imageDark ) ) {
var image = Common . UI . Themes . isDarkTheme ( ) ? ( value . logo . imageDark || value . logo . image ) : ( value . logo . image || value . logo . imageDark ) ;
2018-02-07 08:11:58 +00:00
element = $ ( '#header-logo' ) ;
if ( element ) {
2021-10-04 15:42:14 +00:00
element . html ( '<img src="' + image + '" style="max-width:100px; max-height:20px; margin: 0;"/>' ) ;
2018-02-07 08:11:58 +00:00
element . css ( { 'background-image' : 'none' , width : 'auto' } ) ;
2018-11-13 08:40:35 +00:00
( value . logo . url || value . logo . url === undefined ) && element . addClass ( 'link' ) ;
2018-02-07 08:11:58 +00:00
}
}
2016-09-26 10:54:25 +00:00
}
2017-03-01 13:33:12 +00:00
} ,
2021-10-04 15:42:14 +00:00
changeLogo : function ( ) {
var value = this . branding ;
2021-10-05 21:29:27 +00:00
if ( value && value . logo && value . logo . image && value . logo . imageDark && ( value . logo . image !== value . logo . imageDark ) ) { // change logo when image and imageDark are different
2021-10-04 15:42:14 +00:00
var image = Common . UI . Themes . isDarkTheme ( ) ? ( value . logo . imageDark || value . logo . image ) : ( value . logo . image || value . logo . imageDark ) ;
$ ( '#header-logo img' ) . attr ( 'src' , image ) ;
}
} ,
2017-04-12 08:55:46 +00:00
setDocumentCaption : function ( value ) {
2017-03-01 13:33:12 +00:00
! value && ( value = '' ) ;
this . documentCaption = value ;
2022-01-13 00:29:02 +00:00
var idx = this . documentCaption . lastIndexOf ( '.' ) ;
if ( idx > 0 )
this . fileExtention = this . documentCaption . substring ( idx ) ;
2017-04-12 08:55:46 +00:00
this . isModified && ( value += '*' ) ;
2017-08-01 12:14:19 +00:00
if ( this . labelDocName ) {
2021-12-28 00:00:54 +00:00
this . labelDocName . val ( value ) ;
2017-08-15 11:44:59 +00:00
// this.labelDocName.attr('size', value.length);
2022-01-11 00:35:54 +00:00
this . setCanRename ( this . options . canRename ) ;
2017-08-01 12:14:19 +00:00
2022-01-11 00:35:54 +00:00
//this.setCanRename(true);
2017-08-01 12:14:19 +00:00
}
2017-03-01 13:33:12 +00:00
return value ;
} ,
getDocumentCaption : function ( ) {
return this . documentCaption ;
} ,
setDocumentChanged : function ( changed ) {
2017-04-12 08:55:46 +00:00
this . isModified = changed ;
2017-08-08 08:17:18 +00:00
var _name = this . documentCaption ;
2017-03-01 13:33:12 +00:00
changed && ( _name += '*' ) ;
2021-12-28 00:00:54 +00:00
this . labelDocName . val ( _name ) ;
2017-03-01 13:33:12 +00:00
} ,
2018-03-23 13:00:57 +00:00
setCanBack : function ( value , text ) {
2018-02-03 13:10:38 +00:00
this . options . canBack = value ;
2017-03-01 13:33:12 +00:00
this . btnGoBack [ value ? 'show' : 'hide' ] ( ) ;
2018-03-23 13:00:57 +00:00
if ( value )
this . btnGoBack . updateHint ( ( text && typeof text == 'string' ) ? text : this . textBack ) ;
2018-02-03 13:10:38 +00:00
return this ;
2017-03-01 13:33:12 +00:00
} ,
getCanBack : function ( ) {
2018-02-03 13:10:38 +00:00
return this . options . canBack ;
2017-03-01 13:33:12 +00:00
} ,
2020-12-14 19:45:30 +00:00
setFavorite : function ( value ) {
this . options . favorite = value ;
2020-12-15 14:28:12 +00:00
this . btnFavorite [ value !== undefined && value !== null ? 'show' : 'hide' ] ( ) ;
2020-12-14 19:45:30 +00:00
this . btnFavorite . changeIcon ( ! ! value ? { next : 'btn-in-favorite' } : { curr : 'btn-in-favorite' } ) ;
this . btnFavorite . updateHint ( ! value ? this . textAddFavorite : this . textRemoveFavorite ) ;
return this ;
} ,
getFavorite : function ( ) {
return this . options . favorite ;
} ,
2017-03-01 13:33:12 +00:00
setCanRename : function ( rename ) {
2022-02-03 12:06:09 +00:00
//rename = true; //comment out for merge
2017-08-01 12:14:19 +00:00
2017-08-02 11:52:52 +00:00
var me = this ;
me . options . canRename = rename ;
if ( me . labelDocName ) {
var label = me . labelDocName ;
if ( rename ) {
label . removeAttr ( 'disabled' ) . tooltip ( {
title : me . txtRename ,
placement : 'cursor' }
) ;
2017-08-15 11:44:59 +00:00
label . on ( {
'keydown' : onDocNameKeyDown . bind ( this ) ,
2022-01-11 00:35:54 +00:00
'focus' : onFocusDocName . bind ( this ) ,
2017-08-15 11:44:59 +00:00
'blur' : function ( e ) {
2022-01-13 00:29:02 +00:00
me . imgCrypted && me . imgCrypted . attr ( 'hidden' , false ) ;
if ( ! me . isSaveDocName ) {
me . labelDocName . val ( me . documentCaption ) ;
me . withoutExt = false ;
}
2022-01-13 16:13:44 +00:00
} ,
'paste' : function ( e ) {
setTimeout ( function ( ) {
var name = me . cutDocName ( me . labelDocName . val ( ) ) ;
me . labelDocName . val ( name ) ;
me . labelDocName . attr ( 'size' , name . length + me . fileExtention . length > 10 ? name . length + me . fileExtention . length : 10 ) ;
} ) ;
2017-08-15 11:44:59 +00:00
}
} ) ;
2017-08-02 11:52:52 +00:00
} else {
2017-08-15 11:44:59 +00:00
label . off ( ) ;
2017-08-02 11:52:52 +00:00
label . attr ( 'disabled' , true ) ;
var tip = label . data ( 'bs.tooltip' ) ;
if ( tip ) {
tip . options . title = '' ;
tip . setContent ( ) ;
}
2017-08-01 12:14:19 +00:00
}
2017-08-08 08:17:18 +00:00
label . attr ( 'data-can-copy' , rename ) ;
2017-08-01 12:14:19 +00:00
}
2017-03-01 13:33:12 +00:00
} ,
2022-01-13 00:29:02 +00:00
cutDocName : function ( name ) {
2022-01-13 16:13:44 +00:00
if ( name . length <= this . fileExtention . length ) return ;
2022-01-13 00:29:02 +00:00
var idx = name . length - this . fileExtention . length ;
return ( name . substring ( idx ) == this . fileExtention ) ? name . substring ( 0 , idx ) : name ;
} ,
2018-02-03 13:10:38 +00:00
setUserName : function ( name ) {
2022-02-03 20:14:59 +00:00
this . options . userName = name ;
if ( this . btnUserName ) {
this . btnUserName . updateHint ( name ) ;
} else if ( this . elUserName ) {
this . elUserName . tooltip ( {
title : Common . Utils . String . htmlEncode ( name ) ,
placement : 'cursor' ,
html : true
} ) ;
2018-02-03 13:10:38 +00:00
}
2022-02-04 09:13:13 +00:00
$btnUserName && $btnUserName . text ( this . getInitials ( name ) ) ;
2018-02-03 13:10:38 +00:00
return this ;
2018-01-24 10:57:58 +00:00
} ,
getButton : function ( type ) {
if ( type == 'save' )
return this . btnSave ;
2020-10-15 14:14:06 +00:00
else if ( type == 'users' )
return $panelUsers ;
2022-02-02 21:00:46 +00:00
else if ( type == 'share' )
2022-02-04 11:51:13 +00:00
return this . btnShare ;
2018-01-24 10:57:58 +00:00
} ,
2018-02-22 21:19:40 +00:00
lockHeaderBtns : function ( alias , lock ) {
var me = this ;
if ( alias == 'users' ) {
2022-02-02 21:00:46 +00:00
if ( lock ) {
$btnUsers . addClass ( 'disabled' ) . attr ( 'disabled' , 'disabled' ) ;
} else {
2021-10-08 17:00:17 +00:00
$btnUsers . removeClass ( 'disabled' ) . removeAttr ( 'disabled' ) ;
2022-02-04 11:51:13 +00:00
}
if ( me . btnShare ) {
me . btnShare . setDisabled ( lock ) ;
2022-02-02 21:00:46 +00:00
}
2021-03-26 22:21:17 +00:00
} else if ( alias == 'rename-user' ) {
2022-02-03 20:14:59 +00:00
if ( me . btnUserName ) {
2021-03-26 22:21:17 +00:00
me . btnUserName . setDisabled ( lock ) ;
}
2018-02-22 21:19:40 +00:00
} else {
2018-07-30 14:08:33 +00:00
var _lockButton = function ( btn ) {
2018-02-22 21:19:40 +00:00
if ( btn ) {
if ( lock ) {
btn . keepState = {
disabled : btn . isDisabled ( )
} ;
2018-03-21 11:11:03 +00:00
btn . setDisabled ( true ) ;
2018-02-22 21:19:40 +00:00
} else {
2018-04-18 12:30:09 +00:00
btn . setDisabled ( btn . keepState && btn . keepState . disabled || lock ) ;
2018-03-21 11:11:03 +00:00
delete btn . keepState ;
2018-02-22 21:19:40 +00:00
}
}
2018-07-30 14:08:33 +00:00
} ;
2018-02-22 21:19:40 +00:00
2018-03-21 11:11:03 +00:00
switch ( alias ) {
case 'undo' : _lockButton ( me . btnUndo ) ; break ;
case 'redo' : _lockButton ( me . btnRedo ) ; break ;
default : break ;
2018-02-22 21:19:40 +00:00
}
}
} ,
2022-02-04 09:13:13 +00:00
getInitials : function ( name ) {
var fio = name . split ( ' ' ) ;
var initials = fio [ 0 ] . substring ( 0 , 1 ) . toUpperCase ( ) ;
for ( var i = fio . length - 1 ; i > 0 ; i -- ) {
if ( fio [ i ] [ 0 ] !== '(' && fio [ i ] [ 0 ] !== ')' ) {
initials += fio [ i ] . substring ( 0 , 1 ) . toUpperCase ( ) ;
break ;
}
}
return initials ;
2018-03-21 11:28:28 +00:00
} ,
2017-03-01 13:33:12 +00:00
textBack : 'Go to Documents' ,
2017-04-19 12:09:44 +00:00
txtRename : 'Rename' ,
txtAccessRights : 'Change access rights' ,
tipAccessRights : 'Manage document access rights' ,
labelCoUsersDescr : 'Document is currently being edited by several users.' ,
2017-07-21 10:44:07 +00:00
tipViewUsers : 'View users and manage document access rights' ,
2022-02-08 21:35:13 +00:00
tipUsers : 'View users' ,
2017-07-21 10:44:07 +00:00
tipDownload : 'Download file' ,
tipPrint : 'Print file' ,
2018-01-24 10:57:58 +00:00
tipGoEdit : 'Edit current file' ,
tipSave : 'Save' ,
tipUndo : 'Undo' ,
2018-03-21 11:28:28 +00:00
tipRedo : 'Redo' ,
textCompactView : 'Hide Toolbar' ,
2021-08-16 14:22:13 +00:00
textHideStatusBar : 'Combine sheet and status bars' ,
2018-03-21 11:28:28 +00:00
textHideLines : 'Hide Rulers' ,
textZoom : 'Zoom' ,
textAdvSettings : 'Advanced Settings' ,
2020-12-14 19:45:30 +00:00
tipViewSettings : 'View Settings' ,
textRemoveFavorite : 'Remove from Favorites' ,
2021-04-26 16:49:10 +00:00
textAddFavorite : 'Mark as favorite' ,
2022-02-02 21:00:46 +00:00
textHideNotes : 'Hide Notes' ,
textShare : 'Share'
2017-03-01 13:33:12 +00:00
}
} ( ) , Common . Views . Header || { } ) )
2016-03-11 00:48:53 +00:00
} ) ;