define ( [
'jquery' ,
'/common/toolbar.js' ,
'json.sortify' ,
'/bower_components/nthen/index.js' ,
'/common/sframe-common.js' ,
'/common/common-interface.js' ,
'/common/common-hash.js' ,
'/common/common-util.js' ,
'/common/common-ui-elements.js' ,
'/common/common-feedback.js' ,
'/common/hyperscript.js' ,
'/api/config' ,
'/customize/messages.js' ,
'/customize/application_config.js' ,
'/bower_components/chainpad/chainpad.dist.js' ,
'/file/file-crypto.js' ,
'/common/onlyoffice/history.js' ,
'/common/onlyoffice/oocell_base.js' ,
'/common/onlyoffice/oodoc_base.js' ,
'/common/onlyoffice/ooslide_base.js' ,
'/common/outer/worker-channel.js' ,
'/bower_components/file-saver/FileSaver.min.js' ,
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css' ,
'less!/bower_components/components-font-awesome/css/font-awesome.min.css' ,
'less!/common/onlyoffice/app-oo.less' ,
] , function (
$ ,
Toolbar ,
JSONSortify ,
nThen ,
SFCommon ,
UI ,
Hash ,
Util ,
UIElements ,
Feedback ,
h ,
ApiConfig ,
Messages ,
AppConfig ,
ChainPad ,
FileCrypto ,
History ,
EmptyCell ,
EmptyDoc ,
EmptySlide ,
Channel )
{
var saveAs = window . saveAs ;
var Nacl = window . nacl ;
Messages . oo _exportChrome = "Your browser cannot handle conversion to and from Microsoft Office formats. We suggest using a recent version of Firefox or Chrome." ; // XXX
Messages . oo _importBin = "Click OK to import CryptPad's internal .bin format." ; // XXX
var APP = window . APP = {
$ : $ ,
urlArgs : Util . find ( ApiConfig , [ 'requireConf' , 'urlArgs' ] )
} ;
var CHECKPOINT _INTERVAL = 100 ;
var DISPLAY _RESTORE _BUTTON = false ;
var NEW _VERSION = 4 ;
var PENDING _TIMEOUT = 30000 ;
var CURRENT _VERSION = 'v4' ;
//var READONLY_REFRESH_TO = 15000;
var debug = function ( x , type ) {
if ( ! window . CP _DEV _MODE ) { return ; }
console . debug ( x , type ) ;
} ;
var stringify = function ( obj ) {
return JSONSortify ( obj ) ;
} ;
var supportsXLSX = function ( ) {
//if (true) { return false; } // XXX
return ! ( typeof ( Atomics ) === "undefined" || typeof ( SharedArrayBuffer ) === "undefined" ) ;
} ;
var toolbar ;
var cursor ;
var andThen = function ( common ) {
var Title ;
var sframeChan = common . getSframeChannel ( ) ;
var metadataMgr = common . getMetadataMgr ( ) ;
var privateData = metadataMgr . getPrivateData ( ) ;
var readOnly = false ;
var offline = false ;
var ooLoaded = false ;
var pendingChanges = { } ;
var config = { } ;
var content = {
hashes : { } ,
ids : { } ,
mediasSources : { } ,
version : privateData . ooForceVersion ? Number ( privateData . ooForceVersion ) : NEW _VERSION
} ;
var oldHashes = { } ;
var oldIds = { } ;
var oldLocks = { } ;
var myUniqueOOId ;
var myOOId ;
var sessionId = Hash . createChannelId ( ) ;
var cpNfInner ;
var evOnPatch = Util . mkEvent ( ) ;
var evOnSync = Util . mkEvent ( ) ;
// This structure is used for caching media data and blob urls for each media cryptpad url
var mediasData = { } ;
var startOO = function ( ) { } ;
var getMediasSources = APP . getMediasSources = function ( ) {
content . mediasSources = content . mediasSources || { } ;
return content . mediasSources ;
} ;
var getId = function ( ) {
return metadataMgr . getNetfluxId ( ) + '-' + privateData . clientId ;
} ;
var getEditor = function ( ) {
if ( ! window . frames || ! window . frames [ 0 ] ) { return ; }
return window . frames [ 0 ] . editor || window . frames [ 0 ] . editorCell ;
} ;
var setEditable = function ( state , force ) {
$ ( '#cp-app-oo-editor' ) . find ( '#cp-app-oo-offline' ) . remove ( ) ;
/ *
try {
getEditor ( ) . asc _setViewMode ( ! state ) ;
//window.frames[0].editor.setViewModeDisconnect(true);
} catch ( e ) { }
* /
if ( ! state && ( ! readOnly || force ) ) {
$ ( '#cp-app-oo-editor' ) . append ( h ( 'div#cp-app-oo-offline' ) ) ;
}
} ;
var deleteOffline = function ( ) {
var ids = content . ids ;
var users = Object . keys ( metadataMgr . getMetadata ( ) . users ) ;
Object . keys ( ids ) . forEach ( function ( id ) {
var nId = id . slice ( 0 , 32 ) ;
if ( users . indexOf ( nId ) === - 1 ) {
delete ids [ id ] ;
}
} ) ;
APP . onLocal ( ) ;
} ;
var isRegisteredUserOnline = function ( ) {
var users = metadataMgr . getMetadata ( ) . users || { } ;
return Object . keys ( users ) . some ( function ( id ) {
return users [ id ] && users [ id ] . curvePublic ;
} ) ;
} ;
var isUserOnline = function ( ooid ) {
// Remove ids for users that have left the channel
deleteOffline ( ) ;
var ids = content . ids ;
// Check if the provided id is in the ID list
return Object . keys ( ids ) . some ( function ( id ) {
return ooid === ids [ id ] . ooid ;
} ) ;
} ;
var getUserIndex = function ( ) {
var i = 1 ;
var ids = content . ids || { } ;
Object . keys ( ids ) . forEach ( function ( k ) {
if ( ids [ k ] && ids [ k ] . index && ids [ k ] . index >= i ) {
i = ids [ k ] . index + 1 ;
}
} ) ;
return i ;
} ;
var setMyId = function ( ) {
// Remove ids for users that have left the channel
deleteOffline ( ) ;
var ids = content . ids ;
if ( ! myOOId ) {
myOOId = Util . createRandomInteger ( ) ;
// f: function used in .some(f) but defined outside of the while
var f = function ( id ) {
return ids [ id ] === myOOId ;
} ;
while ( Object . keys ( ids ) . some ( f ) ) {
myOOId = Util . createRandomInteger ( ) ;
}
}
var myId = getId ( ) ;
ids [ myId ] = {
ooid : myOOId ,
index : getUserIndex ( ) ,
netflux : metadataMgr . getNetfluxId ( )
} ;
oldIds = JSON . parse ( JSON . stringify ( ids ) ) ;
APP . onLocal ( ) ;
} ;
// Another tab from our worker has left: remove its id from the list
var removeClient = function ( obj ) {
var tabId = metadataMgr . getNetfluxId ( ) + '-' + obj . id ;
if ( content . ids [ tabId ] ) {
delete content . ids [ tabId ] ;
delete content . locks [ tabId ] ;
APP . onLocal ( ) ;
}
} ;
// Make sure a former tab on the same worker doesn't have remaining locks
var checkClients = function ( clients ) {
Object . keys ( content . ids ) . forEach ( function ( id ) {
var tabId = Number ( id . slice ( 33 ) ) ; // remove the netflux ID and the "-"
if ( clients . indexOf ( tabId ) === - 1 ) {
removeClient ( {
id : tabId
} ) ;
}
} ) ;
} ;
var getFileType = function ( ) {
var type = common . getMetadataMgr ( ) . getPrivateData ( ) . ooType ;
var title = common . getMetadataMgr ( ) . getMetadataLazy ( ) . title ;
var file = { } ;
switch ( type ) {
case 'doc' :
file . type = 'docx' ;
file . title = title + '.docx' || 'document.docx' ;
file . doc = 'text' ;
break ;
case 'sheet' :
file . type = 'xlsx' ;
file . title = title + '.xlsx' || 'spreadsheet.xlsx' ;
file . doc = 'spreadsheet' ;
break ;
case 'presentation' :
file . type = 'pptx' ;
file . title = title + '.pptx' || 'presentation.pptx' ;
file . doc = 'presentation' ;
break ;
}
return file ;
} ;
var now = function ( ) { return + new Date ( ) ; } ;
var sortCpIndex = function ( hashes ) {
return Object . keys ( hashes ) . map ( Number ) . sort ( function ( a , b ) {
return a - b ;
} ) ;
} ;
var getLastCp = function ( old , i ) {
var hashes = old ? oldHashes : content . hashes ;
if ( ! hashes || ! Object . keys ( hashes ) . length ) { return { } ; }
i = i || 0 ;
var idx = sortCpIndex ( hashes ) ;
var lastIndex = idx [ idx . length - 1 - i ] ;
if ( typeof ( lastIndex ) === "undefined" || ! hashes [ lastIndex ] ) {
return { } ;
}
var last = JSON . parse ( JSON . stringify ( hashes [ lastIndex ] ) ) ;
return last ;
} ;
var rtChannel = {
ready : false ,
readyCb : undefined ,
sendCmd : function ( data , cb ) {
if ( APP . history ) { return ; }
sframeChan . query ( 'Q_OO_COMMAND' , data , cb ) ;
} ,
getHistory : function ( cb ) {
rtChannel . sendCmd ( {
cmd : 'GET_HISTORY' ,
data : { }
} , function ( ) {
APP . onHistorySynced = cb ;
} ) ;
} ,
sendMsg : function ( msg , cp , cb ) {
evOnPatch . fire ( ) ;
rtChannel . sendCmd ( {
cmd : 'SEND_MESSAGE' ,
data : {
msg : msg ,
isCp : cp
}
} , function ( err , h ) {
if ( ! err ) { evOnSync . fire ( ) ; }
cb ( err , h ) ;
} ) ;
} ,
} ;
var ooChannel = {
ready : false ,
queue : [ ] ,
send : function ( ) { } ,
cpIndex : 0
} ;
var getContent = function ( ) {
try {
return getEditor ( ) . asc _nativeGetFile ( ) ;
} catch ( e ) {
console . error ( e ) ;
return ;
}
} ;
/ *
var checkDrawings = function ( ) {
var editor = getEditor ( ) ;
if ( ! editor || ! editor . GetSheets ) { return false ; }
var s = editor . GetSheets ( ) ;
return s . some ( function ( obj ) {
return obj . worksheet . Drawings . length ;
} ) ;
} ;
* /
// DEPRECATED from version 3
// Loading a checkpoint reorder the sheet starting from ID "5".
// We have to reorder it manually when a checkpoint is created
// so that the messages we send to the realtime channel are
// loadable by users joining after the checkpoint
var fixSheets = function ( ) {
// Starting from version 3, we don't need to fix the sheet IDs anymore
// because we reload onlyoffice whenever we receive a checkpoint
if ( ! APP . migrate || ( content && content . version > 2 ) ) { return ; }
try {
var editor = getEditor ( ) ;
// if we are not in the sheet app
// we should not call this code
if ( typeof editor . GetSheets === 'undefined' ) { return ; }
var s = editor . GetSheets ( ) ;
if ( s . length === 0 ) { return ; }
var wb = s [ 0 ] . worksheet . workbook ;
s . forEach ( function ( obj , i ) {
var id = String ( i + 5 ) ;
obj . worksheet . Id = id ;
wb . aWorksheetsById [ id ] = obj . worksheet ;
} ) ;
} catch ( e ) {
console . error ( e ) ;
}
} ;
// Add a lock
var isLockedModal = {
content : UI . dialog . customModal ( h ( 'div.cp-oo-x2tXls' , [
h ( 'span.fa.fa-spin.fa-spinner' ) ,
h ( 'span' , Messages . oo _isLocked )
] ) )
} ;
var onUploaded = function ( ev , data , err ) {
content . saveLock = undefined ;
if ( err ) {
console . error ( err ) ;
if ( content . saveLock === myOOId ) { delete content . saveLock ; } // Unlock checkpoints
if ( APP . migrateModal ) {
try { getEditor ( ) . asc _setRestriction ( true ) ; } catch ( e ) { }
setEditable ( true ) ;
delete content . migration ;
APP . migrateModal . closeModal ( ) ;
APP . onLocal ( ) ;
}
if ( isLockedModal . modal && err === "TOO_LARGE" ) {
if ( APP . migrate ) {
UI . warn ( Messages . oo _cantMigrate ) ;
}
APP . cantCheckpoint = true ;
isLockedModal . modal . closeModal ( ) ;
delete isLockedModal . modal ;
if ( content . saveLock === myOOId ) {
delete content . saveLock ;
}
APP . onLocal ( ) ;
return ;
}
return void UI . alert ( Messages . oo _saveError ) ;
}
// Get the last cp idx
var all = sortCpIndex ( content . hashes || { } ) ;
var current = all [ all . length - 1 ] || 0 ;
var i = current + 1 ;
content . hashes [ i ] = {
file : data . url ,
hash : ev . hash ,
index : ev . index ,
version : NEW _VERSION
} ;
oldHashes = JSON . parse ( JSON . stringify ( content . hashes ) ) ;
content . locks = { } ;
content . ids = { } ;
// If this is a migration, set the new version
if ( APP . migrate ) {
delete content . migration ;
content . version = NEW _VERSION ;
}
APP . onLocal ( ) ;
APP . realtime . onSettle ( function ( ) {
UI . log ( Messages . saved ) ;
APP . realtime . onSettle ( function ( ) {
if ( APP . migrate ) {
UI . removeModals ( ) ;
UI . alert ( Messages . oo _sheetMigration _complete , function ( ) {
common . gotoURL ( ) ;
} ) ;
return ;
}
if ( ev . callback ) {
return void ev . callback ( ) ;
}
} ) ;
} ) ;
sframeChan . query ( 'Q_OO_COMMAND' , {
cmd : 'UPDATE_HASH' ,
data : ev . hash
} , function ( err , obj ) {
if ( err || ( obj && obj . error ) ) { console . error ( err || obj . error ) ; }
} ) ;
} ;
var fmConfig = {
noHandlers : true ,
noStore : true ,
body : $ ( 'body' ) ,
onUploaded : function ( ev , data ) {
if ( ! data || ! data . url ) { return ; }
data . hash = ev . hash ;
sframeChan . query ( 'Q_OO_SAVE' , data , function ( err ) {
onUploaded ( ev , data , err ) ;
} ) ;
} ,
onError : function ( err ) {
onUploaded ( null , null , err ) ;
}
} ;
APP . FM = common . createFileManager ( fmConfig ) ;
var resetData = function ( blob , type ) {
// If a read-only refresh popup was planned, abort it
delete APP . refreshPopup ;
clearTimeout ( APP . refreshRoTo ) ;
if ( ! isLockedModal . modal ) {
isLockedModal . modal = UI . openCustomModal ( isLockedModal . content ) ;
}
myUniqueOOId = undefined ;
setMyId ( ) ;
if ( APP . docEditor ) { APP . docEditor . destroyEditor ( ) ; } // Kill the old editor
$ ( 'iframe[name="frameEditor"]' ) . after ( h ( 'div#cp-app-oo-placeholder-a' ) ) . remove ( ) ;
ooLoaded = false ;
oldLocks = { } ;
Object . keys ( pendingChanges ) . forEach ( function ( key ) {
clearTimeout ( pendingChanges [ key ] ) ;
delete pendingChanges [ key ] ;
} ) ;
if ( APP . stopHistory || APP . template ) { APP . history = false ; }
startOO ( blob , type , true ) ;
} ;
var saveToServer = function ( ) {
if ( APP . cantCheckpoint ) { return ; } // TOO_LARGE
var text = getContent ( ) ;
if ( ! text ) {
setEditable ( false , true ) ;
sframeChan . query ( 'Q_CLEAR_CACHE_CHANNELS' , [
'chainpad' ,
content . channel ,
] , function ( ) { } ) ;
UI . alert ( Messages . realtime _unrecoverableError , function ( ) {
common . gotoURL ( ) ;
} ) ;
return ;
}
var blob = new Blob ( [ text ] , { type : 'plain/text' } ) ;
var file = getFileType ( ) ;
blob . name = ( metadataMgr . getMetadataLazy ( ) . title || file . doc ) + '.' + file . type ;
var data = {
hash : ( APP . history || APP . template ) ? ooChannel . historyLastHash : ooChannel . lastHash ,
index : ( APP . history || APP . template ) ? ooChannel . currentIndex : ooChannel . cpIndex
} ;
fixSheets ( ) ;
if ( ! isLockedModal . modal ) {
isLockedModal . modal = UI . openCustomModal ( isLockedModal . content ) ;
}
ooChannel . ready = false ;
ooChannel . queue = [ ] ;
data . callback = function ( ) {
if ( APP . template ) { APP . template = false ; }
resetData ( blob , file ) ;
} ;
APP . FM . handleFile ( blob , data ) ;
} ;
var noLogin = false ;
var makeCheckpoint = function ( force ) {
if ( APP . cantCheckpoint ) { return ; } // TOO_LARGE
var locked = content . saveLock ;
var lastCp = getLastCp ( ) ;
var needCp = force || ooChannel . cpIndex % CHECKPOINT _INTERVAL === 0 ||
( ooChannel . cpIndex - ( lastCp . index || 0 ) ) > CHECKPOINT _INTERVAL ;
if ( ! needCp ) { return ; }
if ( ! locked || ! isUserOnline ( locked ) || force ) {
if ( ! common . isLoggedIn ( ) && ! isRegisteredUserOnline ( ) && ! noLogin ) {
var login = h ( 'button.cp-corner-primary' , Messages . login _login ) ;
var register = h ( 'button.cp-corner-primary' , Messages . login _register ) ;
var cancel = h ( 'button.cp-corner-cancel' , Messages . cancel ) ;
var actions = h ( 'div' , [ cancel , register , login ] ) ;
var modal = UI . cornerPopup ( Messages . oo _login , actions , '' , { alt : true } ) ;
$ ( register ) . click ( function ( ) {
common . setLoginRedirect ( 'register' ) ;
modal . delete ( ) ;
} ) ;
$ ( login ) . click ( function ( ) {
common . setLoginRedirect ( 'login' ) ;
modal . delete ( ) ;
} ) ;
$ ( cancel ) . click ( function ( ) {
modal . delete ( ) ;
noLogin = true ;
} ) ;
return ;
}
if ( ! common . isLoggedIn ( ) ) { return ; }
content . saveLock = myOOId ;
APP . onLocal ( ) ;
APP . realtime . onSettle ( function ( ) {
saveToServer ( ) ;
} ) ;
}
} ;
var deleteLastCp = function ( ) {
var hashes = content . hashes ;
if ( ! hashes || ! Object . keys ( hashes ) . length ) { return ; }
var i = 0 ;
var idx = Object . keys ( hashes ) . map ( Number ) . sort ( function ( a , b ) {
return a - b ;
} ) ;
var lastIndex = idx [ idx . length - 1 - i ] ;
delete content . hashes [ lastIndex ] ;
APP . onLocal ( ) ;
APP . realtime . onSettle ( function ( ) {
UI . log ( Messages . saved ) ;
} ) ;
} ;
var restoreLastCp = function ( ) {
content . saveLock = myOOId ;
APP . onLocal ( ) ;
APP . realtime . onSettle ( function ( ) {
onUploaded ( {
hash : ooChannel . lastHash ,
index : ooChannel . cpIndex
} , {
url : getLastCp ( ) . file ,
} ) ;
} ) ;
} ;
// Add a timeout to check if a checkpoint was correctly saved by the locking user
// and "unlock the sheet" or "make a checkpoint" if needed
var cpTo ;
var checkCheckpoint = function ( ) {
clearTimeout ( cpTo ) ;
var saved = stringify ( content . hashes ) ;
var locked = content . saveLock ;
var to = 20000 + ( Math . random ( ) * 20000 ) ;
cpTo = setTimeout ( function ( ) {
// If no checkpoint was added and the same user still has the lock
// then make a checkpoint if needed (cp interval)
if ( stringify ( content . hashes ) === saved && locked === content . saveLock ) {
content . saveLock = undefined ;
makeCheckpoint ( ) ;
}
} , to ) ;
} ;
var loadInitDocument = function ( type , useNewDefault ) {
var newText ;
switch ( type ) {
case 'sheet' :
newText = EmptyCell ( useNewDefault ) ;
break ;
case 'doc' :
newText = EmptyDoc ( ) ;
break ;
case 'presentation' :
newText = EmptySlide ( ) ;
break ;
default :
newText = '' ;
}
return new Blob ( [ newText ] , { type : 'text/plain' } ) ;
} ;
var loadLastDocument = function ( lastCp , onCpError , cb ) {
if ( ! lastCp || ! lastCp . file ) {
return void onCpError ( 'EEMPTY' ) ;
}
ooChannel . cpIndex = lastCp . index || 0 ;
ooChannel . lastHash = lastCp . hash ;
var parsed = Hash . parsePadUrl ( lastCp . file ) ;
var secret = Hash . getSecrets ( 'file' , parsed . hash ) ;
if ( ! secret || ! secret . channel ) { return ; }
var hexFileName = secret . channel ;
var fileHost = privateData . fileHost || privateData . origin ;
var src = fileHost + Hash . getBlobPathFromHex ( hexFileName ) ;
var key = secret . keys && secret . keys . cryptKey ;
var xhr = new XMLHttpRequest ( ) ;
xhr . open ( 'GET' , src , true ) ;
xhr . responseType = 'arraybuffer' ;
xhr . onload = function ( ) {
if ( /^4/ . test ( '' + this . status ) ) {
onCpError ( this . status ) ;
return void console . error ( 'XHR error' , this . status ) ;
}
var arrayBuffer = xhr . response ;
if ( arrayBuffer ) {
var u8 = new Uint8Array ( arrayBuffer ) ;
FileCrypto . decrypt ( u8 , key , function ( err , decrypted ) {
if ( err ) {
if ( err === "DECRYPTION_ERROR" ) {
console . warn ( err ) ;
return void onCpError ( err ) ;
}
return void console . error ( err ) ;
}
var blob = new Blob ( [ decrypted . content ] , { type : 'plain/text' } ) ;
if ( cb ) {
return cb ( blob , getFileType ( ) ) ;
}
startOO ( blob , getFileType ( ) ) ;
} ) ;
}
} ;
xhr . onerror = function ( err ) {
onCpError ( err ) ;
} ;
xhr . send ( null ) ;
} ;
/ *
var refreshReadOnly = function ( ) {
var cancel = h ( 'button.cp-corner-cancel' , Messages . cancel ) ;
var reload = h ( 'button.cp-corner-primary' , [
h ( 'i.fa.fa-refresh' ) ,
Messages . oo _refresh
] ) ;
var actions = h ( 'div' , [ cancel , reload ] ) ;
var m = UI . cornerPopup ( Messages . oo _refreshText , actions , '' ) ;
$ ( reload ) . click ( function ( ) {
ooChannel . ready = false ;
var lastCp = getLastCp ( ) ;
loadLastDocument ( lastCp , function ( ) {
var file = getFileType ( ) ;
var type = common . getMetadataMgr ( ) . getPrivateData ( ) . ooType ;
var blob = loadInitDocument ( type , true ) ;
resetData ( blob , file ) ;
} , function ( blob , file ) {
resetData ( blob , file ) ;
} ) ;
delete APP . refreshPopup ;
m . delete ( ) ;
} ) ;
$ ( cancel ) . click ( function ( ) {
delete APP . refreshPopup ;
m . delete ( ) ;
} ) ;
} ;
* /
var openVersionHash = function ( version ) {
readOnly = true ;
var hashes = content . hashes || { } ;
var sortedCp = Object . keys ( hashes ) . map ( Number ) . sort ( function ( a , b ) {
return hashes [ a ] . index - hashes [ b ] . index ;
} ) ;
var s = version . split ( '.' ) ;
if ( s . length !== 2 ) { return UI . errorLoadingScreen ( Messages . error ) ; }
var major = Number ( s [ 0 ] ) ;
var cpId = sortedCp [ major - 1 ] ;
var nextCpId = sortedCp [ major ] ;
var cp = hashes [ cpId ] || { } ;
var minor = Number ( s [ 1 ] ) + 1 ;
var toHash = cp . hash || 'NONE' ;
var fromHash = nextCpId ? hashes [ nextCpId ] . hash : 'NONE' ;
sframeChan . query ( 'Q_GET_HISTORY_RANGE' , {
channel : content . channel ,
lastKnownHash : fromHash ,
toHash : toHash ,
} , function ( err , data ) {
if ( err ) { console . error ( err ) ; return void UI . errorLoadingScreen ( Messages . error ) ; }
if ( ! Array . isArray ( data . messages ) ) {
console . error ( 'Not an array' ) ;
return void UI . errorLoadingScreen ( Messages . error ) ;
}
// The first "cp" in history is the empty doc. It doesn't include the first patch
// of the history
var initialCp = major === 0 ;
var messages = ( data . messages || [ ] ) . slice ( initialCp ? 0 : 1 , minor ) ;
messages . forEach ( function ( obj ) {
try { obj . msg = JSON . parse ( obj . msg ) ; } catch ( e ) { console . error ( e ) ; }
} ) ;
// The version exists if we have results in the "messages" array
// or if we requested a x.0 version
var exists = ! Number ( s [ 1 ] ) || messages . length ;
var vHashEl ;
if ( ! privateData . embed ) {
var vTime = ( messages [ messages . length - 1 ] || { } ) . time ;
var vTimeStr = vTime ? new Date ( vTime ) . toLocaleString ( )
: 'v' + privateData . ooVersionHash ;
var vTxt = Messages . _getKey ( 'infobar_versionHash' , [ vTimeStr ] ) ;
// If we expected patched and we don't have any, it means this part
// of the history has been deleted
var vType = "warning" ;
if ( ! exists ) {
vTxt = Messages . oo _deletedVersion ;
vType = "danger" ;
}
vHashEl = h ( 'div.alert.alert-' + vType + '.cp-burn-after-reading' , vTxt ) ;
$ ( '#cp-app-oo-editor' ) . prepend ( vHashEl ) ;
}
if ( ! exists ) { return void UI . removeLoadingScreen ( ) ; }
loadLastDocument ( cp , function ( ) {
if ( cp . hash && vHashEl ) {
// We requested a checkpoint but we can't find it...
UI . removeLoadingScreen ( ) ;
vHashEl . innerText = Messages . oo _deletedVersion ;
$ ( vHashEl ) . removeClass ( 'alert-warning' ) . addClass ( 'alert-danger' ) ;
return ;
}
var file = getFileType ( ) ;
var type = common . getMetadataMgr ( ) . getPrivateData ( ) . ooType ;
var blob = loadInitDocument ( type , true ) ;
ooChannel . queue = messages ;
resetData ( blob , file ) ;
UI . removeLoadingScreen ( ) ;
} , function ( blob , file ) {
ooChannel . queue = messages ;
resetData ( blob , file ) ;
UI . removeLoadingScreen ( ) ;
} ) ;
} ) ;
} ;
var openRtChannel = function ( cb ) {
if ( rtChannel . ready ) { return void cb ( ) ; }
var chan = content . channel || Hash . createChannelId ( ) ;
if ( ! content . channel ) {
content . channel = chan ;
APP . onLocal ( ) ;
}
sframeChan . query ( 'Q_OO_OPENCHANNEL' , {
channel : content . channel ,
lastCpHash : getLastCp ( ) . hash
} , function ( err , obj ) {
if ( err || ( obj && obj . error ) ) { console . error ( err || ( obj && obj . error ) ) ; }
} ) ;
sframeChan . on ( 'EV_OO_EVENT' , function ( obj ) {
switch ( obj . ev ) {
case 'READY' :
checkClients ( obj . data ) ;
cb ( ) ;
break ;
case 'LEAVE' :
removeClient ( obj . data ) ;
break ;
case 'MESSAGE' :
if ( APP . history ) {
ooChannel . historyLastHash = obj . data . hash ;
ooChannel . currentIndex ++ ;
return ;
}
if ( ooChannel . ready ) {
// In read-only mode, push the message to the queue and prompt
// the user to refresh OO (without reloading the page)
/ * i f ( r e a d O n l y ) {
ooChannel . queue . push ( obj . data ) ;
if ( APP . refreshPopup ) { return ; }
APP . refreshPopup = true ;
// Don't "spam" the user instantly and no more than
// 1 popup every 15s
APP . refreshRoTo = setTimeout ( refreshReadOnly , READONLY _REFRESH _TO ) ;
return ;
} * /
ooChannel . send ( obj . data . msg ) ;
ooChannel . lastHash = obj . data . hash ;
ooChannel . cpIndex ++ ;
} else {
ooChannel . queue . push ( obj . data ) ;
}
break ;
case 'HISTORY_SYNCED' :
if ( typeof ( APP . onHistorySynced ) !== "function" ) { return ; }
APP . onHistorySynced ( ) ;
delete APP . onHistorySynced ;
break ;
}
} ) ;
} ;
var getParticipants = function ( ) {
var users = metadataMgr . getMetadata ( ) . users ;
var i = 1 ;
var p = Object . keys ( content . ids || { } ) . map ( function ( id ) {
var nId = id . slice ( 0 , 32 ) ;
if ( ! users [ nId ] ) { return ; }
var ooId = content . ids [ id ] . ooid ;
var idx = content . ids [ id ] . index ;
if ( ! ooId || ooId === myOOId ) { return ; }
if ( idx >= i ) { i = idx + 1 ; }
return {
id : String ( ooId ) + idx ,
idOriginal : String ( ooId ) ,
username : ( users [ nId ] || { } ) . name || Messages . anonymous ,
indexUser : idx ,
connectionId : content . ids [ id ] . netflux || Hash . createChannelId ( ) ,
isCloseCoAuthoring : false ,
view : false
} ;
} ) ;
// Add an history keeper user to show that we're never alone
var hkId = Util . createRandomInteger ( ) ;
p . push ( {
id : hkId ,
idOriginal : String ( hkId ) ,
username : "History" ,
indexUser : i ,
connectionId : Hash . createChannelId ( ) ,
isCloseCoAuthoring : false ,
view : false
} ) ;
i ++ ;
if ( ! myUniqueOOId ) { myUniqueOOId = String ( myOOId ) + i ; }
p . push ( {
id : myUniqueOOId ,
idOriginal : String ( myOOId ) ,
username : metadataMgr . getUserData ( ) . name || Messages . anonymous ,
indexUser : i ,
connectionId : metadataMgr . getNetfluxId ( ) || Hash . createChannelId ( ) ,
isCloseCoAuthoring : false ,
view : false
} ) ;
return {
index : i ,
list : p . filter ( Boolean )
} ;
} ;
// Get all existing locks
var getUserLock = function ( id , forceArray ) {
var type = common . getMetadataMgr ( ) . getPrivateData ( ) . ooType ;
content . locks = content . locks || { } ;
var l = content . locks [ id ] || { } ;
if ( type === "sheet" || forceArray ) {
return Object . keys ( l ) . map ( function ( uid ) { return l [ uid ] ; } ) ;
}
var res = { } ;
Object . keys ( l ) . forEach ( function ( uid ) {
res [ uid ] = l [ uid ] ;
} ) ;
return res ;
} ;
var getLock = function ( ) {
var type = common . getMetadataMgr ( ) . getPrivateData ( ) . ooType ;
var locks = [ ] ;
if ( type === "sheet" ) {
Object . keys ( content . locks || { } ) . forEach ( function ( id ) {
Array . prototype . push . apply ( locks , getUserLock ( id ) ) ;
} ) ;
return locks ;
}
locks = { } ;
Object . keys ( content . locks || { } ) . forEach ( function ( id ) {
Util . extend ( locks , getUserLock ( id ) ) ;
} ) ;
return locks ;
} ;
// Update the userlist in onlyoffice
var handleNewIds = function ( o , n ) {
if ( stringify ( o ) === stringify ( n ) ) { return ; }
var p = getParticipants ( ) ;
ooChannel . send ( {
type : "connectState" ,
participantsTimestamp : + new Date ( ) ,
participants : p . list ,
waitAuth : false
} ) ;
} ;
// Update the locks status in onlyoffice
var handleNewLocks = function ( o , n ) {
var hasNew = false ;
// Check if we have at least one new lock
Object . keys ( n || { } ) . some ( function ( id ) {
if ( typeof ( n [ id ] ) !== "object" ) { return ; } // Ignore old format
// n[id] = { uid: lock, uid2: lock2 };
return Object . keys ( n [ id ] ) . some ( function ( uid ) {
// New lock
if ( ! o [ id ] || ! o [ id ] [ uid ] ) {
hasNew = true ;
return true ;
}
} ) ;
} ) ;
// Remove old locks
Object . keys ( o || { } ) . forEach ( function ( id ) {
if ( typeof ( o [ id ] ) !== "object" ) { return ; } // Ignore old format
Object . keys ( o [ id ] ) . forEach ( function ( uid ) {
// Removed lock
if ( ! n [ id ] || ! n [ id ] [ uid ] ) {
ooChannel . send ( {
type : "releaseLock" ,
locks : [ o [ id ] [ uid ] ]
} ) ;
}
} ) ;
} ) ;
if ( hasNew ) {
ooChannel . send ( {
type : "getLock" ,
locks : getLock ( )
} ) ;
}
} ;
// Remove locks from offline users
var deleteOfflineLocks = function ( ) {
var locks = content . locks || { } ;
var users = Object . keys ( metadataMgr . getMetadata ( ) . users ) ;
Object . keys ( locks ) . forEach ( function ( id ) {
var nId = id . slice ( 0 , 32 ) ;
if ( users . indexOf ( nId ) === - 1 ) {
// Offline locks: support old format
var l = ( locks [ id ] && ! locks [ id ] . block ) ? getUserLock ( id ) : [ locks [ id ] ] ;
ooChannel . send ( {
type : "releaseLock" ,
locks : l
} ) ;
delete content . locks [ id ] ;
}
} ) ;
if ( content . saveLock && ! isUserOnline ( content . saveLock ) ) {
delete content . saveLock ;
}
} ;
var handleAuth = function ( obj , send ) {
//setEditable(false);
var changes = [ ] ;
if ( content . version > 2 ) {
ooChannel . queue . forEach ( function ( data ) {
Array . prototype . push . apply ( changes , data . msg . changes ) ;
} ) ;
ooChannel . ready = true ;
ooChannel . cpIndex += ooChannel . queue . length ;
var last = ooChannel . queue . pop ( ) ;
if ( last ) { ooChannel . lastHash = last . hash ; }
} else {
setEditable ( false , true ) ;
}
send ( {
type : "authChanges" ,
changes : changes
} ) ;
// Answer to the auth command
var p = getParticipants ( ) ;
send ( {
type : "auth" ,
result : 1 ,
sessionId : sessionId ,
participants : p . list ,
locks : [ ] ,
changes : [ ] ,
changesIndex : 0 ,
indexUser : p . index ,
buildVersion : "5.2.6" ,
buildNumber : 2 ,
licenseType : 3 ,
//"g_cAscSpellCheckUrl": "/spellchecker",
//"settings":{"spellcheckerUrl":"/spellchecker","reconnection":{"attempts":50,"delay":2000}}
} ) ;
// Open the document
send ( {
type : "documentOpen" ,
data : { "type" : "open" , "status" : "ok" , "data" : { "Editor.bin" : obj . openCmd . url } }
} ) ;
/ *
// TODO: make sure we don't have new popups that can break our integration
var observer = new MutationObserver ( function ( mutations ) {
mutations . forEach ( function ( mutation ) {
if ( mutation . type === "childList" ) {
for ( var i = 0 ; i < mutation . addedNodes . length ; i ++ ) {
if ( mutation . addedNodes [ i ] . classList . contains ( 'asc-window' ) &&
mutation . addedNodes [ i ] . classList . contains ( 'alert' ) ) {
$ ( mutation . addedNodes [ i ] ) . find ( 'button' ) . not ( '.custom' ) . click ( ) ;
}
}
}
} ) ;
} ) ;
observer . observe ( window . frames [ 0 ] . document . body , {
childList : true ,
} ) ;
* /
} ;
var handleLock = function ( obj , send ) {
if ( APP . history ) { return ; }
if ( content . saveLock ) {
if ( ! isLockedModal . modal ) {
isLockedModal . modal = UI . openCustomModal ( isLockedModal . content ) ;
}
setTimeout ( function ( ) {
handleLock ( obj , send ) ;
} , 50 ) ;
return ;
}
var type = common . getMetadataMgr ( ) . getPrivateData ( ) . ooType ;
content . locks = content . locks || { } ;
// Send the lock to other users
var msg = {
time : now ( ) ,
user : myUniqueOOId ,
block : obj . block && obj . block [ 0 ] ,
} ;
var myId = getId ( ) ;
content . locks [ myId ] = content . locks [ myId ] || { } ;
var b = obj . block && obj . block [ 0 ] ;
if ( type === "sheet" || typeof ( b ) !== "string" ) {
var uid = Util . uid ( ) ;
content . locks [ myId ] [ uid ] = msg ;
} else {
if ( typeof ( b ) === "string" ) { content . locks [ myId ] [ b ] = msg ; }
}
oldLocks = JSON . parse ( JSON . stringify ( content . locks ) ) ;
// Remove old locks
deleteOfflineLocks ( ) ;
// Prepare callback
if ( cpNfInner ) {
var waitLock = APP . waitLock = Util . mkEvent ( true ) ;
setTimeout ( function ( ) {
// Make sure the waitLock is never stuck
waitLock . fire ( ) ;
if ( waitLock === APP . waitLock ) { delete APP . waitLock ; }
} , 5000 ) ;
var onPatchSent = function ( again ) {
if ( ! again ) { cpNfInner . offPatchSent ( onPatchSent ) ; }
// Answer to our onlyoffice
if ( ! content . saveLock ) {
if ( isLockedModal . modal ) {
isLockedModal . modal . closeModal ( ) ;
delete isLockedModal . modal ;
if ( ! APP . history ) {
$ ( '#cp-app-oo-editor > iframe' ) [ 0 ] . contentWindow . focus ( ) ;
}
}
send ( {
type : "getLock" ,
locks : getLock ( )
} ) ;
waitLock . fire ( ) ;
if ( waitLock === APP . waitLock ) { delete APP . waitLock ; }
} else {
if ( ! isLockedModal . modal ) {
isLockedModal . modal = UI . openCustomModal ( isLockedModal . content ) ;
}
setTimeout ( function ( ) {
onPatchSent ( true ) ;
} , 50 ) ;
}
} ;
cpNfInner . onPatchSent ( onPatchSent ) ;
}
// Commit
APP . onLocal ( ) ;
APP . realtime . sync ( ) ;
} ;
var parseChanges = function ( changes ) {
try {
changes = JSON . parse ( changes ) ;
} catch ( e ) {
return [ ] ;
}
return changes . map ( function ( change ) {
return {
docid : "fresh" ,
change : '"' + change + '"' ,
time : now ( ) ,
user : myUniqueOOId ,
useridoriginal : String ( myOOId )
} ;
} ) ;
} ;
var handleChanges = function ( obj , send ) {
// Add a new entry to the pendingChanges object.
// If we can't send the patch within 30s, force a page reload
var uid = Util . uid ( ) ;
pendingChanges [ uid ] = setTimeout ( function ( ) {
// If we're offline, force a reload on reconnect
if ( offline ) {
pendingChanges . force = true ;
return ;
}
// We're online: force a reload now
setEditable ( false ) ;
UI . alert ( Messages . realtime _unrecoverableError , function ( ) {
common . gotoURL ( ) ;
} ) ;
} , PENDING _TIMEOUT ) ;
if ( offline ) {
pendingChanges . force = true ;
return ;
}
// Send the changes
content . locks = content . locks || { } ;
rtChannel . sendMsg ( {
type : "saveChanges" ,
changes : parseChanges ( obj . changes ) ,
changesIndex : ooChannel . cpIndex || 0 ,
locks : getUserLock ( getId ( ) , true ) ,
excelAdditionalInfo : obj . excelAdditionalInfo
} , null , function ( err , hash ) {
if ( err ) {
return void console . error ( err ) ;
}
if ( pendingChanges [ uid ] ) {
clearTimeout ( pendingChanges [ uid ] ) ;
delete pendingChanges [ uid ] ;
}
// Call unSaveLock to tell onlyoffice that the patch was sent.
// It will allow you to make changes to another cell.
// If there is an error and unSaveLock is not called, onlyoffice
// will try to send the patch again
send ( {
type : "unSaveLock" ,
index : ooChannel . cpIndex ,
time : + new Date ( )
} ) ;
// Increment index and update latest hash
ooChannel . cpIndex ++ ;
ooChannel . lastHash = hash ;
// Check if a checkpoint is needed
makeCheckpoint ( ) ;
// Remove my locks
delete content . locks [ getId ( ) ] ;
oldLocks = JSON . parse ( JSON . stringify ( content . locks ) ) ;
APP . onLocal ( ) ;
} ) ;
} ;
var makeChannel = function ( ) {
var msgEv = Util . mkEvent ( ) ;
var iframe = $ ( '#cp-app-oo-editor > iframe' ) [ 0 ] . contentWindow ;
var type = common . getMetadataMgr ( ) . getPrivateData ( ) . ooType ;
window . addEventListener ( 'message' , function ( msg ) {
if ( msg . source !== iframe ) { return ; }
msgEv . fire ( msg ) ;
} ) ;
var postMsg = function ( data ) {
iframe . postMessage ( data , '*' ) ;
} ;
Channel . create ( msgEv , postMsg , function ( chan ) {
APP . chan = chan ;
var send = ooChannel . send = function ( obj , force ) {
// can't push to OO before reloading cp
if ( APP . onStrictSaveChanges && ! force ) { return ; }
// We only need to release locks for sheets
if ( type !== "sheet" && obj . type === "releaseLock" ) { return ; }
debug ( obj , 'toOO' ) ;
chan . event ( 'CMD' , obj ) ;
} ;
chan . on ( 'CMD' , function ( obj ) {
debug ( obj , 'fromOO' ) ;
switch ( obj . type ) {
case "auth" :
handleAuth ( obj , send ) ;
break ;
case "isSaveLock" :
// TODO ping the server to check if we're online first?
if ( ! offline ) {
if ( APP . waitLock ) {
APP . waitLock . reg ( function ( ) {
send ( {
type : "saveLock" ,
saveLock : false
} , true ) ;
} ) ;
} else {
send ( {
type : "saveLock" ,
saveLock : false
} , true ) ;
}
}
break ;
case "cursor" :
cursor . updateCursor ( {
type : "cursor" ,
messages : [ {
cursor : obj . cursor ,
time : + new Date ( ) ,
user : myUniqueOOId ,
useridoriginal : myOOId
} ]
} ) ;
break ;
case "getLock" :
handleLock ( obj , send ) ;
break ;
case "getMessages" :
// OO chat messages?
send ( { type : "message" } ) ;
break ;
case "saveChanges" :
// If we have unsaved data before reloading for a checkpoint...
if ( APP . onStrictSaveChanges ) {
delete APP . unsavedLocks ;
APP . unsavedChanges = {
type : "saveChanges" ,
changes : parseChanges ( obj . changes ) ,
changesIndex : ooChannel . cpIndex || 0 ,
locks : type === "sheet" ? [ ] : APP . unsavedLocks ,
excelAdditionalInfo : null ,
recover : true
} ;
APP . onStrictSaveChanges ( ) ;
return ;
}
// We're sending our changes to netflux
handleChanges ( obj , send ) ;
// If we're alone, clean up the medias
var m = metadataMgr . getChannelMembers ( ) . slice ( ) . filter ( function ( nId ) {
return nId . length === 32 ;
} ) ;
if ( m . length === 1 && APP . loadingImage <= 0 ) {
try {
var docs = window . frames [ 0 ] . AscCommon . g _oDocumentUrls . urls || { } ;
var mediasSources = getMediasSources ( ) ;
Object . keys ( mediasSources ) . forEach ( function ( name ) {
if ( ! docs [ 'media/' + name ] ) {
delete mediasSources [ name ] ;
}
} ) ;
APP . onLocal ( ) ;
} catch ( e ) { }
}
break ;
case "unLockDocument" :
if ( obj . releaseLocks && content . locks && content . locks [ getId ( ) ] ) {
send ( {
type : "releaseLock" ,
locks : getUserLock ( getId ( ) )
} ) ;
delete content . locks [ getId ( ) ] ;
APP . onLocal ( ) ;
}
if ( obj . isSave ) {
send ( {
type : "unSaveLock" ,
time : - 1 ,
index : - 1
} ) ;
}
if ( APP . onDocumentUnlock ) {
APP . onDocumentUnlock ( ) ;
APP . onDocumentUnlock = undefined ;
}
break ;
}
} ) ;
} ) ;
} ;
startOO = function ( blob , file , force ) {
if ( APP . ooconfig && ! force ) { return void console . error ( 'already started' ) ; }
var url = URL . createObjectURL ( blob ) ;
var lock = ! APP . history && ( APP . migrate ) ;
// Starting from version 3, we can use the view mode again
// defined but never used
//var mode = (content && content.version > 2 && lock) ? "view" : "edit";
// Config
APP . ooconfig = {
"document" : {
"fileType" : file . type ,
"key" : "fresh" ,
"title" : file . title ,
"url" : url ,
"permissions" : {
"download" : false ,
"print" : true ,
}
} ,
"documentType" : file . doc ,
"editorConfig" : {
customization : {
chat : false ,
logo : {
url : "/bounce/#" + encodeURIComponent ( 'https://www.onlyoffice.com' )
}
} ,
"user" : {
"id" : String ( myOOId ) , //"c0c3bf82-20d7-4663-bf6d-7fa39c598b1d",
"firstname" : metadataMgr . getUserData ( ) . name || Messages . anonymous ,
"name" : metadataMgr . getUserData ( ) . name || Messages . anonymous ,
} ,
"mode" : "edit" ,
"lang" : ( window . cryptpadLanguage || navigator . language || navigator . userLanguage || '' ) . slice ( 0 , 2 )
} ,
"events" : {
"onAppReady" : function ( /*evt*/ ) {
var $iframe = $ ( 'iframe[name="frameEditor"]' ) . contents ( ) ;
$iframe . prop ( 'tabindex' , '-1' ) ;
var $tb = $iframe . find ( 'head' ) ;
var css = // Old OO
//'#id-toolbar-full .toolbar-group:nth-child(2), #id-toolbar-full .separator:nth-child(3) { display: none; }' +
//'#fm-btn-save { display: none !important; }' +
//'#panel-settings-general tr.autosave { display: none !important; }' +
//'#panel-settings-general tr.coauth { display: none !important; }' +
//'#header { display: none !important; }' +
'#title-doc-name { display: none !important; }' +
'#title-user-name { display: none !important; }' +
( supportsXLSX ( ) ? '' : '#slot-btn-dt-print { display: none !important; }' ) +
// New OO:
'section[data-tab="ins"] .separator:nth-last-child(2) { display: none !important; }' + // separator
'#slot-btn-insequation { display: none !important; }' + // Insert equation
'#asc-gen125 { display: none !important; }' + // Disable presenter mode
//'.toolbar .tabs .ribtab:not(.canedit) { display: none !important; }' + // Switch collaborative mode
'#fm-btn-info { display: none !important; }' + // Author name, doc title, etc. in "File" (menu entry)
'#panel-info { display: none !important; }' + // Same but content
'#image-button-from-url { display: none !important; }' + // Inline image settings: replace with url
'.cp-from-url, #textart-button-from-url { display: none !important; }' + // Spellcheck language
'.statusbar .cnt-lang { display: none !important; }' + // Spellcheck language
'.statusbar #btn-doc-spell { display: none !important; }' + // Spellcheck button
'#file-menu-panel .devider { display: none !important; }' + // separator in the "File" menu
'#left-btn-spellcheck, #left-btn-about { display: none !important; }' +
'div.btn-users.dropdown-toggle { display: none; !important }' ;
if ( readOnly ) {
css += '#toolbar { display: none !important; }' ;
//css += '#app-title { display: none !important; }'; // OnlyOffice logo + doc title
//css += '#file-menu-panel { top: 28px !important; }'; // Position of the "File" menu
}
$ ( '<style>' ) . text ( css ) . appendTo ( $tb ) ;
setTimeout ( function ( ) {
$ ( window ) . trigger ( 'resize' ) ;
} ) ;
if ( UI . findOKButton ( ) . length ) {
UI . findOKButton ( ) . on ( 'focusout' , function ( ) {
window . setTimeout ( function ( ) { UI . findOKButton ( ) . focus ( ) ; } ) ;
} ) ;
}
} ,
"onDocumentReady" : function ( ) {
evOnSync . fire ( ) ;
var onMigrateRdy = Util . mkEvent ( ) ;
onMigrateRdy . reg ( function ( ) {
var div = h ( 'div.cp-oo-x2tXls' , [
h ( 'span.fa.fa-spin.fa-spinner' ) ,
h ( 'span' , Messages . oo _sheetMigration _loading )
] ) ;
APP . migrateModal = UI . openCustomModal ( UI . dialog . customModal ( div , { buttons : [ ] } ) ) ;
makeCheckpoint ( true ) ;
} ) ;
// DEPRECATED: from version 3, the queue is sent again during init
if ( APP . migrate && ( ( content . version || 1 ) <= 2 ) ) {
// The doc is ready, fix the worksheets IDs and push the queue
fixSheets ( ) ;
// Push changes since last cp
ooChannel . ready = true ;
var changes = [ ] ;
var changesIndex ;
ooChannel . queue . forEach ( function ( data ) {
Array . prototype . push . apply ( changes , data . msg . changes ) ;
changesIndex = data . msg . changesIndex ;
//ooChannel.send(data.msg);
} ) ;
ooChannel . cpIndex += ooChannel . queue . length ;
var last = ooChannel . queue . pop ( ) ;
if ( last ) { ooChannel . lastHash = last . hash ; }
var onDocUnlock = function ( ) {
// Migration required but read-only: continue...
if ( readOnly ) {
setEditable ( true ) ;
try { getEditor ( ) . asc _setRestriction ( true ) ; } catch ( e ) { }
} else {
// No changes after the cp: migrate now
onMigrateRdy . fire ( ) ;
}
} ;
// Send the changes all at once
if ( changes . length ) {
setTimeout ( function ( ) {
ooChannel . send ( {
type : 'saveChanges' ,
changesIndex : changesIndex ,
changes : changes ,
locks : [ ]
} ) ;
APP . onDocumentUnlock = onDocUnlock ;
} , 5000 ) ;
return ;
}
onDocUnlock ( ) ;
return ;
}
if ( lock || readOnly ) {
try { getEditor ( ) . asc _setRestriction ( true ) ; } catch ( e ) { }
//getEditor().setViewModeDisconnect(); // can't be used anymore, display an OO error popup
} else {
setEditable ( true ) ;
deleteOfflineLocks ( ) ;
handleNewLocks ( { } , content . locks ) ;
if ( APP . unsavedChanges ) {
var unsaved = APP . unsavedChanges ;
delete APP . unsavedChanges ;
rtChannel . sendMsg ( unsaved , null , function ( err , hash ) {
if ( err ) { return void UI . alert ( Messages . oo _lostEdits ) ; }
// This is supposed to be a "send" function to tell our OO
// to unlock the cell. We use this to know that the patch was
// correctly sent so that we can apply it to our OO too.
ooChannel . send ( unsaved ) ;
ooChannel . cpIndex ++ ;
ooChannel . lastHash = hash ;
} ) ;
}
}
if ( isLockedModal . modal && force ) {
isLockedModal . modal . closeModal ( ) ;
delete isLockedModal . modal ;
if ( ! APP . history ) {
$ ( '#cp-app-oo-editor > iframe' ) [ 0 ] . contentWindow . focus ( ) ;
}
}
if ( APP . template ) {
try { getEditor ( ) . asc _setRestriction ( true ) ; } catch ( e ) { }
//getEditor().setViewModeDisconnect();
UI . removeLoadingScreen ( ) ;
makeCheckpoint ( true ) ;
return ;
}
APP . onLocal ( ) ; // Add our data to the userlist
if ( APP . history ) {
try {
getEditor ( ) . asc _setRestriction ( true ) ;
} catch ( e ) { }
}
if ( lock && ! readOnly ) {
onMigrateRdy . fire ( ) ;
}
// Check if history can/should be trimmed
var cp = getLastCp ( ) ;
if ( cp && cp . file && cp . hash ) {
var channels = [ {
channel : content . channel ,
lastKnownHash : cp . hash
} ] ;
common . checkTrimHistory ( channels ) ;
}
}
}
} ;
/ *
// NOTE: Make sure it won't break anaything new (Firefox setTimeout bug)
window . onbeforeunload = function ( ) {
var ifr = document . getElementsByTagName ( 'iframe' ) [ 0 ] ;
if ( ifr ) { ifr . remove ( ) ; }
} ;
* /
APP . getUserColor = function ( userId ) {
var hex ;
Object . keys ( content . ids || { } ) . some ( function ( k ) {
var u = content . ids [ k ] ;
if ( Number ( u . ooid ) === Number ( userId ) ) {
var md = common . getMetadataMgr ( ) . getMetadataLazy ( ) ;
if ( md && md . users && md . users [ u . netflux ] ) {
hex = md . users [ u . netflux ] . color ;
}
return true ;
}
} ) ;
if ( hex ) {
var rgb = Util . hexToRGB ( hex ) ;
return {
r : rgb [ 0 ] ,
g : rgb [ 1 ] ,
b : rgb [ 2 ] ,
a : 255
} ;
}
} ;
APP . UploadImageFiles = function ( files , type , id , jwt , cb ) {
return void cb ( ) ;
} ;
APP . AddImage = function ( cb1 , cb2 ) {
APP . AddImageSuccessCallback = cb1 ;
APP . AddImageErrorCallback = cb2 ;
common . openFilePicker ( {
types : [ 'file' ] ,
where : [ 'root' ] ,
filter : {
fileType : [ 'image/' ]
}
} , function ( data ) {
if ( data . type !== 'file' ) {
debug ( "Unexpected data type picked " + data . type ) ;
return ;
}
var name = data . name ;
// Add image to the list
var mediasSources = getMediasSources ( ) ;
// Check if name already exists
var getUniqueName = function ( name , mediasSources ) {
var get = function ( ) {
var s = name . split ( '.' ) ;
if ( s . length > 1 ) {
s [ s . length - 2 ] = s [ s . length - 2 ] + '-' + Util . uid ( ) ;
name = s . join ( '.' ) ;
} else {
name += '-' + Util . uid ( ) ;
}
} ;
while ( mediasSources [ name ] ) { get ( ) ; }
return name ;
} ;
if ( mediasSources [ name ] ) {
name = getUniqueName ( name , mediasSources ) ;
data . name = name ;
}
mediasSources [ name ] = data ;
APP . onLocal ( ) ;
APP . realtime . onSettle ( function ( ) {
APP . getImageURL ( name , function ( url ) {
debug ( "CRYPTPAD success add " + name ) ;
common . setPadAttribute ( 'atime' , + new Date ( ) , null , data . href ) ;
APP . AddImageSuccessCallback ( {
name : name ,
url : url
} ) ;
} ) ;
} ) ;
} ) ;
} ;
APP . openURL = function ( url ) {
common . openUnsafeURL ( url ) ;
} ;
APP . loadingImage = 0 ;
APP . getImageURL = function ( name , callback ) {
if ( name && /^data:image/ . test ( name ) ) {
return void callback ( '' ) ;
}
var mediasSources = getMediasSources ( ) ;
var data = mediasSources [ name ] ;
if ( typeof data === 'undefined' ) {
debug ( "CryptPad - could not find matching media for " + name ) ;
return void callback ( "" ) ;
}
var blobUrl = ( typeof mediasData [ data . src ] === 'undefined' ) ? "" : mediasData [ data . src ] . src ;
if ( blobUrl ) {
debug ( "CryptPad Image already loaded " + blobUrl ) ;
return void callback ( blobUrl ) ;
}
APP . loadingImage ++ ;
Util . fetch ( data . src , function ( err , u8 ) {
if ( err ) {
APP . loadingImage -- ;
console . error ( err ) ;
return void callback ( "" ) ;
}
try {
debug ( "Decrypt with key " + data . key ) ;
FileCrypto . decrypt ( u8 , Nacl . util . decodeBase64 ( data . key ) , function ( err , res ) {
APP . loadingImage -- ;
if ( err || ! res . content ) {
debug ( "Decrypting failed" ) ;
return void callback ( "" ) ;
}
try {
var blobUrl = URL . createObjectURL ( res . content ) ;
// store media blobUrl and content for cache and export
var mediaData = { blobUrl : blobUrl , content : "" } ;
mediasData [ data . src ] = mediaData ;
var reader = new FileReader ( ) ;
reader . onloadend = function ( ) {
debug ( "MediaData set" ) ;
mediaData . content = reader . result ;
} ;
reader . readAsArrayBuffer ( res . content ) ;
debug ( "Adding CryptPad Image " + data . name + ": " + blobUrl ) ;
window . frames [ 0 ] . AscCommon . g _oDocumentUrls . addImageUrl ( data . name , blobUrl ) ;
callback ( blobUrl ) ;
} catch ( e ) { }
} ) ;
} catch ( e ) {
APP . loadingImage -- ;
debug ( "Exception decrypting image " + data . name ) ;
console . error ( e ) ;
callback ( "" ) ;
}
} , void 0 , common . getCache ( ) ) ;
} ;
APP . docEditor = new window . DocsAPI . DocEditor ( "cp-app-oo-placeholder-a" , APP . ooconfig ) ;
ooLoaded = true ;
makeChannel ( ) ;
} ;
var x2tReady = Util . mkEvent ( true ) ;
var fetchFonts = function ( x2t ) {
var path = '/common/onlyoffice/' + CURRENT _VERSION + '/fonts/' ;
var e = getEditor ( ) ;
var fonts = e . FontLoader . fontInfos ;
var files = e . FontLoader . fontFiles ;
var suffixes = {
indexR : '' ,
indexB : '_Bold' ,
indexBI : '_Bold_Italic' ,
indexI : '_Italic' ,
} ;
nThen ( function ( waitFor ) {
fonts . forEach ( function ( font ) {
// Check if the font is already loaded
if ( ! font . NeedStyles ) { return ; }
// Pick the variants we need (regular, bold, italic)
[ 'indexR' , 'indexB' , 'indexI' , 'indexBI' ] . forEach ( function ( k ) {
if ( typeof ( font [ k ] ) !== "number" || font [ k ] === - 1 ) { return ; } // No matching file
var file = files [ font [ k ] ] ;
var name = font . Name + suffixes [ k ] + '.ttf' ;
Util . fetch ( path + file . Id , waitFor ( function ( err , buffer ) {
if ( buffer ) {
x2t . FS . writeFile ( '/working/fonts/' + name , buffer ) ;
}
} ) ) ;
} ) ;
} ) ;
} ) . nThen ( function ( ) {
x2tReady . fire ( ) ;
} ) ;
} ;
var x2tInitialized = false ;
var x2tInit = function ( x2t ) {
debug ( "x2t mount" ) ;
// x2t.FS.mount(x2t.MEMFS, {} , '/');
x2t . FS . mkdir ( '/working' ) ;
x2t . FS . mkdir ( '/working/media' ) ;
x2t . FS . mkdir ( '/working/fonts' ) ;
x2tInitialized = true ;
fetchFonts ( x2t ) ;
debug ( "x2t mount done" ) ;
} ;
var getX2T = function ( cb ) {
// Perform the x2t conversion
require ( [ '/common/onlyoffice/x2t/x2t.js' ] , function ( ) { // FIXME why does this fail without an access-control-allow-origin header?
var x2t = window . Module ;
x2t . run ( ) ;
if ( x2tInitialized ) {
debug ( "x2t runtime already initialized" ) ;
return void x2tReady . reg ( function ( ) {
cb ( x2t ) ;
} ) ;
}
x2t . onRuntimeInitialized = function ( ) {
debug ( "x2t in runtime initialized" ) ;
// Init x2t js module
x2tInit ( x2t ) ;
x2tReady . reg ( function ( ) {
cb ( x2t ) ;
} ) ;
} ;
} ) ;
} ;
/ *
Converting Data
This function converts a data in a specific format to the outputformat
The filename extension needs to represent the input format
Example : fileName = cryptpad . bin outputFormat = xlsx
* /
var getFormatId = function ( ext ) {
// Sheets
if ( ext === 'xlsx' ) { return 257 ; }
if ( ext === 'xls' ) { return 258 ; }
if ( ext === 'ods' ) { return 259 ; }
if ( ext === 'csv' ) { return 260 ; }
if ( ext === 'pdf' ) { return 513 ; }
return ;
} ;
var getFromId = function ( ext ) {
var id = getFormatId ( ext ) ;
if ( ! id ) { return '' ; }
return '<m_nFormatFrom>' + id + '</m_nFormatFrom>' ;
} ;
var getToId = function ( ext ) {
var id = getFormatId ( ext ) ;
if ( ! id ) { return '' ; }
return '<m_nFormatTo>' + id + '</m_nFormatTo>' ;
} ;
var x2tConvertDataInternal = function ( x2t , data , fileName , outputFormat ) {
debug ( "Converting Data for " + fileName + " to " + outputFormat ) ;
// PDF
var pdfData = '' ;
if ( outputFormat === "pdf" && typeof ( data ) === "object" && data . bin && data . buffer ) {
// Add conversion rules
pdfData = "<m_bIsNoBase64>false</m_bIsNoBase64>" +
"<m_sFontDir>/working/fonts/</m_sFontDir>" ;
// writing file to mounted working disk (in memory)
x2t . FS . writeFile ( '/working/' + fileName , data . bin ) ;
x2t . FS . writeFile ( '/working/pdf.bin' , data . buffer ) ;
} else {
// writing file to mounted working disk (in memory)
x2t . FS . writeFile ( '/working/' + fileName , data ) ;
}
// Adding images
Object . keys ( window . frames [ 0 ] . AscCommon . g _oDocumentUrls . urls || { } ) . forEach ( function ( _mediaFileName ) {
var mediaFileName = _mediaFileName . substring ( 6 ) ;
var mediasSources = getMediasSources ( ) ;
var mediaSource = mediasSources [ mediaFileName ] ;
var mediaData = mediaSource ? mediasData [ mediaSource . src ] : undefined ;
if ( mediaData ) {
debug ( "Writing media data " + mediaFileName ) ;
debug ( "Data" ) ;
var fileData = mediaData . content ;
x2t . FS . writeFile ( '/working/media/' + mediaFileName , new Uint8Array ( fileData ) ) ;
} else {
debug ( "Could not find media content for " + mediaFileName ) ;
}
} ) ;
var inputFormat = fileName . split ( '.' ) . pop ( ) ;
var params = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<TaskQueueDataConvert xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
+ "<m_sFileFrom>/working/" + fileName + "</m_sFileFrom>"
+ "<m_sFileTo>/working/" + fileName + "." + outputFormat + "</m_sFileTo>"
+ pdfData
+ getFromId ( inputFormat )
+ getToId ( outputFormat )
+ "<m_bIsNoBase64>false</m_bIsNoBase64>"
+ "</TaskQueueDataConvert>" ;
// writing params file to mounted working disk (in memory)
x2t . FS . writeFile ( '/working/params.xml' , params ) ;
// running conversion
x2t . ccall ( "runX2T" , [ "number" ] , [ "string" ] , [ "/working/params.xml" ] ) ;
// reading output file from working disk (in memory)
var result ;
try {
result = x2t . FS . readFile ( '/working/' + fileName + "." + outputFormat ) ;
} catch ( e ) {
debug ( "Failed reading converted file" ) ;
UI . removeModals ( ) ;
UI . warn ( Messages . error ) ;
return "" ;
}
return result ;
} ;
APP . printPdf = function ( obj , cb ) {
getX2T ( function ( x2t ) {
//var e = getEditor();
//var d = e.asc_nativePrint(undefined, undefined, 0x100 + opts.printType).ImData;
var bin = getContent ( ) ;
var xlsData = x2tConvertDataInternal ( x2t , {
buffer : obj . data ,
bin : bin
} , 'output.bin' , 'pdf' ) ;
if ( xlsData ) {
var md = common . getMetadataMgr ( ) . getMetadataLazy ( ) ;
var type = common . getMetadataMgr ( ) . getPrivateData ( ) . ooType ;
var title = md . title || md . defaultTitle || type ;
var blob = new Blob ( [ xlsData ] , { type : "application/pdf" } ) ;
//var url = URL.createObjectURL(blob, { type: "application/pdf" });
saveAs ( blob , title + '.pdf' ) ;
//window.open(url);
cb ( {
"type" : "save" ,
"status" : "ok" ,
//"data":url + "?disposition=inline&ooname=output.pdf"
} ) ;
/ *
ooChannel . send ( {
"type" : "documentOpen" ,
"data" : {
"type" : "save" ,
"status" : "ok" ,
"data" : url + "?disposition=inline&ooname=output.pdf"
}
} ) ;
* /
}
} ) ;
} ;
var x2tSaveAndConvertDataInternal = function ( x2t , data , filename , extension , finalFilename ) {
var type = common . getMetadataMgr ( ) . getPrivateData ( ) . ooType ;
var xlsData ;
// PDF
if ( type === "sheet" && extension === "pdf" ) {
var e = getEditor ( ) ;
var d = e . asc _nativePrint ( undefined , undefined , 0x101 ) . ImData ;
xlsData = x2tConvertDataInternal ( x2t , {
buffer : d . data ,
bin : data
} , filename , extension ) ;
if ( xlsData ) {
var _blob = new Blob ( [ xlsData ] , { type : "application/bin;charset=utf-8" } ) ;
UI . removeModals ( ) ;
saveAs ( _blob , finalFilename ) ;
}
return ;
}
if ( type === "sheet" && extension !== 'xlsx' ) {
xlsData = x2tConvertDataInternal ( x2t , data , filename , 'xlsx' ) ;
filename += '.xlsx' ;
} else if ( type === "presentation" && extension !== "pptx" ) {
xlsData = x2tConvertDataInternal ( x2t , data , filename , 'pptx' ) ;
filename += '.pptx' ;
} else if ( type === "doc" && extension !== "docx" ) {
xlsData = x2tConvertDataInternal ( x2t , data , filename , 'docx' ) ;
filename += '.docx' ;
}
xlsData = x2tConvertDataInternal ( x2t , data , filename , extension ) ;
if ( xlsData ) {
var blob = new Blob ( [ xlsData ] , { type : "application/bin;charset=utf-8" } ) ;
UI . removeModals ( ) ;
saveAs ( blob , finalFilename ) ;
}
} ;
var x2tSaveAndConvertData = function ( data , filename , extension , finalName ) {
getX2T ( function ( x2t ) {
x2tSaveAndConvertDataInternal ( x2t , data , filename , extension , finalName ) ;
} ) ;
} ;
var exportXLSXFile = function ( ) {
var text = getContent ( ) ;
var suggestion = Title . suggestTitle ( Title . defaultTitle ) ;
var ext = [ '.xlsx' , '.ods' , '.bin' , '.csv' , '.pdf' ] ;
var type = common . getMetadataMgr ( ) . getPrivateData ( ) . ooType ;
var warning = '' ;
if ( type === "presentation" ) {
ext = [ '.pptx' , /*'.odp',*/ '.bin' ] ;
} else if ( type === "doc" ) {
ext = [ '.docx' , /*'.odt',*/ '.bin' ] ;
}
if ( ! supportsXLSX ( ) ) {
ext = [ '.bin' ] ;
warning = h ( 'div.alert.alert-info.cp-alert-top' , Messages . oo _exportChrome ) ;
}
var types = ext . map ( function ( val ) {
return {
tag : 'a' ,
attributes : {
'data-value' : val ,
href : '#'
} ,
content : val
} ;
} ) ;
var dropdownConfig = {
text : ext [ 0 ] , // Button initial text
caretDown : true ,
options : types , // Entries displayed in the menu
isSelect : true ,
initialValue : ext [ 0 ] ,
common : common
} ;
var $select = UIElements . createDropdown ( dropdownConfig ) ;
var promptMessage = h ( 'span' , [
Messages . exportPrompt ,
warning
] ) ;
UI . prompt ( promptMessage , Util . fixFileName ( suggestion ) , function ( filename ) {
// $select.getValue()
if ( ! ( typeof ( filename ) === 'string' && filename ) ) { return ; }
var ext = ( $select . getValue ( ) || '' ) . slice ( 1 ) ;
if ( ext === 'bin' ) {
var blob = new Blob ( [ text ] , { type : "application/bin;charset=utf-8" } ) ;
saveAs ( blob , filename + '.bin' ) ;
return ;
}
var content = h ( 'div.cp-oo-x2tXls' , [
h ( 'span.fa.fa-spin.fa-spinner' ) ,
h ( 'span' , Messages . oo _exportInProgress )
] ) ;
UI . openCustomModal ( UI . dialog . customModal ( content , { buttons : [ ] } ) ) ;
setTimeout ( function ( ) {
x2tSaveAndConvertData ( text , "filename.bin" , ext , filename + '.' + ext ) ;
} , 100 ) ;
} , {
typeInput : $select [ 0 ]
} , true ) ;
$select . find ( 'button' ) . addClass ( 'btn' ) ;
} ;
var x2tImportImagesInternal = function ( x2t , images , i , callback ) {
if ( i >= images . length ) {
callback ( ) ;
} else {
debug ( "Import image " + i ) ;
var handleFileData = {
name : images [ i ] ,
mediasSources : getMediasSources ( ) ,
callback : function ( ) {
debug ( "next image" ) ;
x2tImportImagesInternal ( x2t , images , i + 1 , callback ) ;
} ,
} ;
var filePath = "/working/media/" + images [ i ] ;
debug ( "Import filename " + filePath ) ;
var fileData = x2t . FS . readFile ( "/working/media/" + images [ i ] , { encoding : "binary" } ) ;
debug ( "Importing data" ) ;
debug ( "Buffer" ) ;
debug ( fileData . buffer ) ;
var blob = new Blob ( [ fileData . buffer ] , { type : 'image/png' } ) ;
blob . name = images [ i ] ;
APP . FMImages . handleFile ( blob , handleFileData ) ;
}
} ;
var x2tImportImages = function ( x2t , callback ) {
if ( ! APP . FMImages ) {
var fmConfigImages = {
noHandlers : true ,
noStore : true ,
body : $ ( 'body' ) ,
onUploaded : function ( ev , data ) {
if ( ! ev . callback ) { return ; }
debug ( "Image uploaded at " + data . url ) ;
var parsed = Hash . parsePadUrl ( data . url ) ;
if ( parsed . type === 'file' ) {
var secret = Hash . getSecrets ( 'file' , parsed . hash , data . password ) ;
var fileHost = privateData . fileHost || privateData . origin ;
var src = fileHost + Hash . getBlobPathFromHex ( secret . channel ) ;
var key = Hash . encodeBase64 ( secret . keys . cryptKey ) ;
debug ( "Final src: " + src ) ;
ev . mediasSources [ ev . name ] = { name : ev . name , src : src , key : key } ;
}
ev . callback ( ) ;
}
} ;
APP . FMImages = common . createFileManager ( fmConfigImages ) ;
}
// Import Images
debug ( "Import Images" ) ;
var files = x2t . FS . readdir ( "/working/media/" ) ;
var images = [ ] ;
files . forEach ( function ( file ) {
if ( file !== "." && file !== ".." ) {
images . push ( file ) ;
}
} ) ;
x2tImportImagesInternal ( x2t , images , 0 , function ( ) {
debug ( "Sync media sources elements" ) ;
debug ( getMediasSources ( ) ) ;
APP . onLocal ( ) ;
debug ( "Import Images finalized" ) ;
callback ( ) ;
} ) ;
} ;
var x2tConvertData = function ( x2t , data , filename , extension , callback ) {
var convertedContent ;
// Convert from ODF format:
// first convert to Office format then to the selected extension
if ( filename . endsWith ( ".ods" ) ) {
console . log ( x2t , data , filename , extension ) ;
convertedContent = x2tConvertDataInternal ( x2t , new Uint8Array ( data ) , filename , "xlsx" ) ;
console . log ( convertedContent ) ;
convertedContent = x2tConvertDataInternal ( x2t , convertedContent , filename + ".xlsx" , extension ) ;
} else if ( filename . endsWith ( ".odt" ) ) {
convertedContent = x2tConvertDataInternal ( x2t , new Uint8Array ( data ) , filename , "docx" ) ;
convertedContent = x2tConvertDataInternal ( x2t , convertedContent , filename + ".docx" , extension ) ;
} else if ( filename . endsWith ( ".odp" ) ) {
convertedContent = x2tConvertDataInternal ( x2t , new Uint8Array ( data ) , filename , "pptx" ) ;
convertedContent = x2tConvertDataInternal ( x2t , convertedContent , filename + ".pptx" , extension ) ;
} else {
convertedContent = x2tConvertDataInternal ( x2t , new Uint8Array ( data ) , filename , extension ) ;
}
x2tImportImages ( x2t , function ( ) {
callback ( convertedContent ) ;
} ) ;
} ;
var importFile = function ( content ) {
// Abort if there is another real user in the channel (history keeper excluded)
var m = metadataMgr . getChannelMembers ( ) . slice ( ) . filter ( function ( nId ) {
return nId . length === 32 ;
} ) ;
if ( m . length > 1 ) {
UI . removeModals ( ) ;
return void UI . alert ( Messages . oo _cantUpload ) ;
}
if ( ! content ) {
UI . removeModals ( ) ;
return void UI . alert ( Messages . oo _invalidFormat ) ;
}
var blob = new Blob ( [ content ] , { type : 'plain/text' } ) ;
var file = getFileType ( ) ;
blob . name = ( metadataMgr . getMetadataLazy ( ) . title || file . doc ) + '.' + file . type ;
var uploadedCallback = function ( ) {
UI . removeModals ( ) ;
UI . confirm ( Messages . oo _uploaded , function ( yes ) {
try {
getEditor ( ) . asc _setRestriction ( true ) ;
} catch ( e ) { }
if ( ! yes ) { return ; }
common . gotoURL ( ) ;
} ) ;
} ;
var data = {
hash : ooChannel . lastHash ,
index : ooChannel . cpIndex ,
callback : uploadedCallback
} ;
APP . FM . handleFile ( blob , data ) ;
} ;
var importXLSXFile = function ( content , filename , ext ) {
// Perform the x2t conversion
debug ( "Filename" ) ;
debug ( filename ) ;
if ( ext === "bin" ) {
return void importFile ( content ) ;
}
if ( ! supportsXLSX ( ) ) {
return void UI . alert ( Messages . oo _invalidFormat ) ;
}
var div = h ( 'div.cp-oo-x2tXls' , [
h ( 'span.fa.fa-spin.fa-spinner' ) ,
h ( 'span' , Messages . oo _importInProgress )
] ) ;
UI . openCustomModal ( UI . dialog . customModal ( div , { buttons : [ ] } ) ) ;
setTimeout ( function ( ) {
getX2T ( function ( x2t ) {
x2tConvertData ( x2t , new Uint8Array ( content ) , filename . name , "bin" , function ( c ) {
importFile ( c ) ;
} ) ;
} ) ;
} , 100 ) ;
} ;
var loadDocument = function ( noCp , useNewDefault , i ) {
if ( ooLoaded ) { return ; }
var type = common . getMetadataMgr ( ) . getPrivateData ( ) . ooType ;
var file = getFileType ( ) ;
if ( ! noCp ) {
var lastCp = getLastCp ( false , i ) ;
// If the last checkpoint is empty, load the "initial" doc instead
if ( ! lastCp || ! lastCp . file ) { return void loadDocument ( true , useNewDefault ) ; }
// Load latest checkpoint
return void loadLastDocument ( lastCp , function ( ) {
// Checkpoint error: load the previous one
i = i || 0 ;
loadDocument ( noCp , useNewDefault , ++ i ) ;
} ) ;
}
var newText ;
switch ( type ) {
case 'sheet' :
newText = EmptyCell ( useNewDefault ) ;
break ;
case 'doc' :
newText = EmptyDoc ( ) ;
break ;
case 'presentation' :
newText = EmptySlide ( ) ;
break ;
default :
newText = '' ;
}
var blob = loadInitDocument ( type , useNewDefault ) ;
startOO ( blob , file ) ;
} ;
var initializing = true ;
var $bar = $ ( '#cp-toolbar' ) ;
config = {
patchTransformer : ChainPad . SmartJSONTransformer ,
// cryptpad debug logging (default is 1)
// logLevel: 0,
validateContent : function ( content ) {
try {
JSON . parse ( content ) ;
return true ;
} catch ( e ) {
debug ( "Failed to parse, rejecting patch" ) ;
return false ;
}
}
} ;
var stringifyInner = function ( ) {
var obj = {
content : content ,
metadata : metadataMgr . getMetadataLazy ( )
} ;
// stringify the json and send it into chainpad
return stringify ( obj ) ;
} ;
var pinImages = function ( ) {
if ( content . mediasSources ) {
var toPin = Object . keys ( content . mediasSources || { } ) . map ( function ( id ) {
var data = content . mediasSources [ id ] || { } ;
var src = data . src ;
if ( ! src ) { return ; }
// Remove trailing slash
if ( src . slice ( - 1 ) === '/' ) {
src = src . slice ( 0 , - 1 ) ;
}
// Extract the channel id from the source href
return src . slice ( src . lastIndexOf ( '/' ) + 1 ) ;
} ) . filter ( Boolean ) ;
sframeChan . query ( 'EV_OO_PIN_IMAGES' , toPin ) ;
}
} ;
var wasEditing = false ;
var setStrictEditing = function ( ) {
if ( APP . isFast ) { return ; }
var editor = getEditor ( ) ;
var editing = editor . asc _isDocumentModified ? editor . asc _isDocumentModified ( ) : editor . isDocumentModified ( ) ;
if ( editing ) {
evOnPatch . fire ( ) ;
} else {
evOnSync . fire ( ) ;
}
wasEditing = Boolean ( editing ) ;
} ;
APP . onFastChange = function ( isFast ) {
APP . isFast = isFast ;
if ( isFast ) {
wasEditing = false ;
if ( APP . hasChangedInterval ) {
window . clearInterval ( APP . hasChangedInterval ) ;
}
return ;
}
setStrictEditing ( ) ;
APP . hasChangedInterval = window . setInterval ( setStrictEditing , 500 ) ;
} ;
APP . getContent = function ( ) { return content ; } ;
APP . onLocal = config . onLocal = function ( ) {
if ( initializing ) { return ; }
if ( readOnly ) { return ; }
// Update metadata
var content = stringifyInner ( ) ;
APP . realtime . contentUpdate ( content ) ;
pinImages ( ) ;
} ;
var loadCp = function ( cp , keepQueue ) {
if ( ! isLockedModal . modal ) {
isLockedModal . modal = UI . openCustomModal ( isLockedModal . content ) ;
}
loadLastDocument ( cp , function ( ) {
var file = getFileType ( ) ;
var type = common . getMetadataMgr ( ) . getPrivateData ( ) . ooType ;
var blob = loadInitDocument ( type , true ) ;
if ( ! keepQueue ) { ooChannel . queue = [ ] ; }
resetData ( blob , file ) ;
} , function ( blob , file ) {
if ( ! keepQueue ) { ooChannel . queue = [ ] ; }
resetData ( blob , file ) ;
} ) ;
} ;
var loadTemplate = function ( href , pw , parsed ) {
APP . history = true ;
APP . template = true ;
var editor = getEditor ( ) ;
if ( editor ) {
try { getEditor ( ) . asc _setRestriction ( true ) ; } catch ( e ) { }
}
var content = parsed . content ;
// Get checkpoint
var hashes = content . hashes || { } ;
var idx = sortCpIndex ( hashes ) ;
var lastIndex = idx [ idx . length - 1 ] ;
var lastCp = hashes [ lastIndex ] || { } ;
// Current cp or initial hash (invalid hash ==> initial hash)
var toHash = lastCp . hash || 'NONE' ;
// Last hash
var fromHash = 'NONE' ;
sframeChan . query ( 'Q_GET_HISTORY_RANGE' , {
href : href ,
password : pw ,
channel : content . channel ,
lastKnownHash : fromHash ,
toHash : toHash ,
} , function ( err , data ) {
if ( err ) { return void console . error ( err ) ; }
if ( ! Array . isArray ( data . messages ) ) { return void console . error ( 'Not an array!' ) ; }
// The first "cp" in history is the empty doc. It doesn't include the first patch
// of the history
var initialCp = ! lastCp . hash ;
var messages = ( data . messages || [ ] ) . slice ( initialCp ? 0 : 1 ) ;
ooChannel . queue = messages . map ( function ( obj ) {
return {
hash : obj . serverHash ,
msg : JSON . parse ( obj . msg )
} ;
} ) ;
ooChannel . historyLastHash = ooChannel . lastHash ;
ooChannel . currentIndex = ooChannel . cpIndex ;
loadCp ( lastCp , true ) ;
} ) ;
} ;
var openTemplatePicker = function ( ) {
var metadataMgr = common . getMetadataMgr ( ) ;
var type = metadataMgr . getPrivateData ( ) . app ;
var sframeChan = common . getSframeChannel ( ) ;
var pickerCfgInit = {
types : [ type ] ,
where : [ 'template' ] ,
hidden : true
} ;
var pickerCfg = {
types : [ type ] ,
where : [ 'template' ] ,
} ;
var onConfirm = function ( ) {
common . openFilePicker ( pickerCfg , function ( data ) {
if ( data . type !== type ) { return ; }
UI . addLoadingScreen ( { hideTips : true } ) ;
sframeChan . query ( 'Q_OO_TEMPLATE_USE' , {
href : data . href ,
} , function ( err , val ) {
var parsed ;
try {
parsed = JSON . parse ( val ) ;
} catch ( e ) {
console . error ( e , val ) ;
UI . removeLoadingScreen ( ) ;
return void UI . warn ( Messages . error ) ;
}
console . error ( data ) ;
loadTemplate ( data . href , data . password , parsed ) ;
} ) ;
} ) ;
} ;
sframeChan . query ( "Q_TEMPLATE_EXIST" , type , function ( err , data ) {
if ( data ) {
common . openFilePicker ( pickerCfgInit ) ;
onConfirm ( ) ;
} else {
UI . alert ( Messages . template _empty ) ;
}
} ) ;
} ;
config . onInit = function ( info ) {
var privateData = metadataMgr . getPrivateData ( ) ;
metadataMgr . setDegraded ( false ) ; // FIXME degraded moded unsupported (no cursor channel)
readOnly = privateData . readOnly ;
Title = common . createTitle ( { } ) ;
var configTb = {
displayed : [ 'pad' ] ,
title : Title . getTitleConfig ( ) ,
metadataMgr : metadataMgr ,
readOnly : readOnly ,
realtime : info . realtime ,
spinner : {
onPatch : evOnPatch ,
onSync : evOnSync
} ,
sfCommon : common ,
$container : $bar ,
$contentContainer : $ ( '#cp-app-oo-container' )
} ;
toolbar = APP . toolbar = Toolbar . create ( configTb ) ;
toolbar . showColors ( ) ;
Title . setToolbar ( toolbar ) ;
if ( window . CP _DEV _MODE ) {
var $save = common . createButton ( 'save' , true , { } , function ( ) {
makeCheckpoint ( true ) ;
} ) ;
$save . appendTo ( toolbar . $bottomM ) ;
}
if ( ! privateData . ooVersionHash ) {
( function ( ) {
/* add a history button */
var commit = function ( ) {
// Wait for the checkpoint to be uploaded before leaving history mode
// (race condition). We use "stopHistory" to remove the history
// flag only when the checkpoint is ready.
APP . stopHistory = true ;
makeCheckpoint ( true ) ;
} ;
var onPatch = function ( patch ) {
// Patch on the current cp
ooChannel . send ( JSON . parse ( patch . msg ) ) ;
} ;
var onCheckpoint = function ( cp ) {
// We want to load a checkpoint:
loadCp ( cp ) ;
} ;
var setHistoryMode = function ( bool ) {
if ( bool ) {
APP . history = true ;
try { getEditor ( ) . asc _setRestriction ( true ) ; } catch ( e ) { }
return ;
}
// Cancel button: redraw from lastCp
APP . history = false ;
ooChannel . queue = [ ] ;
ooChannel . ready = false ;
// Fill the queue and then load the last CP
rtChannel . getHistory ( function ( ) {
var lastCp = getLastCp ( ) ;
loadCp ( lastCp , true ) ;
} ) ;
} ;
var deleteSnapshot = function ( hash ) {
var md = Util . clone ( cpNfInner . metadataMgr . getMetadata ( ) ) ;
var snapshots = md . snapshots = md . snapshots || { } ;
delete snapshots [ hash ] ;
metadataMgr . updateMetadata ( md ) ;
APP . onLocal ( ) ;
} ;
var makeSnapshot = function ( title , cb , obj ) {
var hash , time ;
if ( obj && obj . hash && obj . time ) {
hash = obj . hash ;
time = obj . time ;
} else {
var major = Object . keys ( content . hashes ) . length ;
var cpIndex = getLastCp ( ) . index || 0 ;
var minor = ooChannel . cpIndex - cpIndex ;
hash = major + '.' + minor ;
time = + new Date ( ) ;
}
var md = Util . clone ( metadataMgr . getMetadata ( ) ) ;
var snapshots = md . snapshots = md . snapshots || { } ;
if ( snapshots [ hash ] ) { cb ( 'EEXISTS' ) ; return void UI . warn ( Messages . snapshot _error _exists ) ; }
snapshots [ hash ] = {
title : title ,
time : time
} ;
metadataMgr . updateMetadata ( md ) ;
APP . onLocal ( ) ;
APP . realtime . onSettle ( cb ) ;
} ;
var loadSnapshot = function ( hash ) {
sframeChan . event ( 'EV_OO_OPENVERSION' , {
hash : hash
} ) ;
} ;
common . createButton ( '' , true , {
name : 'history' ,
icon : 'fa-history' ,
text : Messages . historyText ,
tippy : Messages . historyButton
} ) . click ( function ( ) {
ooChannel . historyLastHash = ooChannel . lastHash ;
ooChannel . currentIndex = ooChannel . cpIndex ;
Feedback . send ( 'OO_HISTORY' ) ;
var histConfig = {
onPatch : onPatch ,
onCheckpoint : onCheckpoint ,
onRevert : commit ,
setHistory : setHistoryMode ,
makeSnapshot : makeSnapshot ,
onlyoffice : {
hashes : content . hashes || { } ,
channel : content . channel ,
lastHash : ooChannel . lastHash
} ,
$toolbar : $ ( '.cp-toolbar-container' )
} ;
History . create ( common , histConfig ) ;
} ) . appendTo ( toolbar . $drawer ) ;
// Snapshots
var $snapshot = common . createButton ( 'snapshots' , true , {
remove : deleteSnapshot ,
make : makeSnapshot ,
load : loadSnapshot
} ) ;
toolbar . $drawer . append ( $snapshot ) ;
// Import template
var $template = common . createButton ( 'importtemplate' , true , { } , openTemplatePicker ) ;
if ( $template && typeof ( $template . appendTo ) === 'function' ) {
$template . appendTo ( toolbar . $drawer ) ;
}
} ) ( ) ;
}
if ( window . CP _DEV _MODE || DISPLAY _RESTORE _BUTTON ) {
common . createButton ( '' , true , {
name : 'delete' ,
icon : 'fa-trash' ,
hiddenReadOnly : true
} ) . click ( function ( ) {
if ( initializing ) { return void console . error ( 'initializing' ) ; }
deleteLastCp ( ) ;
} ) . attr ( 'title' , 'Delete last checkpoint' ) . appendTo ( toolbar . $bottomM ) ;
common . createButton ( '' , true , {
name : 'restore' ,
icon : 'fa-history' ,
hiddenReadOnly : true
} ) . click ( function ( ) {
if ( initializing ) { return void console . error ( 'initializing' ) ; }
restoreLastCp ( ) ;
} ) . attr ( 'title' , 'Restore last checkpoint' ) . appendTo ( toolbar . $bottomM ) ;
}
var $exportXLSX = common . createButton ( 'export' , true , { } , exportXLSXFile ) ;
$exportXLSX . appendTo ( toolbar . $drawer ) ;
var type = privateData . ooType ;
var accept = [ ".bin" , ".ods" , ".xlsx" ] ;
if ( type === "presentation" ) {
accept = [ '.bin' , '.odp' , '.pptx' ] ;
} else if ( type === "doc" ) {
accept = [ '.bin' , '.odt' , '.docx' ] ;
}
var first ;
if ( ! supportsXLSX ( ) ) {
accept = [ '.bin' ] ;
first = function ( cb ) {
var msg = h ( 'span' , [
Messages . oo _exportChrome ,
' ' , h ( 'span' , Messages . oo _importBin ) ,
] ) ;
UI . confirm ( msg , function ( yes ) {
if ( yes ) {
cb ( ) ;
}
} ) ;
} ;
}
if ( common . isLoggedIn ( ) ) {
window . CryptPad _deleteLastCp = deleteLastCp ;
var $importXLSX = common . createButton ( 'import' , true , {
accept : accept ,
binary : [ "ods" , "xlsx" , "odt" , "docx" , "odp" , "pptx" ] ,
first : first ,
} , importXLSXFile ) ;
$importXLSX . appendTo ( toolbar . $drawer ) ;
common . createButton ( 'hashtag' , true ) . appendTo ( toolbar . $drawer ) ;
}
var $forget = common . createButton ( 'forget' , true , { } , function ( err ) {
if ( err ) { return ; }
setEditable ( false ) ;
} ) ;
toolbar . $drawer . append ( $forget ) ;
if ( ! privateData . isEmbed ) {
var helpMenu = APP . helpMenu = common . createHelpMenu ( [ 'beta' , 'oo' ] ) ;
$ ( '#cp-app-oo-editor' ) . prepend ( common . getBurnAfterReadingWarning ( ) ) ;
$ ( '#cp-app-oo-editor' ) . prepend ( helpMenu . menu ) ;
toolbar . $drawer . append ( helpMenu . button ) ;
}
var $properties = common . createButton ( 'properties' , true ) ;
toolbar . $drawer . append ( $properties ) ;
} ;
var noCache = false ; // Prevent reload loops
var onCorruptedCache = function ( ) {
if ( noCache ) {
UI . errorLoadingScreen ( Messages . unableToDisplay , false , function ( ) {
common . gotoURL ( '' ) ;
} ) ;
}
noCache = true ;
var sframeChan = common . getSframeChannel ( ) ;
sframeChan . event ( "EV_CORRUPTED_CACHE" ) ;
} ;
var firstReady = true ;
config . onReady = function ( info ) {
if ( APP . realtime !== info . realtime ) {
APP . realtime = info . realtime ;
}
var userDoc = APP . realtime . getUserDoc ( ) ;
var isNew = false ;
var newDoc = true ;
if ( userDoc === "" || userDoc === "{}" ) { isNew = true ; }
if ( userDoc !== "" ) {
var hjson = JSON . parse ( userDoc ) ;
if ( hjson && hjson . metadata ) {
metadataMgr . updateMetadata ( hjson . metadata ) ;
}
if ( typeof ( hjson ) !== 'object' || Array . isArray ( hjson ) ||
( hjson . metadata && typeof ( hjson . metadata . type ) !== 'undefined' &&
hjson . metadata . type !== 'oo' ) ) {
var errorText = Messages . typeError ;
UI . errorLoadingScreen ( errorText ) ;
throw new Error ( errorText ) ;
}
content = hjson . content || content ;
var newLatest = getLastCp ( ) ;
sframeChan . query ( 'Q_OO_SAVE' , {
hash : newLatest . hash ,
url : newLatest . file
} , function ( ) { } ) ;
newDoc = ! content . hashes || Object . keys ( content . hashes ) . length === 0 ;
} else if ( ! privateData . isNewFile ) {
// This is an empty doc but not a new file: error
onCorruptedCache ( ) ;
return void console . error ( "Empty chainpad for a non-empty doc" ) ;
} else {
Title . updateTitle ( Title . defaultTitle ) ;
}
var version = CURRENT _VERSION + '/' ;
var msg ;
// Old version detected: use the old OO and start the migration if we can
if ( privateData . ooForceVersion ) {
if ( privateData . ooForceVersion === "1" ) {
version = "v1/" ;
}
} else if ( content && ( ! content . version || content . version === 1 ) ) {
version = 'v1/' ;
APP . migrate = true ;
// Registedred ~~users~~ editors can start the migration
if ( common . isLoggedIn ( ) && ! readOnly ) {
content . migration = true ;
APP . onLocal ( ) ;
} else {
msg = h ( 'div.alert.alert-warning.cp-burn-after-reading' , Messages . oo _sheetMigration _anonymousEditor ) ;
if ( APP . helpMenu ) {
$ ( APP . helpMenu . menu ) . after ( msg ) ;
} else {
$ ( '#cp-app-oo-editor' ) . prepend ( msg ) ;
}
readOnly = true ;
}
} else if ( content && content . version <= 3 ) { // V2 or V3
version = 'v2b/' ;
APP . migrate = true ;
// Registedred ~~users~~ editors can start the migration
if ( common . isLoggedIn ( ) && ! readOnly ) {
content . migration = true ;
APP . onLocal ( ) ;
} else {
msg = h ( 'div.alert.alert-warning.cp-burn-after-reading' , Messages . oo _sheetMigration _anonymousEditor ) ;
if ( APP . helpMenu ) {
$ ( APP . helpMenu . menu ) . after ( msg ) ;
} else {
$ ( '#cp-app-oo-editor' ) . prepend ( msg ) ;
}
readOnly = true ;
}
}
// If the sheet is locked by an offline user, remove it
if ( content && content . saveLock && ! isUserOnline ( content . saveLock ) ) {
content . saveLock = undefined ;
APP . onLocal ( ) ;
} else if ( content && content . saveLock ) {
// If someone is currently creating a checkpoint (and locking the sheet),
// make sure it will end (maybe you'll have to make the checkpoint yourself)
checkCheckpoint ( ) ;
}
var s = h ( 'script' , {
type : 'text/javascript' ,
src : '/common/onlyoffice/' + version + 'web-apps/apps/api/documents/api.js'
} ) ;
$ ( '#cp-app-oo-editor' ) . append ( s ) ;
if ( metadataMgr . getPrivateData ( ) . burnAfterReading && content && content . channel ) {
sframeChan . event ( 'EV_BURN_PAD' , content . channel ) ;
}
if ( privateData . ooVersionHash ) {
return void openVersionHash ( privateData . ooVersionHash ) ;
}
// Only execute the following code the first time we call onReady
if ( ! firstReady ) {
setMyId ( ) ;
oldHashes = JSON . parse ( JSON . stringify ( content . hashes ) ) ;
initializing = false ;
return void setEditable ( ! readOnly ) ;
}
firstReady = false ;
var useNewDefault = content . version && content . version >= 2 ;
openRtChannel ( function ( ) {
setMyId ( ) ;
oldHashes = JSON . parse ( JSON . stringify ( content . hashes ) ) ;
initializing = false ;
common . openPadChat ( APP . onLocal ) ;
if ( ! readOnly ) {
var cursors = { } ;
common . openCursorChannel ( APP . onLocal ) ;
cursor = common . createCursor ( APP . onLocal ) ;
cursor . onCursorUpdate ( function ( data ) {
// Leaving user
if ( data && data . leave && data . id ) {
// When a netflux user leaves, remove all their cursors
Object . keys ( cursors ) . forEach ( function ( ooid ) {
var d = cursors [ ooid ] ;
if ( d !== data . id ) { return ; } // Only continue for the leaving user
// Remove from OO UI
ooChannel . send ( {
type : "cursor" ,
messages : [ {
cursor : "10;AgAAADIAAAAAAA==" ,
time : + new Date ( ) ,
user : ooid ,
useridoriginal : String ( ooid ) . slice ( 0 , - 1 ) ,
} ]
} ) ;
// Remove from memory
delete cursors [ ooid ] ;
} ) ;
handleNewIds ( { } , content . ids ) ;
}
// Cursor update
if ( ! data || ! data . cursor ) { return ; }
// Store the new cursor in memory for this user, with their netflux ID
var ooid = Util . find ( data . cursor , [ 'messages' , 0 , 'user' ] ) ;
if ( ooid ) { cursors [ ooid ] = data . id . slice ( 0 , 32 ) ; }
// Update cursor in the UI
ooChannel . send ( data . cursor ) ;
} ) ;
}
if ( APP . startWithTemplate ) {
var template = APP . startWithTemplate ;
loadTemplate ( template . href , template . password , template . content ) ;
return ;
}
loadDocument ( newDoc , useNewDefault ) ;
setEditable ( ! readOnly ) ;
UI . removeLoadingScreen ( ) ;
} ) ;
} ;
config . onError = function ( err ) {
common . onServerError ( err , toolbar , function ( ) {
setEditable ( false ) ;
} ) ;
} ;
var reloadPopup = false ;
var checkNewCheckpoint = function ( ) {
if ( ! isLockedModal . modal ) {
isLockedModal . modal = UI . openCustomModal ( isLockedModal . content ) ;
}
var lastCp = getLastCp ( ) ;
loadLastDocument ( lastCp , function ( err ) {
console . error ( err ) ;
// On error, do nothing
// FIXME lock the document or ask for a page reload?
} , function ( blob , type ) {
resetData ( blob , type ) ;
} ) ;
} ;
config . onRemote = function ( ) {
if ( initializing ) { return ; }
var userDoc = APP . realtime . getUserDoc ( ) ;
var json = JSON . parse ( userDoc ) ;
if ( json . metadata ) {
metadataMgr . updateMetadata ( json . metadata ) ;
}
var wasLocked = content . saveLock ;
var wasMigrating = content . migration ;
var myLocks = getUserLock ( getId ( ) , true ) ;
content = json . content ;
if ( content . saveLock && wasLocked !== content . saveLock ) {
// Someone new is creating a checkpoint: fix the sheets ids
fixSheets ( ) ;
// If the checkpoint is not saved in 20s to 40s, do it ourselves
checkCheckpoint ( ) ;
}
var editor = getEditor ( ) ;
if ( content . hashes ) {
var latest = getLastCp ( true ) ;
var newLatest = getLastCp ( ) ;
if ( newLatest . index > latest . index || ( newLatest . index && ! latest . index ) ) {
ooChannel . queue = [ ] ;
ooChannel . ready = false ;
var reload = function ( ) {
// New checkpoint
sframeChan . query ( 'Q_OO_SAVE' , {
hash : newLatest . hash ,
url : newLatest . file
} , function ( ) {
checkNewCheckpoint ( ) ;
} ) ;
} ;
var editing = editor . asc _isDocumentModified ? editor . asc _isDocumentModified ( ) : editor . isDocumentModify ;
if ( editing ) {
setEditable ( false ) ;
APP . unsavedLocks = myLocks ;
APP . onStrictSaveChanges = function ( ) {
reload ( ) ;
delete APP . onStrictSaveChanges ;
} ;
editor . asc _Save ( ) ;
} else {
reload ( ) ;
}
}
oldHashes = JSON . parse ( JSON . stringify ( content . hashes ) ) ;
}
if ( content . ids ) {
handleNewIds ( oldIds , content . ids ) ;
oldIds = JSON . parse ( JSON . stringify ( content . ids ) ) ;
}
if ( content . locks ) {
handleNewLocks ( oldLocks , content . locks ) ;
oldLocks = JSON . parse ( JSON . stringify ( content . locks ) ) ;
}
if ( content . migration ) {
setEditable ( false ) ;
}
if ( wasMigrating && ! content . migration && ! reloadPopup ) {
reloadPopup = true ;
UI . alert ( Messages . oo _sheetMigration _complete , function ( ) {
common . gotoURL ( ) ;
} ) ;
}
pinImages ( ) ;
} ;
config . onConnectionChange = function ( info ) {
if ( info . state ) {
// If we tried to send changes while we were offline, force a page reload
UIElements . reconnectAlert ( ) ;
if ( Object . keys ( pendingChanges ) . length ) {
return void UI . confirm ( Messages . oo _reconnect , function ( yes ) {
if ( ! yes ) { return ; }
common . gotoURL ( ) ;
} ) ;
}
//setEditable(true);
try { getEditor ( ) . asc _setViewMode ( false ) ; } catch ( e ) { }
offline = false ;
} else {
try { getEditor ( ) . asc _setViewMode ( true ) ; } catch ( e ) { }
//setEditable(false);
offline = true ;
UI . findOKButton ( ) . click ( ) ;
UIElements . disconnectAlert ( ) ;
}
} ;
cpNfInner = common . startRealtime ( config ) ;
cpNfInner . onInfiniteSpinner ( function ( ) {
if ( offline ) { return ; }
setEditable ( false ) ;
UI . confirm ( Messages . realtime _unrecoverableError , function ( yes ) {
if ( ! yes ) { return ; }
common . gotoURL ( ) ;
} ) ;
} ) ;
common . onLogout ( function ( ) { setEditable ( false ) ; } ) ;
} ;
var main = function ( ) {
var common ;
nThen ( function ( waitFor ) {
$ ( waitFor ( function ( ) {
UI . addLoadingScreen ( ) ;
} ) ) ;
SFCommon . create ( waitFor ( function ( c ) { APP . common = common = c ; } ) ) ;
} ) . nThen ( function ( waitFor ) {
common . getSframeChannel ( ) . on ( 'EV_OO_TEMPLATE' , function ( data ) {
APP . startWithTemplate = data ;
} ) ;
common . handleNewFile ( waitFor , {
//noTemplates: true
} ) ;
} ) . nThen ( function ( /*waitFor*/ ) {
andThen ( common ) ;
} ) ;
} ;
main ( ) ;
} ) ;