2016-08-03 18:16:20 +02:00
|
|
|
/**
|
|
|
|
* Copyright (C) 2014 KO GmbH <copyright@kogmbh.com>
|
|
|
|
*
|
|
|
|
* @licstart
|
|
|
|
* This file is part of WebODF.
|
|
|
|
*
|
|
|
|
* WebODF 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.
|
|
|
|
*
|
|
|
|
* WebODF is distributed in the hope that it will be useful, but
|
|
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
* @licend
|
|
|
|
*
|
|
|
|
* @source: http://www.webodf.org/
|
|
|
|
* @source: https://github.com/kogmbh/WebODF/
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*global Wodo, require, navigator, dojo, runtime, document, window, core, ops, gui, odf*/
|
|
|
|
|
|
|
|
window.Wodo = window.Wodo || (function () {
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
var /** @type{!boolean} */
|
|
|
|
isInitalized = false,
|
|
|
|
/** @type{!Array.<!function():undefined>} */
|
|
|
|
pendingInstanceCreationCalls = [],
|
|
|
|
/** @type{!number} */
|
|
|
|
instanceCounter = 0,
|
|
|
|
// constructors
|
|
|
|
BorderContainer, ContentPane, FullWindowZoomHelper, EditorSession, Tools, MemberListView,
|
|
|
|
// const strings
|
|
|
|
/** @const
|
|
|
|
@type {!string} */
|
|
|
|
EVENT_UNKNOWNERROR = "unkownError",
|
|
|
|
/** @const
|
|
|
|
@type {!string} */
|
|
|
|
EVENT_METADATACHANGED = "metadataChanged",
|
|
|
|
/** @const
|
|
|
|
@type {!string} */
|
|
|
|
EVENT_BEFORESAVETOFILE = "beforeSaveToFile",
|
|
|
|
/** @const
|
|
|
|
@type {!string} */
|
|
|
|
EVENT_SAVEDTOFILE = "savedToFile",
|
|
|
|
/** @const
|
|
|
|
@type {!string} */
|
|
|
|
EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED = "hasLocalUnsyncedOperationsChanged",
|
|
|
|
/** @const
|
|
|
|
@type {!string} */
|
|
|
|
EVENT_HASSESSIONHOSTCONNECTIONCHANGED = "hasSessionHostConnectionChanged";
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function initCollabTextEditor() {
|
|
|
|
require([
|
|
|
|
"dijit/layout/BorderContainer",
|
|
|
|
"dijit/layout/ContentPane",
|
|
|
|
"webodf/editor/FullWindowZoomHelper",
|
|
|
|
"webodf/editor/EditorSession",
|
|
|
|
"webodf/editor/Tools",
|
|
|
|
"webodf/editor/MemberListView",
|
|
|
|
"webodf/editor/Translator"],
|
|
|
|
function (BC, CP, FWZH, ES, T, MLV, Translator) {
|
|
|
|
var locale = navigator.language || "en-US",
|
|
|
|
editorBase = dojo.config && dojo.config.paths && dojo.config.paths["webodf/editor"],
|
|
|
|
translationsDir = editorBase + '/translations',
|
|
|
|
t;
|
|
|
|
|
|
|
|
BorderContainer = BC;
|
|
|
|
ContentPane = CP;
|
|
|
|
FullWindowZoomHelper = FWZH;
|
|
|
|
EditorSession = ES;
|
|
|
|
Tools = T;
|
|
|
|
MemberListView = MLV;
|
|
|
|
|
|
|
|
// TODO: locale cannot be set by the user, also different for different editors
|
|
|
|
t = new Translator(translationsDir, locale, function (editorTranslator) {
|
|
|
|
runtime.setTranslator(editorTranslator.translate);
|
|
|
|
// Extend runtime with a convenient translation function
|
|
|
|
runtime.translateContent = function (node) {
|
|
|
|
var i,
|
|
|
|
element,
|
|
|
|
tag,
|
|
|
|
placeholder,
|
|
|
|
translatable = node.querySelectorAll("*[text-i18n]");
|
|
|
|
|
|
|
|
for (i = 0; i < translatable.length; i += 1) {
|
|
|
|
element = translatable[i];
|
|
|
|
tag = element.localName;
|
|
|
|
placeholder = element.getAttribute('text-i18n');
|
|
|
|
if (tag === "label"
|
|
|
|
|| tag === "span"
|
|
|
|
|| /h\d/i.test(tag)) {
|
|
|
|
element.textContent = runtime.tr(placeholder);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
isInitalized = true;
|
|
|
|
pendingInstanceCreationCalls.forEach(function (create) { create(); });
|
|
|
|
return t; // return it so 't' is not unused
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @constructor
|
|
|
|
* @param {!string} mainContainerElementId
|
|
|
|
* @param {!Object.<!string,!*>} editorOptions
|
|
|
|
*/
|
|
|
|
function CollabTextEditor(mainContainerElementId, editorOptions) {
|
|
|
|
instanceCounter = instanceCounter + 1;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if either all features are wanted and this one is not explicitely disabled
|
|
|
|
* or if not all features are wanted by default and it is explicitely enabled
|
|
|
|
* @param {?boolean|undefined} isFeatureEnabled explicit flag which enables a feature
|
|
|
|
* @param {!boolean=} isUnstable set to true if the feature is not stable (in collab mode)
|
|
|
|
* @return {!boolean}
|
|
|
|
*/
|
|
|
|
function isEnabled(isFeatureEnabled, isUnstable) {
|
|
|
|
if (isUnstable && !editorOptions.unstableFeaturesEnabled) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return editorOptions.allFeaturesEnabled ? (isFeatureEnabled !== false) : isFeatureEnabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
var //
|
|
|
|
mainContainerElement = document.getElementById(mainContainerElementId),
|
|
|
|
canvasElement,
|
|
|
|
canvasContainerElement,
|
|
|
|
toolbarElement,
|
|
|
|
toolbarContainerElement, // needed because dijit toolbar overwrites direct classList
|
|
|
|
editorElement,
|
|
|
|
/** @const
|
|
|
|
@type{!string} */
|
|
|
|
canvasElementId = "webodfeditor-canvas" + instanceCounter,
|
|
|
|
/** @const
|
|
|
|
@type{!string} */
|
|
|
|
canvasContainerElementId = "webodfeditor-canvascontainer" + instanceCounter,
|
|
|
|
/** @const
|
|
|
|
@type{!string} */
|
|
|
|
toolbarElementId = "webodfeditor-toolbar" + instanceCounter,
|
|
|
|
/** @const
|
|
|
|
@type{!string} */
|
|
|
|
editorElementId = "webodfeditor-editor" + instanceCounter,
|
|
|
|
//
|
|
|
|
fullWindowZoomHelper,
|
|
|
|
//
|
|
|
|
memberListElement,
|
|
|
|
membersElement,
|
|
|
|
/** @const
|
|
|
|
@type{!string} */
|
|
|
|
membersElementId = "webodfeditor-members" + instanceCounter,
|
|
|
|
//
|
|
|
|
mainContainer,
|
|
|
|
memberListView,
|
|
|
|
tools,
|
|
|
|
odfCanvas,
|
|
|
|
//
|
|
|
|
editorSession,
|
|
|
|
session,
|
|
|
|
//
|
|
|
|
saveOdtFile = editorOptions.saveCallback,
|
|
|
|
close = editorOptions.closeCallback,
|
|
|
|
//
|
|
|
|
directTextStylingEnabled = isEnabled(editorOptions.directTextStylingEnabled),
|
|
|
|
directParagraphStylingEnabled = isEnabled(editorOptions.directParagraphStylingEnabled),
|
|
|
|
paragraphStyleSelectingEnabled = isEnabled(editorOptions.paragraphStyleSelectingEnabled),
|
|
|
|
paragraphStyleEditingEnabled = isEnabled(editorOptions.paragraphStyleEditingEnabled),
|
|
|
|
imageEditingEnabled = isEnabled(editorOptions.imageEditingEnabled, true),
|
|
|
|
hyperlinkEditingEnabled = isEnabled(editorOptions.hyperlinkEditingEnabled, true),
|
|
|
|
reviewModeEnabled = isEnabled(editorOptions.reviewModeEnabled, true),
|
|
|
|
annotationsEnabled = reviewModeEnabled || isEnabled(editorOptions.annotationsEnabled),
|
|
|
|
undoRedoEnabled = false, // no proper mechanism yet for collab
|
|
|
|
zoomingEnabled = isEnabled(editorOptions.zoomingEnabled),
|
|
|
|
//
|
|
|
|
pendingMemberId,
|
|
|
|
pendingEditorReadyCallback,
|
|
|
|
//
|
|
|
|
eventNotifier = new core.EventNotifier([
|
|
|
|
EVENT_UNKNOWNERROR,
|
|
|
|
EVENT_METADATACHANGED,
|
|
|
|
EVENT_BEFORESAVETOFILE,
|
|
|
|
EVENT_SAVEDTOFILE,
|
|
|
|
EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED,
|
|
|
|
EVENT_HASSESSIONHOSTCONNECTIONCHANGED
|
|
|
|
]);
|
|
|
|
|
|
|
|
runtime.assert(Boolean(mainContainerElement), "No id of an existing element passed to Wodo.createCollabTextEditor(): "+mainContainerElementId);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!Object} changes
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function relayMetadataSignal(changes) {
|
|
|
|
eventNotifier.emit(EVENT_METADATACHANGED, changes);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function createSession() {
|
|
|
|
var viewOptions = {
|
|
|
|
editInfoMarkersInitiallyVisible: true,
|
|
|
|
caretAvatarsInitiallyVisible: true,
|
|
|
|
caretBlinksOnRangeSelect: true
|
|
|
|
};
|
|
|
|
|
|
|
|
// create session around loaded document
|
|
|
|
session = new ops.Session(odfCanvas);
|
|
|
|
editorSession = new EditorSession(session, pendingMemberId, {
|
2016-08-23 12:29:49 +02:00
|
|
|
viewOptions: jQuery.extend(viewOptions,editorOptions.viewOptions),
|
2016-08-03 18:16:20 +02:00
|
|
|
directTextStylingEnabled: directTextStylingEnabled,
|
|
|
|
directParagraphStylingEnabled: directParagraphStylingEnabled,
|
|
|
|
paragraphStyleSelectingEnabled: paragraphStyleSelectingEnabled,
|
|
|
|
paragraphStyleEditingEnabled: paragraphStyleEditingEnabled,
|
|
|
|
imageEditingEnabled: imageEditingEnabled,
|
|
|
|
hyperlinkEditingEnabled: hyperlinkEditingEnabled,
|
|
|
|
annotationsEnabled: annotationsEnabled,
|
|
|
|
zoomingEnabled: zoomingEnabled,
|
|
|
|
reviewModeEnabled: reviewModeEnabled
|
|
|
|
});
|
|
|
|
if (undoRedoEnabled) {
|
|
|
|
editorSession.sessionController.setUndoManager(new gui.TrivialUndoManager());
|
|
|
|
}
|
|
|
|
|
|
|
|
memberListView.setEditorSession(editorSession);
|
|
|
|
|
|
|
|
// Relay any metadata changes to the Editor's consumer as an event
|
|
|
|
editorSession.sessionController.getMetadataController().subscribe(gui.MetadataController.signalMetadataChanged, relayMetadataSignal);
|
|
|
|
|
|
|
|
// and report back to caller
|
|
|
|
pendingEditorReadyCallback();
|
|
|
|
// reset
|
|
|
|
pendingEditorReadyCallback = null;
|
|
|
|
pendingMemberId = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function startEditing() {
|
|
|
|
runtime.assert(editorSession, "editorSession should exist here.");
|
|
|
|
|
|
|
|
tools.setEditorSession(editorSession);
|
|
|
|
editorSession.sessionController.insertLocalCursor();
|
|
|
|
editorSession.sessionController.startEditing();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function endEditing() {
|
|
|
|
runtime.assert(editorSession, "editorSession should exist here.");
|
|
|
|
|
|
|
|
tools.setEditorSession(undefined);
|
|
|
|
editorSession.sessionController.endEditing();
|
|
|
|
editorSession.sessionController.removeLocalCursor();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!string} eventid
|
|
|
|
* @param {*} args
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function fireEvent(eventid, args) {
|
|
|
|
eventNotifier.emit(eventid, args);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!Object} error
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function handleOperationRouterErrors(error) {
|
|
|
|
// TODO: translate error into Editor ids or at least document the possible values
|
|
|
|
fireEvent(EVENT_UNKNOWNERROR, error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!SessionBackend} sessionBackend
|
|
|
|
* @param {!function(!Error=):undefined} callback, passing an error object in case of error
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function joinSession(sessionBackend, editorReadyCallback) {
|
|
|
|
runtime.assert(sessionBackend, "No sessionBackend passed.");
|
|
|
|
runtime.assert(sessionBackend.getMemberId(), "sessionBackend should deliver a memberId here.");
|
|
|
|
|
|
|
|
runtime.assert(!pendingEditorReadyCallback, "pendingEditorReadyCallback should not exist here.");
|
|
|
|
runtime.assert(!editorSession, "editorSession should not exist here.");
|
|
|
|
runtime.assert(!session, "session should not exist here.");
|
|
|
|
|
|
|
|
pendingMemberId = sessionBackend.getMemberId();
|
|
|
|
pendingEditorReadyCallback = function () {
|
|
|
|
// overwrite router
|
|
|
|
var opRouter = sessionBackend.createOperationRouter(odfCanvas.odfContainer(), handleOperationRouterErrors);
|
|
|
|
session.setOperationRouter(opRouter);
|
|
|
|
// forward events
|
|
|
|
// TODO: relying here on that opRouter uses the same id strings ATM, those should be defined at OperationRouter interface
|
|
|
|
opRouter.subscribe(EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED, function (hasUnsyncedOps) {
|
|
|
|
fireEvent(EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED, hasUnsyncedOps);
|
|
|
|
});
|
|
|
|
opRouter.subscribe(EVENT_HASSESSIONHOSTCONNECTIONCHANGED, function (hasSessionHostConnection) {
|
|
|
|
fireEvent(EVENT_HASSESSIONHOSTCONNECTIONCHANGED, hasSessionHostConnection);
|
|
|
|
});
|
|
|
|
opRouter.subscribe(EVENT_BEFORESAVETOFILE, function () {
|
|
|
|
fireEvent(EVENT_BEFORESAVETOFILE, null);
|
|
|
|
});
|
|
|
|
opRouter.subscribe(EVENT_SAVEDTOFILE, function () {
|
|
|
|
fireEvent(EVENT_SAVEDTOFILE, null);
|
|
|
|
});
|
|
|
|
|
|
|
|
// now get existing ops and after that let the user edit
|
|
|
|
opRouter.requestReplay(function done() {
|
|
|
|
startEditing();
|
|
|
|
if (editorReadyCallback) {
|
|
|
|
editorReadyCallback();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
odfCanvas.load(sessionBackend.getGenesisUrl());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Leave the currently joined edit session, and do cleanup.
|
|
|
|
* @param {!function(!Error=)} callback, passing an error object in case of error
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function leaveSession(callback) {
|
|
|
|
runtime.assert(session, "session should exist here.");
|
|
|
|
|
|
|
|
endEditing();
|
|
|
|
|
|
|
|
session.close(function(err) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
// now also destroy session, will not be reused for new document
|
|
|
|
memberListView.setEditorSession(undefined);
|
|
|
|
editorSession.sessionController.getMetadataController().unsubscribe(gui.MetadataController.signalMetadataChanged, relayMetadataSignal);
|
|
|
|
editorSession.destroy(function(err) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
editorSession = undefined;
|
|
|
|
session.destroy(function(err) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
session = undefined;
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return {!Element}
|
|
|
|
*/
|
|
|
|
function getCanvasContainerElement() {
|
|
|
|
return canvasContainerElement;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!function(err:?Error, !Uint8Array=):undefined} cb receiving zip as bytearray
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function getDocumentAsByteArray(cb) {
|
|
|
|
var odfContainer = odfCanvas.odfContainer();
|
|
|
|
|
|
|
|
if (odfContainer) {
|
|
|
|
odfContainer.createByteArray(function (ba) {
|
|
|
|
cb(null, ba);
|
|
|
|
}, function (err) {
|
|
|
|
cb(err || "Could not create bytearray.");
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
cb("No odfContainer!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the metadata fields from the given properties map.
|
|
|
|
* Avoid setting certain fields since they are automatically set:
|
|
|
|
* dc:creator
|
|
|
|
* dc:date
|
|
|
|
* meta:editing-cycles
|
|
|
|
* If you do wish to externally set these fields, try getting
|
|
|
|
* the server backend (if any) to inject operations into the timeline
|
|
|
|
* with the relevant properties.
|
|
|
|
*
|
|
|
|
* The following properties are never used and will be removed for semantic
|
|
|
|
* consistency from the document:
|
|
|
|
* meta:editing-duration
|
|
|
|
* meta:document-statistic
|
|
|
|
*
|
|
|
|
* Setting any of the above mentioned fields using this method will have no effect.
|
|
|
|
*
|
|
|
|
* @param {?Object.<!string, !string>} setProperties A flat object that is a string->string map of field name -> value.
|
|
|
|
* @param {?Array.<!string>} removedProperties An array of metadata field names (prefixed).
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function setMetadata(setProperties, removedProperties) {
|
|
|
|
runtime.assert(editorSession, "editorSession should exist here.");
|
|
|
|
|
|
|
|
editorSession.sessionController.getMetadataController().setMetadata(setProperties, removedProperties);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the value of the requested document metadata field
|
|
|
|
* @param {!string} property A namespace-prefixed field name, for example
|
|
|
|
* dc:creator
|
|
|
|
* @return {?string}
|
|
|
|
*/
|
|
|
|
function getMetadata(property) {
|
|
|
|
runtime.assert(editorSession, "editorSession should exist here.");
|
|
|
|
|
|
|
|
return editorSession.sessionController.getMetadataController().getMetadata(property);
|
|
|
|
}
|
|
|
|
|
2016-08-15 17:18:35 +02:00
|
|
|
/**
|
|
|
|
* Sets the current state of the document to be either the unmodified state
|
|
|
|
* or a modified state.
|
|
|
|
* If @p modified is @true and the current state was already a modified state,
|
|
|
|
* this call has no effect and also does not remove the unmodified flag
|
|
|
|
* from the state which has it set.
|
|
|
|
*
|
|
|
|
* @name TextEditor#setDocumentModified
|
|
|
|
* @function
|
|
|
|
* @param {!boolean} modified
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
this.setDocumentModified = function (modified) {
|
|
|
|
runtime.assert(editorSession, "editorSession should exist here.");
|
|
|
|
|
|
|
|
if (undoRedoEnabled) {
|
|
|
|
editorSession.sessionController.getUndoManager().setDocumentModified(modified);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns if the current state of the document matches the unmodified state.
|
|
|
|
* @name TextEditor#isDocumentModified
|
|
|
|
* @function
|
|
|
|
* @return {!boolean}
|
|
|
|
*/
|
|
|
|
this.isDocumentModified = function () {
|
|
|
|
runtime.assert(editorSession, "editorSession should exist here.");
|
|
|
|
|
|
|
|
if (undoRedoEnabled) {
|
|
|
|
return editorSession.sessionController.getUndoManager().isDocumentModified();
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2016-08-03 18:16:20 +02:00
|
|
|
/**
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function setFocusToOdfCanvas() {
|
|
|
|
editorSession.sessionController.getEventManager().focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!function(!Error=):undefined} callback, passing an error object in case of error
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function destroyInternal(callback) {
|
|
|
|
mainContainerElement.removeChild(editorElement);
|
|
|
|
mainContainerElement.removeChild(membersElement);
|
|
|
|
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!function(!Error=):undefined} callback, passing an error object in case of error
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function destroy(callback) {
|
|
|
|
var destroyCallbacks = [];
|
|
|
|
|
|
|
|
// TODO: decide if some forced close should be done here instead of enforcing proper API usage
|
|
|
|
runtime.assert(!session, "session should not exist here.");
|
|
|
|
|
|
|
|
// TODO: investigate what else needs to be done
|
|
|
|
mainContainer.destroyRecursive(true);
|
|
|
|
|
|
|
|
destroyCallbacks = destroyCallbacks.concat([
|
|
|
|
fullWindowZoomHelper.destroy,
|
|
|
|
memberListView.destroy,
|
|
|
|
tools.destroy,
|
|
|
|
odfCanvas.destroy,
|
|
|
|
destroyInternal
|
|
|
|
]);
|
|
|
|
|
|
|
|
core.Async.destroyAll(destroyCallbacks, callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
// Exposed API
|
|
|
|
// make sure no data structures are shared, copy as needed
|
|
|
|
//
|
|
|
|
|
|
|
|
this.joinSession = joinSession;
|
|
|
|
this.leaveSession = leaveSession;
|
|
|
|
|
|
|
|
this.getDocumentAsByteArray = getDocumentAsByteArray;
|
|
|
|
|
|
|
|
// setReadOnly: setReadOnly,
|
|
|
|
|
|
|
|
this.setMetadata = setMetadata;
|
|
|
|
this.getMetadata = getMetadata;
|
|
|
|
|
|
|
|
// temporary hack:
|
|
|
|
this.getCanvasContainerElement = getCanvasContainerElement;
|
|
|
|
|
|
|
|
this.addEventListener = eventNotifier.subscribe;
|
|
|
|
this.removeEventListener = eventNotifier.unsubscribe;
|
|
|
|
|
|
|
|
this.destroy = destroy;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function init() {
|
|
|
|
var editorPane, memberListPane,
|
|
|
|
documentns = document.documentElement.namespaceURI;
|
|
|
|
|
|
|
|
function createElement(tagLocalName, id, className) {
|
|
|
|
var element;
|
|
|
|
element = document.createElementNS(documentns, tagLocalName);
|
|
|
|
if (id) {
|
|
|
|
element.id = id;
|
|
|
|
}
|
|
|
|
element.classList.add(className);
|
|
|
|
return element;
|
|
|
|
}
|
|
|
|
|
|
|
|
// create needed tree structure
|
|
|
|
canvasElement = createElement('div', canvasElementId, "webodfeditor-canvas");
|
|
|
|
canvasContainerElement = createElement('div', canvasContainerElementId, "webodfeditor-canvascontainer");
|
|
|
|
toolbarElement = createElement('span', toolbarElementId, "webodfeditor-toolbar");
|
|
|
|
toolbarContainerElement = createElement('span', undefined, "webodfeditor-toolbarcontainer");
|
|
|
|
editorElement = createElement('div', editorElementId, "webodfeditor-editor");
|
|
|
|
|
|
|
|
// put into tree
|
|
|
|
canvasContainerElement.appendChild(canvasElement);
|
|
|
|
toolbarContainerElement.appendChild(toolbarElement);
|
|
|
|
editorElement.appendChild(toolbarContainerElement);
|
|
|
|
editorElement.appendChild(canvasContainerElement);
|
|
|
|
mainContainerElement.appendChild(editorElement);
|
|
|
|
|
|
|
|
// memberlist plugin
|
|
|
|
memberListElement = createElement('div', undefined, "webodfeditor-memberList");
|
|
|
|
membersElement = createElement('div', membersElementId, "webodfeditor-members");
|
|
|
|
|
|
|
|
// put into tree
|
|
|
|
membersElement.appendChild(memberListElement);
|
|
|
|
mainContainerElement.appendChild(membersElement);
|
|
|
|
|
|
|
|
// style all elements with Dojo's claro.
|
|
|
|
// Not nice to do this on body, but then there is no other way known
|
|
|
|
// to style also all dialogs, which are attached directly to body
|
|
|
|
document.body.classList.add("claro");
|
|
|
|
|
|
|
|
// prevent browser translation service messing up internal address system
|
|
|
|
canvasElement.setAttribute("translate", "no");
|
|
|
|
canvasElement.classList.add("notranslate");
|
|
|
|
|
|
|
|
// create widgets
|
|
|
|
mainContainer = new BorderContainer({}, mainContainerElementId);
|
|
|
|
|
|
|
|
editorPane = new ContentPane({
|
|
|
|
region: 'center'
|
|
|
|
}, editorElementId);
|
|
|
|
mainContainer.addChild(editorPane);
|
|
|
|
|
|
|
|
memberListPane = new ContentPane({
|
|
|
|
region: 'right',
|
|
|
|
title: runtime.tr("Members")
|
|
|
|
}, membersElementId);
|
|
|
|
mainContainer.addChild(memberListPane);
|
|
|
|
memberListView = new MemberListView(memberListElement);
|
|
|
|
|
|
|
|
mainContainer.startup();
|
|
|
|
|
|
|
|
tools = new Tools(toolbarElementId, {
|
|
|
|
onToolDone: setFocusToOdfCanvas,
|
|
|
|
saveOdtFile: saveOdtFile,
|
|
|
|
close: close,
|
|
|
|
directTextStylingEnabled: directTextStylingEnabled,
|
|
|
|
directParagraphStylingEnabled: directParagraphStylingEnabled,
|
|
|
|
paragraphStyleSelectingEnabled: paragraphStyleSelectingEnabled,
|
|
|
|
paragraphStyleEditingEnabled: paragraphStyleEditingEnabled,
|
|
|
|
imageInsertingEnabled: imageEditingEnabled,
|
|
|
|
hyperlinkEditingEnabled: hyperlinkEditingEnabled,
|
|
|
|
annotationsEnabled: annotationsEnabled,
|
|
|
|
undoRedoEnabled: undoRedoEnabled,
|
|
|
|
zoomingEnabled: zoomingEnabled
|
|
|
|
});
|
|
|
|
|
|
|
|
odfCanvas = new odf.OdfCanvas(canvasElement);
|
|
|
|
odfCanvas.enableAnnotations(annotationsEnabled, true);
|
|
|
|
|
|
|
|
odfCanvas.addListener("statereadychange", createSession);
|
|
|
|
|
|
|
|
fullWindowZoomHelper = new FullWindowZoomHelper(toolbarContainerElement, canvasContainerElement);
|
|
|
|
}
|
|
|
|
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!string} mainContainerElementId
|
|
|
|
* @param {!Object.<!string,!*>} editorOptions
|
|
|
|
* @param {!function(err:?Error, editor:!CollabTextEditor=):undefined} onEditorCreated
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function createInstance(mainContainerElementId, editorOptions, onEditorCreated) {
|
|
|
|
/**
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function create() {
|
|
|
|
var editor = new CollabTextEditor(mainContainerElementId, editorOptions);
|
|
|
|
onEditorCreated(null, editor);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isInitalized) {
|
|
|
|
pendingInstanceCreationCalls.push(create);
|
|
|
|
// first request?
|
|
|
|
if (pendingInstanceCreationCalls.length === 1) {
|
|
|
|
initCollabTextEditor();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
create();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// exposed API
|
|
|
|
return {
|
|
|
|
createCollabTextEditor: createInstance,
|
|
|
|
// flags
|
|
|
|
EVENT_UNKNOWNERROR: EVENT_UNKNOWNERROR,
|
|
|
|
EVENT_METADATACHANGED: EVENT_METADATACHANGED,
|
|
|
|
EVENT_BEFORESAVETOFILE: EVENT_BEFORESAVETOFILE,
|
|
|
|
EVENT_SAVEDTOFILE: EVENT_SAVEDTOFILE,
|
|
|
|
EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED: EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED,
|
|
|
|
EVENT_HASSESSIONHOSTCONNECTIONCHANGED: EVENT_HASSESSIONHOSTCONNECTIONCHANGED
|
|
|
|
};
|
|
|
|
}());
|