You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
richdocuments/js/editor/boot_editor.js

463 lines
16 KiB
JavaScript

/**
* Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
*
* @licstart
* The JavaScript code in this page is free software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* (GNU AGPL) as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. The code is distributed
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
*
* As additional permission under GNU AGPL version 3 section 7, you
* may distribute non-source (e.g., minimized or compacted) forms of
* that code without the copy of the GNU GPL normally required by
* section 4, provided you include this license notice and a URL
* through which recipients can access the Corresponding Source.
*
* As a special exception to the AGPL, any HTML file which merely makes function
* calls to this code, and for that purpose includes it by reference shall be
* deemed a separate work for copyright law purposes. In addition, the copyright
* holders of this code give you permission to combine this code with free
* software libraries that are released under the GNU LGPL. You may copy and
* distribute such a system following the terms of the GNU AGPL for this code
* and the LGPL for the libraries. If you modify this code, you may extend this
* exception to your version of the code, but you are not obligated to do so.
* If you do not wish to do so, delete this exception statement from your
* version.
*
* This license applies to this entire compilation.
* @licend
* @source: http://www.webodf.org/
* @source: http://gitorious.org/webodf/webodf/
*/
/*
* bootstrap the editor in different ways.
* this file is meant to be included from HTML and used
* by users who do not want to know much about the inner
* complexity.
* so we need to make it really easy.
*
* including this file will result in the namespace/object
* "webodfEditor" to be available from the HTML side.
* calling webodfEditor.boot() will start the editor.
* the method can also take some parameters to specify
* behaviour. see documentation of that method.
*
*/
/*global runtime, require, document, alert, net, window, SessionListView, ops */
// define the namespace/object we want to provide
// this is the first line of API, the user gets.
var webodfEditor = (function () {
"use strict";
runtime.currentDirectory = function () {
return "../../webodf/lib";
};
runtime.libraryPaths = function () {
return [ runtime.currentDirectory() ];
};
var editorInstance = null,
serverFactory = null,
server = null,
booting = false,
loadedFilename;
/**
* wait for a network connection through nowjs to establish.
* call the callback when done, when finally failed, or
* when a timeout reached.
* the parameter to the callback is a string with the possible
* values:
* "unavailable", "timeout", "ready"
*
* @param {!function(!string)} callback
* @return {undefined}
*/
function connectNetwork(backend, callback) {
function createServer(ServerFactory) {
serverFactory = new ServerFactory();
server = serverFactory.createServer();
server.connect(8000, callback);
}
switch (backend) {
case "pullbox":
require({ }, ["webodf/editor/server/pullbox/serverFactory"], createServer);
break;
case "nowjs":
require({ }, ["webodf/editor/server/nowjs/serverFactory"], createServer);
break;
case "owncloud":
require({ }, ["webodf/editor/server/pullbox/serverFactory"], function (ServerFactory) {
serverFactory = new ServerFactory();
server = serverFactory.createServer({url: "./documents/ajax/otpoll.php"});
server.getGenesisUrl = function(sid) {
// what a dirty hack :)
return OC.Router.generate('documents_genesis')+'/' +sid;
};
server.connect(8000, callback);
});
default:
callback("unavailable");
}
}
/**
* try to auto-sense the server-backend.
*
* NOT IMPLEMENTED / MIGHT BE NICE TO HAVE...
* for now: try to connect to pullbox backend
*
* @param {!function(!string)} callback
* @return {undefined}
*/
function detectNetwork(callback) {
connectNetwork("pullbox", callback);
}
/**
* extract document url from the url-fragment
*
* @return {?string}
*/
function guessDocUrl() {
var pos, docUrl = String(document.location);
// If the URL has a fragment (#...), try to load the file it represents
pos = docUrl.indexOf('#');
if (pos !== -1) {
docUrl = docUrl.substr(pos + 1);
} else {
docUrl = "welcome.odt";
}
return docUrl || null;
}
function fileSelectHandler(evt) {
var file, files, reader;
files = (evt.target && evt.target.files) ||
(evt.dataTransfer && evt.dataTransfer.files);
function onloadend() {
if (reader.readyState === 2) {
runtime.registerFile(file.name, reader.result);
loadedFilename = file.name;
editorInstance.loadDocument(file.name);
}
}
if (files && files.length === 1) {
file = files[0];
reader = new FileReader();
reader.onloadend = onloadend;
reader.readAsArrayBuffer(file);
} else {
alert("File could not be opened in this browser.");
}
}
function enhanceRuntime() {
var openedFiles = {},
read = runtime.read,
getFileSize = runtime.getFileSize;
runtime.read = function (path, offset, length, callback) {
var array;
if (openedFiles.hasOwnProperty(path)) {
array = new Uint8Array(openedFiles[path], offset, length);
callback(undefined, array);
} else {
return read(path, offset, length, callback);
}
};
runtime.getFileSize = function (path, callback) {
if (openedFiles.hasOwnProperty(path)) {
return callback(openedFiles[path].byteLength);
} else {
return getFileSize(path, callback);
}
};
runtime.registerFile = function (path, data) {
openedFiles[path] = data;
};
}
function createFileLoadForm() {
var form = document.createElement("form"),
input = document.createElement("input");
form.appendChild(input);
form.style.display = "none";
input.id = "fileloader";
input.setAttribute("type", "file");
input.addEventListener("change", fileSelectHandler, false);
document.body.appendChild(form);
}
function load() {
var form = document.getElementById("fileloader");
if (!form) {
enhanceRuntime();
createFileLoadForm();
form = document.getElementById("fileloader");
}
form.click();
}
function save() {
editorInstance.saveDocument(loadedFilename);
}
/**
* create a new editor instance, and start the editor with
* the given document.
*
* @param {!string} docUrl
* @param {?Object} editorOptions
* @param {?function(!Object)} editorReadyCallback
*/
function createLocalEditor(docUrl, editorOptions, editorReadyCallback) {
var pos;
booting = true;
editorOptions = editorOptions || {};
editorOptions.memberid = "localuser";
editorOptions.loadCallback = load;
editorOptions.saveCallback = save;
if (docUrl === undefined) {
docUrl = guessDocUrl();
}
runtime.assert(docUrl, "docUrl needs to be specified");
runtime.assert(editorInstance === null, "cannot boot with instanciated editor");
document.getElementById("mainContainer").style.display = "";
require({ }, ["webodf/editor/Editor"],
function (Editor) {
editorInstance = new Editor(editorOptions);
editorInstance.initAndLoadDocument(docUrl, function (editorSession) {
editorSession.startEditing();
editorReadyCallback(editorInstance);
});
}
);
}
/**
* assume the network connection is established, create a new editor instance,
* and start the editor on the network.
*
* @param {!string} sessionId
* @param {!string} memberId
* @param {?string} token
* @param {?Object} editorOptions
* @param {?function(!Object)} editorReadyCallback
*/
function createNetworkedEditor(sessionId, memberId, token, editorOptions, editorReadyCallback) {
runtime.assert(sessionId, "sessionId needs to be specified");
runtime.assert(memberId, "memberId needs to be specified");
runtime.assert(editorInstance === null, "cannot boot with instanciated editor");
editorOptions = editorOptions || {};
editorOptions.memberid = memberId;
editorOptions.networked = true;
editorOptions.networkSecurityToken = token;
require({ }, ["webodf/editor/Editor"],
function (Editor) {
// TODO: the networkSecurityToken needs to be retrieved via now.login
// (but this is to be implemented later)
editorInstance = new Editor(editorOptions, server, serverFactory);
// load the document and get called back when it's live
editorInstance.loadSession(sessionId, function (editorSession) {
editorSession.startEditing();
editorReadyCallback(editorInstance);
});
}
);
}
/**
* start the login process by offering a login/password prompt.
* the login is validated via nowjs namespace.
* on success a list of sessions is offered.
* when the user selects a session the callback is called
* with the sessionId as parameter
*
* @param {!function(!string, !string, ?string)} callback
* @returns {undefined}
*/
function startLoginProcess(callback) {
var userid, token;
booting = true;
runtime.assert(editorInstance === null, "cannot boot with instanciated editor");
function enterSession(selectedSessionId) {
document.getElementById("sessionListContainer").style.display = "none";
document.getElementById("mainContainer").style.display = "";
callback(selectedSessionId, userid, token);
}
function showSessions() {
require({ }, ["webodf/editor/SessionListView"],
function (SessionListView) {
var sessionListDiv = document.getElementById("sessionList"),
sessionList = new serverFactory.createSessionList(server),
sessionListView = new SessionListView(sessionList, sessionListDiv, enterSession);
// hide login view
document.getElementById("loginContainer").style.display = "none";
// show session list
document.getElementById("sessionListContainer").style.display = "";
}
);
}
function loginSuccess(userData) {
runtime.log("connected:" + userData.full_name);
userid = userData.uid;
token = userData.securityToken || null;
showSessions();
}
function loginFail(result) {
alert("Login failed: " + result);
}
function onLoginSubmit() {
server.login(document.loginForm.login.value, document.loginForm.password.value, loginSuccess, loginFail);
// block the submit button, we already dealt with the input
return false;
}
// bring up the login form
document.loginForm.Submit.onclick = onLoginSubmit;
document.getElementById("loginContainer").style.display = "";
}
/**
* make a guess about the document (# in URL)
* also guess about local/collaborative (depending on nowjs)
*
* @param {?Object} args
*
* args:
*
* collaborative: if set to true: connect to the network and start a
* collaborative editor. in that case the document url
* is ignored. and user needs to select a session.
*
* if set to the string "auto": it will try the above
* but fall back to non-collaborative mode [default]
*
* docUrl: if given it is used as the url to the document to load
*
* callback: callback to be called as soon as the document is loaded
*
*/
function boot(args) {
var editorOptions = {},
loginProcedure = startLoginProcess;
runtime.assert(!booting, "editor creation already in progress");
args = args || {};
if (args.collaborative === undefined) {
args.collaborative = "standalone";
} else {
args.collaborative = String(args.collaborative).toLowerCase();
}
if (args.saveCallback) {
editorOptions.saveCallback = args.saveCallback;
}
if (args.cursorAddedCallback) {
editorOptions.cursorAddedCallback = args.cursorAddedCallback;
}
if (args.cursorRemovedCallback) {
editorOptions.cursorRemovedCallback = args.cursorRemovedCallback;
}
if (args.registerCallbackForShutdown) {
editorOptions.registerCallbackForShutdown = args.registerCallbackForShutdown;
} else {
editorOptions.registerCallbackForShutdown = function (callback) {
window.onunload = callback;
};
}
if (args.loginProcedure) {
loginProcedure = args.loginProcedure;
}
// start the editor with network
function handleNetworkedSituation() {
var joinSession = server.joinSession;
if (args.joinSession) {
joinSession = args.joinSession;
}
loginProcedure(function (sessionId, userId, token) {
// if pre-authentication has happened:
if (token) {
server.setToken(token);
}
joinSession(userId, sessionId, function(memberId) {
createNetworkedEditor(sessionId, memberId, token, editorOptions, function (ed) {
if (args.callback) {
args.callback(ed);
}
});
});
});
}
// start the editor without network
function handleNonNetworkedSituation() {
createLocalEditor(args.docUrl, editorOptions, function (editor) {
if (args.callback) {
args.callback(editor);
}
});
}
if (args.collaborative === "auto") {
runtime.log("detecting network...");
detectNetwork(function (state) {
if (state === "ready") {
runtime.log("... network available.");
handleNetworkedSituation();
} else {
runtime.log("... no network available (" + state + ").");
handleNonNetworkedSituation();
}
});
} else if ((args.collaborative === "pullbox") ||
(args.collaborative === "owncloud")) {
runtime.log("starting collaborative editor for ["+args.collaborative+"].");
connectNetwork(args.collaborative, function (state) {
if (state === "ready") {
handleNetworkedSituation();
}
});
} else {
runtime.log("starting local editor.");
handleNonNetworkedSituation();
}
}
// exposed API
return { boot: boot };
}());