wodotexteditor.js 30 KB

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