1 /** 2 * Copyright (C) 2014 KO GmbH <copyright@kogmbh.com> 3 * 4 * @licstart 5 * This file is part of WebODF. 6 * 7 * WebODF is free software: you can redistribute it and/or modify it 8 * under the terms of the GNU Affero General Public License (GNU AGPL) 9 * as published by the Free Software Foundation, either version 3 of 10 * the License, or (at your option) any later version. 11 * 12 * WebODF is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License 18 * along with WebODF. If not, see <http://www.gnu.org/licenses/>. 19 * @licend 20 * 21 * @source: http://www.webodf.org/ 22 * @source: https://github.com/kogmbh/WebODF/ 23 */ 24 25 /** 26 * Namespace of the Wodo.TextEditor 27 * @namespace 28 */ 29 var Wodo = Wodo || (function () { 30 "use strict"; 31 32 var installationPath = (function() { 33 "use strict"; 34 35 /** 36 * Sees to get the url of this script on top of the stack trace. 37 * @param {!string|undefined} stack 38 * @return {!string|undefined} 39 */ 40 function getScriptUrlFromStack(stack) { 41 var url, matches; 42 43 if (typeof stack === "string" && stack) { 44 matches = stack.match(/((?:http[s]?|file):\/\/[\/]?.+?\/[^:\)]*?)(?::\d+)(?::\d+)?/); 45 url = matches && matches[1]; 46 } 47 if (typeof url === "string" && url) { 48 return url; 49 } 50 return undefined; 51 } 52 53 /** 54 * Tries by various tricks to get the url of this script. 55 * To be called if document.currentScript is not supported 56 * @return {!string|undefined} 57 */ 58 function getCurrentScriptElementSrcByTricks() { 59 var i, 60 pageUrl = window.location.href, 61 scriptElements = document.getElementsByTagName("script"); 62 63 // if there is only one script, it must be this 64 if (scriptElements.length === 1) { 65 return scriptElements[0].src; 66 } 67 68 // otherwise get it from the stacktrace 69 try { 70 throw new Error(); 71 } 72 catch (err) { 73 return getScriptUrlFromStack(err.stack); 74 } 75 } 76 77 var path = ".", scriptElementSrc, 78 a, pathname, pos; 79 80 if (document.currentScript && document.currentScript.src) { 81 scriptElementSrc = document.currentScript.src; 82 } else { 83 scriptElementSrc = getCurrentScriptElementSrcByTricks(); 84 } 85 86 if (scriptElementSrc) { 87 a = document.createElement('a'); 88 a.href = scriptElementSrc; 89 pathname = a.pathname; 90 if (pathname.charAt(0) !== "/") { 91 // Various versions of Internet Explorer seems to neglect the leading slash under some conditions 92 // (not when watching it with the dev tools of course!). This was confirmed in IE10 + IE11 93 pathname = "/" + pathname; 94 } 95 96 pos = pathname.lastIndexOf("/"); 97 if (pos !== -1) { 98 path = pathname.substr(0, pos); 99 } 100 } else { 101 alert("Could not estimate installation path of the Wodo.TextEditor."); 102 } 103 return path; 104 }()); 105 106 window.dojoConfig = (function() { 107 var WebODFEditorDojoLocale = "C"; 108 109 if (navigator && navigator.language && navigator.language.match(/^(de)/)) { 110 WebODFEditorDojoLocale = navigator.language.substr(0, 2); 111 } 112 113 return { 114 locale: WebODFEditorDojoLocale, 115 paths: { 116 "webodf/editor": installationPath, 117 "dijit": installationPath + "/dijit", 118 "dojox": installationPath + "/dojox", 119 "dojo": installationPath + "/dojo", 120 "resources": installationPath + "/resources" 121 } 122 }; 123 }()); 124 125 var /** @inner @type{!boolean} */ 126 isInitalized = false, 127 /** @inner @type{!Array.<!function():undefined>} */ 128 pendingInstanceCreationCalls = [], 129 /** @inner @type{!number} */ 130 instanceCounter = 0, 131 // TODO: avatar image url needs base-url setting. 132 // so far Wodo itself does not have a setup call, 133 // but then the avatar is also not used yet here 134 defaultUserData = { 135 fullName: "", 136 color: "black", 137 imageUrl: "avatar-joe.png" 138 }, 139 /** @inner @const 140 @type{!Array.<!string>} */ 141 userDataFieldNames = ["fullName", "color", "imageUrl"], 142 /** @inner @const 143 @type{!string} */ 144 memberId = "localuser", 145 // constructors 146 BorderContainer, ContentPane, FullWindowZoomHelper, EditorSession, Tools, 147 /** @inner @const 148 @type{!string} */ 149 EVENT_UNKNOWNERROR = "unknownError", 150 /** @inner @const 151 @type {!string} */ 152 EVENT_METADATACHANGED = "metadataChanged"; 153 154 155 /** 156 * @return {undefined} 157 */ 158 function initTextEditor() { 159 require([ 160 "dijit/layout/BorderContainer", 161 "dijit/layout/ContentPane", 162 "webodf/editor/FullWindowZoomHelper", 163 "webodf/editor/EditorSession", 164 "webodf/editor/Tools", 165 "webodf/editor/Translator"], 166 function (BC, CP, FWZH, ES, T, Translator) { 167 var locale = navigator.language || "en-US", 168 editorBase = dojo.config && dojo.config.paths && dojo.config.paths["webodf/editor"], 169 translationsDir = editorBase + '/translations', 170 t; 171 172 BorderContainer = BC; 173 ContentPane = CP; 174 FullWindowZoomHelper = FWZH, 175 EditorSession = ES; 176 Tools = T; 177 178 // TODO: locale cannot be set by the user, also different for different editors 179 t = new Translator(translationsDir, locale, function (editorTranslator) { 180 runtime.setTranslator(editorTranslator.translate); 181 // Extend runtime with a convenient translation function 182 runtime.translateContent = function (node) { 183 var i, 184 element, 185 tag, 186 placeholder, 187 translatable = node.querySelectorAll("*[text-i18n]"); 188 189 for (i = 0; i < translatable.length; i += 1) { 190 element = translatable[i]; 191 tag = element.localName; 192 placeholder = element.getAttribute('text-i18n'); 193 if (tag === "label" 194 || tag === "span" 195 || /h\d/i.test(tag)) { 196 element.textContent = runtime.tr(placeholder); 197 } 198 } 199 }; 200 201 defaultUserData.fullName = runtime.tr("Unknown Author"); 202 203 isInitalized = true; 204 pendingInstanceCreationCalls.forEach(function (create) { create(); }); 205 }); 206 } 207 ); 208 } 209 210 /** 211 * Creates a new record with userdata, and for all official fields 212 * copies over the value from the original or, if not present there, 213 * sets it to the default value. 214 * @param {?Object.<!string,!string>|undefined} original, defaults to {} 215 * @return {!Object.<!string,!string>} 216 */ 217 function cloneUserData(original) { 218 var result = {}; 219 220 if (!original) { 221 original = {}; 222 } 223 224 userDataFieldNames.forEach(function (fieldName) { 225 result[fieldName] = original[fieldName] || defaultUserData[fieldName]; 226 }); 227 228 return result; 229 } 230 231 /** 232 * @name TextEditor 233 * @constructor 234 * @param {!string} mainContainerElementId 235 * @param {!Object.<!string,!*>} editorOptions 236 */ 237 function TextEditor(mainContainerElementId, editorOptions) { 238 instanceCounter = instanceCounter + 1; 239 240 /** 241 * Returns true if either all features are wanted and this one is not explicitely disabled 242 * or if not all features are wanted by default and it is explicitely enabled 243 * @param {?boolean|undefined} isFeatureEnabled explicit flag which enables a feature 244 * @return {!boolean} 245 */ 246 function isEnabled(isFeatureEnabled) { 247 return editorOptions.allFeaturesEnabled ? (isFeatureEnabled !== false) : isFeatureEnabled; 248 } 249 250 var self = this, 251 userData, 252 // 253 mainContainerElement = document.getElementById(mainContainerElementId), 254 canvasElement, 255 canvasContainerElement, 256 toolbarElement, 257 toolbarContainerElement, // needed because dijit toolbar overwrites direct classList 258 editorElement, 259 /** @inner @const 260 @type{!string} */ 261 canvasElementId = "webodfeditor-canvas" + instanceCounter, 262 /** @inner @const 263 @type{!string} */ 264 canvasContainerElementId = "webodfeditor-canvascontainer" + instanceCounter, 265 /** @inner @const 266 @type{!string} */ 267 toolbarElementId = "webodfeditor-toolbar" + instanceCounter, 268 /** @inner @const 269 @type{!string} */ 270 editorElementId = "webodfeditor-editor" + instanceCounter, 271 // 272 fullWindowZoomHelper, 273 // 274 mainContainer, 275 tools, 276 odfCanvas, 277 // 278 editorSession, 279 session, 280 // 281 loadOdtFile = editorOptions.loadCallback, 282 saveOdtFile = editorOptions.saveCallback, 283 close = editorOptions.closeCallback, 284 // 285 directTextStylingEnabled = isEnabled(editorOptions.directTextStylingEnabled), 286 directParagraphStylingEnabled = isEnabled(editorOptions.directParagraphStylingEnabled), 287 paragraphStyleSelectingEnabled = isEnabled(editorOptions.paragraphStyleSelectingEnabled), 288 paragraphStyleEditingEnabled = isEnabled(editorOptions.paragraphStyleEditingEnabled), 289 imageEditingEnabled = isEnabled(editorOptions.imageEditingEnabled), 290 hyperlinkEditingEnabled = isEnabled(editorOptions.hyperlinkEditingEnabled), 291 reviewModeEnabled = Boolean(editorOptions.reviewModeEnabled), // needs to be explicitly enabled 292 annotationsEnabled = reviewModeEnabled || isEnabled(editorOptions.annotationsEnabled), 293 undoRedoEnabled = isEnabled(editorOptions.undoRedoEnabled), 294 zoomingEnabled = isEnabled(editorOptions.zoomingEnabled), 295 // 296 pendingMemberId, 297 pendingEditorReadyCallback, 298 // 299 eventNotifier = new core.EventNotifier([ 300 EVENT_UNKNOWNERROR, 301 EVENT_METADATACHANGED 302 ]); 303 304 runtime.assert(Boolean(mainContainerElement), "No id of an existing element passed to Wodo.createTextEditor(): "+mainContainerElementId); 305 306 /** 307 * @param {!Object} changes 308 * @return {undefined} 309 */ 310 function relayMetadataSignal(changes) { 311 eventNotifier.emit(EVENT_METADATACHANGED, changes); 312 } 313 314 /** 315 * @return {undefined} 316 */ 317 function createSession() { 318 var viewOptions = { 319 editInfoMarkersInitiallyVisible: false, 320 caretAvatarsInitiallyVisible: false, 321 caretBlinksOnRangeSelect: true 322 }; 323 324 // create session around loaded document 325 session = new ops.Session(odfCanvas); 326 editorSession = new EditorSession(session, pendingMemberId, { 327 viewOptions: viewOptions, 328 directTextStylingEnabled: directTextStylingEnabled, 329 directParagraphStylingEnabled: directParagraphStylingEnabled, 330 paragraphStyleSelectingEnabled: paragraphStyleSelectingEnabled, 331 paragraphStyleEditingEnabled: paragraphStyleEditingEnabled, 332 imageEditingEnabled: imageEditingEnabled, 333 hyperlinkEditingEnabled: hyperlinkEditingEnabled, 334 annotationsEnabled: annotationsEnabled, 335 zoomingEnabled: zoomingEnabled, 336 reviewModeEnabled: reviewModeEnabled 337 }); 338 if (undoRedoEnabled) { 339 editorSession.sessionController.setUndoManager(new gui.TrivialUndoManager()); 340 } 341 342 // Relay any metadata changes to the Editor's consumer as an event 343 editorSession.sessionController.getMetadataController().subscribe(gui.MetadataController.signalMetadataChanged, relayMetadataSignal); 344 345 // and report back to caller 346 pendingEditorReadyCallback(); 347 // reset 348 pendingEditorReadyCallback = null; 349 pendingMemberId = null; 350 } 351 352 /** 353 * @return {undefined} 354 */ 355 function startEditing() { 356 runtime.assert(editorSession, "editorSession should exist here."); 357 358 tools.setEditorSession(editorSession); 359 editorSession.sessionController.insertLocalCursor(); 360 editorSession.sessionController.startEditing(); 361 } 362 363 /** 364 * @return {undefined} 365 */ 366 function endEditing() { 367 runtime.assert(editorSession, "editorSession should exist here."); 368 369 tools.setEditorSession(undefined); 370 editorSession.sessionController.endEditing(); 371 editorSession.sessionController.removeLocalCursor(); 372 } 373 374 /** 375 * Loads an ODT document into the editor. 376 * @name TextEditor#openDocumentFromUrl 377 * @function 378 * @param {!string} docUrl url from which the ODT document can be loaded 379 * @param {!function(!Error=):undefined} callback Called once the document has been opened, passes an error object in case of error 380 * @return {undefined} 381 */ 382 this.openDocumentFromUrl = function(docUrl, editorReadyCallback) { 383 runtime.assert(docUrl, "document should be defined here."); 384 runtime.assert(!pendingEditorReadyCallback, "pendingEditorReadyCallback should not exist here."); 385 runtime.assert(!editorSession, "editorSession should not exist here."); 386 runtime.assert(!session, "session should not exist here."); 387 388 pendingMemberId = memberId; 389 pendingEditorReadyCallback = function () { 390 var op = new ops.OpAddMember(); 391 op.init({ 392 memberid: memberId, 393 setProperties: userData 394 }); 395 session.enqueue([op]); 396 startEditing(); 397 if (editorReadyCallback) { 398 editorReadyCallback(); 399 } 400 }; 401 402 odfCanvas.load(docUrl); 403 } 404 405 /** 406 * Closes the document, and does cleanup. 407 * @name TextEditor#closeDocument 408 * @function 409 * @param {!function(!Error=):undefined} callback Called once the document has been closed, passes an error object in case of error 410 * @return {undefined} 411 */ 412 this.closeDocument = function(callback) { 413 runtime.assert(session, "session should exist here."); 414 415 endEditing(); 416 417 var op = new ops.OpRemoveMember(); 418 op.init({ 419 memberid: memberId 420 }); 421 session.enqueue([op]); 422 423 session.close(function (err) { 424 if (err) { 425 callback(err); 426 } else { 427 editorSession.sessionController.getMetadataController().unsubscribe(gui.MetadataController.signalMetadataChanged, relayMetadataSignal); 428 editorSession.destroy(function (err) { 429 if (err) { 430 callback(err); 431 } else { 432 editorSession = undefined; 433 session.destroy(function (err) { 434 if (err) { 435 callback(err); 436 } else { 437 session = undefined; 438 callback(); 439 } 440 }); 441 } 442 }); 443 } 444 }); 445 } 446 447 /** 448 * @name TextEditor#getDocumentAsByteArray 449 * @function 450 * @param {!function(err:?Error, file:!Uint8Array=):undefined} callback Called with the current document as ODT file as bytearray, passes an error object in case of error 451 * @return {undefined} 452 */ 453 this.getDocumentAsByteArray = function(callback) { 454 var odfContainer = odfCanvas.odfContainer(); 455 456 if (odfContainer) { 457 odfContainer.createByteArray(function(ba) { 458 callback(null, ba); 459 }, function(errorString) { 460 callback(new Error(errorString ? errorString : "Could not create bytearray from OdfContainer.")); 461 }); 462 } else { 463 callback(new Error("No odfContainer set!")); 464 } 465 } 466 467 /** 468 * Sets the metadata fields from the given properties map. 469 * Avoid setting certain fields since they are automatically set: 470 * dc:creator 471 * dc:date 472 * meta:editing-cycles 473 * 474 * The following properties are never used and will be removed for semantic 475 * consistency from the document: 476 * meta:editing-duration 477 * meta:document-statistic 478 * 479 * Setting any of the above mentioned fields using this method will have no effect. 480 * 481 * @name TextEditor#setMetadata 482 * @function 483 * @param {?Object.<!string, !string>} setProperties A flat object that is a string->string map of field name -> value. 484 * @param {?Array.<!string>} removedProperties An array of metadata field names (prefixed). 485 * @return {undefined} 486 */ 487 this.setMetadata = function(setProperties, removedProperties) { 488 runtime.assert(editorSession, "editorSession should exist here."); 489 490 editorSession.sessionController.getMetadataController().setMetadata(setProperties, removedProperties); 491 }; 492 493 /** 494 * Returns the value of the requested document metadata field. 495 * @name TextEditor#getMetadata 496 * @function 497 * @param {!string} property A namespace-prefixed field name, for example 498 * dc:creator 499 * @return {?string} 500 */ 501 this.getMetadata = function(property) { 502 runtime.assert(editorSession, "editorSession should exist here."); 503 504 return editorSession.sessionController.getMetadataController().getMetadata(property); 505 }; 506 507 /** 508 * Sets the data for the person that is editing the document. 509 * The supported fields are: 510 * "fullName": the full name of the editing person 511 * "color": color to use for the user specific UI elements 512 * @name TextEditor#setUserData 513 * @function 514 * @param {?Object.<!string,!string>|undefined} data 515 * @return {undefined} 516 */ 517 function setUserData(data) { 518 userData = cloneUserData(data); 519 } 520 this.setUserData = setUserData; 521 522 /** 523 * Returns the data set for the person that is editing the document. 524 * @name TextEditor#getUserData 525 * @function 526 * @return {!Object.<!string,!string>} 527 */ 528 this.getUserData = function() { 529 return cloneUserData(userData); 530 } 531 532 /** 533 * @return {undefined} 534 */ 535 function setFocusToOdfCanvas() { 536 editorSession.sessionController.getEventManager().focus(); 537 } 538 539 /** 540 * @param {!function(!Error=):undefined} callback passes an error object in case of error 541 * @return {undefined} 542 */ 543 function destroyInternal(callback) { 544 mainContainerElement.removeChild(editorElement); 545 546 callback(); 547 } 548 549 /** 550 * Destructs the editor object completely. 551 * @name TextEditor#destroy 552 * @function 553 * @param {!function(!Error=):undefined} callback Called once the destruction has been completed, passes an error object in case of error 554 * @return {undefined} 555 */ 556 this.destroy = function(callback) { 557 var destroyCallbacks = []; 558 559 // TODO: decide if some forced close should be done here instead of enforcing proper API usage 560 runtime.assert(!session, "session should not exist here."); 561 562 // TODO: investigate what else needs to be done 563 mainContainer.destroyRecursive(true); 564 565 destroyCallbacks = destroyCallbacks.concat([ 566 fullWindowZoomHelper.destroy, 567 tools.destroy, 568 odfCanvas.destroy, 569 destroyInternal 570 ]); 571 572 core.Async.destroyAll(destroyCallbacks, callback); 573 } 574 575 // TODO: 576 // this.openDocumentFromByteArray = openDocumentFromByteArray; see also https://github.com/kogmbh/WebODF/issues/375 577 // setReadOnly: setReadOnly, 578 579 /** 580 * Registers a callback which should be called if the given event happens. 581 * @name TextEditor#addEventListener 582 * @function 583 * @param {!string} eventId 584 * @param {!Function} callback 585 * @return {undefined} 586 */ 587 this.addEventListener = eventNotifier.subscribe; 588 /** 589 * Unregisters a callback for the given event. 590 * @name TextEditor#removeEventListener 591 * @function 592 * @param {!string} eventId 593 * @param {!Function} callback 594 * @return {undefined} 595 */ 596 this.removeEventListener = eventNotifier.unsubscribe; 597 598 599 /** 600 * @return {undefined} 601 */ 602 function init() { 603 var editorPane, 604 /** @inner @const 605 @type{!string} */ 606 documentns = document.documentElement.namespaceURI; 607 608 /** 609 * @param {!string} tagLocalName 610 * @param {!string|undefined} id 611 * @param {!string} className 612 * @return {!Element} 613 */ 614 function createElement(tagLocalName, id, className) { 615 var element; 616 element = document.createElementNS(documentns, tagLocalName); 617 if (id) { 618 element.id = id; 619 } 620 element.classList.add(className); 621 return element; 622 } 623 624 // create needed tree structure 625 canvasElement = createElement('div', canvasElementId, "webodfeditor-canvas"); 626 canvasContainerElement = createElement('div', canvasContainerElementId, "webodfeditor-canvascontainer"); 627 toolbarElement = createElement('span', toolbarElementId, "webodfeditor-toolbar"); 628 toolbarContainerElement = createElement('span', undefined, "webodfeditor-toolbarcontainer"); 629 editorElement = createElement('div', editorElementId, "webodfeditor-editor"); 630 631 // put into tree 632 canvasContainerElement.appendChild(canvasElement); 633 toolbarContainerElement.appendChild(toolbarElement); 634 editorElement.appendChild(toolbarContainerElement); 635 editorElement.appendChild(canvasContainerElement); 636 mainContainerElement.appendChild(editorElement); 637 638 // style all elements with Dojo's claro. 639 // Not nice to do this on body, but then there is no other way known 640 // to style also all dialogs, which are attached directly to body 641 document.body.classList.add("claro"); 642 643 // create widgets 644 mainContainer = new BorderContainer({}, mainContainerElementId); 645 646 editorPane = new ContentPane({ 647 region: 'center' 648 }, editorElementId); 649 mainContainer.addChild(editorPane); 650 651 mainContainer.startup(); 652 653 tools = new Tools(toolbarElementId, { 654 onToolDone: setFocusToOdfCanvas, 655 loadOdtFile: loadOdtFile, 656 saveOdtFile: saveOdtFile, 657 close: close, 658 directTextStylingEnabled: directTextStylingEnabled, 659 directParagraphStylingEnabled: directParagraphStylingEnabled, 660 paragraphStyleSelectingEnabled: paragraphStyleSelectingEnabled, 661 paragraphStyleEditingEnabled: paragraphStyleEditingEnabled, 662 imageInsertingEnabled: imageEditingEnabled, 663 hyperlinkEditingEnabled: hyperlinkEditingEnabled, 664 annotationsEnabled: annotationsEnabled, 665 undoRedoEnabled: undoRedoEnabled, 666 zoomingEnabled: zoomingEnabled, 667 aboutEnabled: true 668 }); 669 670 odfCanvas = new odf.OdfCanvas(canvasElement); 671 odfCanvas.enableAnnotations(annotationsEnabled, true); 672 673 odfCanvas.addListener("statereadychange", createSession); 674 675 fullWindowZoomHelper = new FullWindowZoomHelper(toolbarContainerElement, canvasContainerElement); 676 677 setUserData(editorOptions.userData); 678 } 679 680 init(); 681 } 682 683 function loadDojoAndStuff(callback) { 684 var head = document.getElementsByTagName("head")[0], 685 frag = document.createDocumentFragment(), 686 link, 687 script; 688 689 // append two link and two script elements to the header 690 link = document.createElement("link"); 691 link.rel = "stylesheet"; 692 link.href = installationPath + "/app/resources/app.css"; 693 link.type = "text/css"; 694 link.async = false; 695 frag.appendChild(link); 696 link = document.createElement("link"); 697 link.rel = "stylesheet"; 698 link.href = installationPath + "/wodotexteditor.css"; 699 link.type = "text/css"; 700 link.async = false; 701 frag.appendChild(link); 702 script = document.createElement("script"); 703 script.src = installationPath + "/dojo-amalgamation.js"; 704 script["data-dojo-config"] = "async: true"; 705 script.charset = "utf-8"; 706 script.type = "text/javascript"; 707 script.async = false; 708 frag.appendChild(script); 709 script = document.createElement("script"); 710 script.src = installationPath + "/webodf.js"; 711 script.charset = "utf-8"; 712 script.type = "text/javascript"; 713 script.async = false; 714 script.onload = callback; 715 frag.appendChild(script); 716 head.appendChild(frag); 717 } 718 719 /** 720 * Creates a text editor object and returns it on success in the passed callback. 721 * @name Wodo#createTextEditor 722 * @function 723 * @param {!string} editorContainerElementId id of the existing div element which will contain the editor (should be empty before) 724 * @param editorOptions options to configure the features of the editor. All entries are optional 725 * @param [editorOptions.loadCallback] parameter-less callback method, adds a "Load" button to the toolbar which triggers this method 726 * @param [editorOptions.saveCallback] parameter-less callback method, adds a "Save" button to the toolbar which triggers this method 727 * @param [editorOptions.closeCallback] parameter-less callback method, adds a "Save" button to the toolbar which triggers this method 728 * @param [editorOptions.allFeaturesEnabled=false] if set to 'true', switches the default for all features from 'false' to 'true' 729 * @param [editorOptions.directTextStylingEnabled=false] if set to 'true', enables the direct styling of text (e.g. bold/italic or font) 730 * @param [editorOptions.directParagraphStylingEnabled=false] if set to 'true', enables the direct styling of paragraphs (e.g. indentation or alignement) 731 * @param [editorOptions.paragraphStyleSelectingEnabled=false] if set to 'true', enables setting of defined paragraph styles to paragraphs 732 * @param [editorOptions.paragraphStyleEditingEnabled=false] if set to 'true', enables the editing of defined paragraph styles 733 * @param [editorOptions.imageEditingEnabled=false] if set to 'true', enables the insertion of images 734 * @param [editorOptions.hyperlinkEditingEnabled=false] if set to 'true', enables the editing of hyperlinks 735 * @param [editorOptions.annotationsEnabled=false] if set to 'true', enables the display and the editing of annotations 736 * @param [editorOptions.undoRedoEnabled=false] if set to 'true', enables the Undo and Redo of editing actions 737 * @param [editorOptions.zoomingEnabled=false] if set to 'true', enables the zooming tool 738 * @param [editorOptions.userData] data about the user editing the document 739 * @param [editorOptions.userData.fullName] full name of the user, used for annotations and in the metadata of the document 740 * @param [editorOptions.userData.color="black"] color to use for any user related indicators like cursor or annotations 741 * @param {!function(err:?Error, editor:!TextEditor=):undefined} onEditorCreated 742 * @return {undefined} 743 */ 744 function createTextEditor(editorContainerElementId, editorOptions, onEditorCreated) { 745 /** 746 * @return {undefined} 747 */ 748 function create() { 749 var editor = new TextEditor(editorContainerElementId, editorOptions); 750 onEditorCreated(null, editor); 751 } 752 753 if (!isInitalized) { 754 pendingInstanceCreationCalls.push(create); 755 // first request? 756 if (pendingInstanceCreationCalls.length === 1) { 757 if (String(typeof WodoFromSource) === "undefined") { 758 loadDojoAndStuff(initTextEditor); 759 } else { 760 initTextEditor(); 761 } 762 } 763 } else { 764 create(); 765 } 766 } 767 768 769 /** 770 * @lends Wodo# 771 */ 772 return { 773 createTextEditor: createTextEditor, 774 // flags 775 /** Id of event for an unkown error */ 776 EVENT_UNKNOWNERROR: EVENT_UNKNOWNERROR, 777 /** Id of event if metadata changes */ 778 EVENT_METADATACHANGED: EVENT_METADATACHANGED 779 }; 780 }()); 781