diff --git a/apps/api/documents/api.js b/apps/api/documents/api.js index cd018fa5b2..1fd9bc252c 100644 --- a/apps/api/documents/api.js +++ b/apps/api/documents/api.js @@ -106,6 +106,62 @@ }, chat: false, comments: false + }, + plugins: { + url: '../../../../sdkjs-plugins/', + pluginsData: [{ + name : "chess (fen)", + guid : "{FFE1F462-1EA2-4391-990D-4CC84940B754}", + + variations : [ + { + description : "chess", + url : "chess/index.html", + + icons : ["chess/icon.png", "chess/icon@2x.png"], + isViewer : true, + EditorsSupport : ["word", "cell", "slide"], + + isVisual : true, + isModal : true, + isInsideMode : false, + + initDataType : "ole", + initData : "", + + isUpdateOleOnResize : true, + + buttons : [ { text: "Ok", primary: true }, + { text: "Cancel", primary: false } ] + } + ] + }, + { + name : "glavred", + guid : "{B631E142-E40B-4B4C-90B9-2D00222A286E}", + + variations : [ + { + description : "glavred", + url : "glavred/index.html", + + icons : ["glavred/icon.png", "glavred/icon@2x.png"], + isViewer : true, + EditorsSupport : ["word", "cell", "slide"], + + isVisual : true, + isModal : true, + isInsideMode : false, + + initDataType : "text", + initData : "", + + isUpdateOleOnResize : false, + + buttons : [ { text: "Ok", primary: true } ] + } + ] + } } }, events: { diff --git a/apps/common/main/lib/collection/Plugins.js b/apps/common/main/lib/collection/Plugins.js new file mode 100644 index 0000000000..97deb6f42a --- /dev/null +++ b/apps/common/main/lib/collection/Plugins.js @@ -0,0 +1,54 @@ +/* + * + * (c) Copyright Ascensio System Limited 2010-2016 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, + * EU, LV-1021. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing content are licensed under the terms of the + * Creative Commons Attribution-ShareAlike 4.0 International. See the License + * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + * +*/ +/** + * User: Julia.Radzhabova + * Date: 17.05.16 + * Time: 15:54 + */ + +if (Common === undefined) + var Common = {}; + +Common.Collections = Common.Collections || {}; + +define([ + 'underscore', + 'backbone', + 'common/main/lib/model/Plugin' +], function(_, Backbone){ + 'use strict'; + + Common.Collections.Plugins = Backbone.Collection.extend({ + model: Common.Models.Plugin + }); +}); diff --git a/apps/common/main/lib/controller/Plugins.js b/apps/common/main/lib/controller/Plugins.js new file mode 100644 index 0000000000..3490f52738 --- /dev/null +++ b/apps/common/main/lib/controller/Plugins.js @@ -0,0 +1,168 @@ +/* + * + * (c) Copyright Ascensio System Limited 2010-2016 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, + * EU, LV-1021. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing content are licensed under the terms of the + * Creative Commons Attribution-ShareAlike 4.0 International. See the License + * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + * +*/ +/** + * User: Julia.Radzhabova + * Date: 17.05.16 + * Time: 15:38 + */ + +define([ + 'core', + 'common/main/lib/collection/Plugins', + 'common/main/lib/view/Plugins' +], function () { + 'use strict'; + + Common.Controllers.Plugins = Backbone.Controller.extend(_.extend({ + models: [], + collections: [ + 'Common.Collections.Plugins' + ], + views: [ + 'Common.Views.Plugins' + ], + + initialize: function() { + }, + + events: { + }, + + onLaunch: function() { + this.panelPlugins= this.createView('Common.Views.Plugins', { + storePlugins: this.getApplication().getCollection('Common.Collections.Plugins') + }); + this.panelPlugins.on('render:after', _.bind(this.onAfterRender, this)); + }, + + setApi: function(api) { + this.api = api; + + this.api.asc_registerCallback("asc_onPluginShow", _.bind(this.onPluginShow, this)); + this.api.asc_registerCallback("asc_onPluginClose", _.bind(this.onPluginClose, this)); + + return this; + }, + + setMode: function(mode) { + if (mode.canPlugins) { + this.panelPlugins.pluginsPath = mode.pluginsPath; + this.updatePluginsList(); + } + }, + + onAfterRender: function(historyView) { + historyView.viewPluginsList.on('item:click', _.bind(this.onSelectPlugin, this)); + }, + + updatePluginsList: function() { + var me = this; + var storePlugins = this.getApplication().getCollection('Common.Collections.Plugins'), + arr = []; + storePlugins.each(function(item){ + var plugin = new Asc.CPlugin(); + plugin.set_Name(item.get('name')); + plugin.set_Guid(item.get('guid')); + var variations = item.get('variations'), + variationsArr = []; + variations.forEach(function(itemVar){ + var variation = new Asc.CPluginVariation(); + variation.set_Description(itemVar.get('description')); + variation.set_Url(itemVar.get('url')); + variation.set_Icons(itemVar.get('icons')); + variation.set_Visual(itemVar.get('isVisual')); + variation.set_Viewer(itemVar.get('isViewer')); + variation.set_EditorsSupport(itemVar.get('EditorsSupport')); + variation.set_Modal(itemVar.get('isModal')); + variation.set_InsideMode(itemVar.get('isInsideMode')); + variation.set_InitDataType(itemVar.get('initDataType')); + variation.set_InitData(itemVar.get('initData')); + variation.set_UpdateOleOnResize(itemVar.get('isUpdateOleOnResize')); + variation.set_Buttons(itemVar.get('buttons')); + variationsArr.push(variation); + }); + plugin.set_Variations(variationsArr); + item.set('pluginObj', plugin); + arr.push(plugin); + }); + this.api.asc_pluginsRegister(this.panelPlugins.pluginsPath, arr); + }, + + onSelectPlugin: function(picker, item, record){ + this.api.asc_pluginRun(record.get('guid'), record.get('currentVariation'), ''); + }, + + onPluginShow: function(plugin) { + var variation = plugin.get_Variations()[0]; + if (!variation.get_Visual()) return; + + var me = this, + arrBtns = variation.get_Buttons(), + newBtns = {}; + + if (_.isArray(arrBtns)) { + _.each(arrBtns, function(b, index){ + newBtns[index] = {text: b.text, cls: 'custom' + ((b.primary) ? ' primary' : '')}; + }); + } + + me.pluginDlg = new Common.Views.PluginDlg({ + title: plugin.get_Name(), + url: me.panelPlugins.pluginsPath + variation.get_Url(), + buttons: newBtns, + toolcallback: _.bind(this.onToolClose, this) + }); + me.pluginDlg.on('render:after', function(obj){ + obj.getChild('.footer .dlg-btn').on('click', _.bind(me.onDlgBtnClick, me)); + }).on('close', function(obj){ + me.pluginDlg = undefined; + }); + me.pluginDlg.show(); + }, + + onPluginClose: function() { + if (this.pluginDlg) + this.pluginDlg.close(); + }, + + onDlgBtnClick: function(event) { + var state = event.currentTarget.attributes['result'].value; + window.g_asc_plugins.buttonClick(parseInt(state)); + }, + + onToolClose: function() { + window.g_asc_plugins.buttonClick(-1); + } + + }, Common.Controllers.Plugins || {})); +}); diff --git a/apps/common/main/lib/model/Plugin.js b/apps/common/main/lib/model/Plugin.js new file mode 100644 index 0000000000..2d90128bfe --- /dev/null +++ b/apps/common/main/lib/model/Plugin.js @@ -0,0 +1,85 @@ +/* + * + * (c) Copyright Ascensio System Limited 2010-2016 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, + * EU, LV-1021. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing content are licensed under the terms of the + * Creative Commons Attribution-ShareAlike 4.0 International. See the License + * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + * +*/ +/** + * User: Julia.Radzhabova + * Date: 17.05.16 + * Time: 15:56 + */ + +if (Common === undefined) + var Common = {}; + +Common.Models = Common.Models || {}; + +define([ + 'underscore', + 'backbone', + 'common/main/lib/component/BaseView' +], function(_, Backbone){ + 'use strict'; + + Common.Models.PluginVariation = Backbone.Model.extend({ + defaults: function() { + return { + description: "", + url: "", + index: 0, + icons: undefined, + isViewer: false, + EditorsSupport: ["word", "cell", "slide"], + isVisual: false, + isModal: false, + isInsideMode: false, + initDataType: 0, + initData: "", + isUpdateOleOnResize: false, + buttons: [] + } + } + }); + + Common.Models.Plugin = Backbone.Model.extend({ + defaults: function() { + return { + id: Common.UI.getId(), + name : '', + guid: Common.UI.getId(), + variations: [], + currentVariation: 0, + pluginObj: undefined, + allowSelected: false, + selected: false + } + } + }); +}); diff --git a/apps/common/main/lib/view/Plugins.js b/apps/common/main/lib/view/Plugins.js new file mode 100644 index 0000000000..b4817a90ca --- /dev/null +++ b/apps/common/main/lib/view/Plugins.js @@ -0,0 +1,181 @@ +/* + * + * (c) Copyright Ascensio System Limited 2010-2016 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, + * EU, LV-1021. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing content are licensed under the terms of the + * Creative Commons Attribution-ShareAlike 4.0 International. See the License + * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + * +*/ +/** + * User: Julia.Radzhabova + * Date: 17.05.16 + * Time: 15:38 + */ + +if (Common === undefined) + var Common = {}; + +Common.Views = Common.Views || {}; + +define([ + 'common/main/lib/util/utils', + 'common/main/lib/component/BaseView', + 'common/main/lib/component/Layout', + 'common/main/lib/component/Window' +], function (template) { + 'use strict'; + + Common.Views.Plugins = Common.UI.BaseView.extend(_.extend({ + el: '#left-panel-plugins', + + storePlugins: undefined, + template: _.template([ + '
', + '', + '
', + '
', + '
' + ].join('')), + + initialize: function(options) { + _.extend(this, options); + this.pluginsPath = '../../../../sdkjs-plugins/'; + Common.UI.BaseView.prototype.initialize.call(this, arguments); + }, + + render: function(el) { + el = el || this.el; + $(el).html(this.template({scope: this})).width( (parseInt(Common.localStorage.getItem('de-mainmenu-width')) || MENU_SCALE_PART) - SCALE_MIN); + + this.viewPluginsList = new Common.UI.DataView({ + el: $('#plugins-list'), + store: this.storePlugins, + enableKeyEvents: false, + itemTemplate: _.template('
1) ? 1 : 0] %>); background-position: 0 0;">
') + }); + + this.trigger('render:after', this); + return this; + }, + + strPlugins: 'Plugins' + + }, Common.Views.Plugins || {})); + + Common.Views.PluginDlg = Common.UI.Window.extend(_.extend({ + initialize : function(options) { + var _options = {}; + _.extend(_options, { + width: 800, + height: (window.innerHeight-600)<0 ? window.innerHeight: 600, + cls: 'advanced-settings-dlg', + header: true + }, options); + + var header_footer = (_options.buttons && _.size(_options.buttons)>0) ? 85 : 34; + this.template = [ + '
', + '
', + '
', + '<% if (_.size(buttons) > 0) { %>', + '
', + '', + '<% } %>' + ].join(''); + + _options.tpl = _.template(this.template, _options); + + this.url = options.url || ''; + Common.UI.Window.prototype.initialize.call(this, _options); + }, + + render: function() { + Common.UI.Window.prototype.render.call(this); + this.$window.find('> .body').css({height: 'auto', overflow: 'hidden'}); + + var iframe = document.createElement("iframe"); + iframe.id = 'plugin_iframe'; + iframe.name = 'frameEditor', + iframe.width = '100%'; + iframe.height = '100%'; + iframe.align = "top"; + iframe.frameBorder = 0; + iframe.scrolling = "no"; + iframe.onload = _.bind(this._onLoad,this); + $('#id-plugin-placeholder').append(iframe); + + this.loadMask = new Common.UI.LoadMask({owner: $('#id-plugin-placeholder')}); + this.loadMask.setTitle(this.textLoading); + this.loadMask.show(); + + iframe.src = this.url; + + var me = this; + + this.on('close', function(obj){ + }); + + this.on('show', function(obj){ + var h = me.getHeight(); + if (window.innerHeight>h && h<600 || window.innerHeight= 0) { + var min = parseInt(this.$window.css('min-height')); + height < min && (height = min); + this.$window.height(height); + + var header_height = (this.initConfig.header) ? parseInt(this.$window.find('> .header').css('height')) : 0; + + this.$window.find('> .body').css('height', height-header_height); + this.$window.find('> .body > .box').css('height', height-85); + + var top = ((parseInt(window.innerHeight) - parseInt(height)) / 2) * 0.9; + var left = (parseInt(window.innerWidth) - parseInt(this.initConfig.width)) / 2; + + this.$window.css('left',left); + this.$window.css('top',top); + } + }, + + textLoading : 'Loading' + }, Common.Views.PluginDlg || {})); +}); \ No newline at end of file diff --git a/apps/common/main/resources/less/plugins.less b/apps/common/main/resources/less/plugins.less new file mode 100644 index 0000000000..3577df2dd0 --- /dev/null +++ b/apps/common/main/resources/less/plugins.less @@ -0,0 +1,27 @@ +#plugins-box { + position: relative; + width: 100%; + height: 100%; + + #plugins-header { + position: absolute; + height: 20px; + left: 0; + top: 0; + font-weight: bold; + margin-top: 10px; + margin-left: 12px; + } + + #plugins-list { + height: 100%; + overflow: hidden; + padding: 30px 0 10px 10px; + + .item-plugins { + width: 40px; + height: 40px; + } + } +} + diff --git a/apps/documenteditor/main/app.js b/apps/documenteditor/main/app.js index 1dd8bdf6ab..40df85e8ab 100644 --- a/apps/documenteditor/main/app.js +++ b/apps/documenteditor/main/app.js @@ -161,6 +161,7 @@ require([ ,'Common.Controllers.Chat' ,'Common.Controllers.Comments' /** coauthoring end **/ + ,'Common.Controllers.Plugins' ,'Common.Controllers.ExternalDiagramEditor' ,'Common.Controllers.ExternalMergeEditor' ,'Common.Controllers.ReviewChanges' @@ -191,6 +192,7 @@ require([ ,'common/main/lib/controller/Comments' ,'common/main/lib/controller/Chat' /** coauthoring end **/ + ,'common/main/lib/controller/Plugins' ,'documenteditor/main/app/view/ChartSettings' ,'common/main/lib/controller/ExternalDiagramEditor' ,'common/main/lib/controller/ExternalMergeEditor' diff --git a/apps/documenteditor/main/app/controller/LeftMenu.js b/apps/documenteditor/main/app/controller/LeftMenu.js index 5d47539e64..0d65e83e39 100644 --- a/apps/documenteditor/main/app/controller/LeftMenu.js +++ b/apps/documenteditor/main/app/controller/LeftMenu.js @@ -174,10 +174,20 @@ define([ if (this.mode.canUseHistory) this.leftMenu.setOptionsPanel('history', this.getApplication().getController('Common.Controllers.History').getView('Common.Views.History')); + this.enablePlugins(); + Common.util.Shortcuts.resumeEvents(); return this; }, + enablePlugins: function() { + if (this.mode.canPlugins) { + this.leftMenu.btnPlugins.show(); + this.leftMenu.setOptionsPanel('plugins', this.getApplication().getController('Common.Controllers.Plugins').getView('Common.Views.Plugins')); + } else + this.leftMenu.btnPlugins.hide(); + }, + clickMenuFileItem: function(menu, action, isopts) { var close_menu = true; switch (action) { @@ -437,6 +447,7 @@ define([ this.leftMenu.btnComments.setDisabled(true); this.leftMenu.btnChat.setDisabled(true); /** coauthoring end **/ + this.leftMenu.btnPlugins.setDisabled(true); this.leftMenu.getMenu('file').setMode({isDisconnected: true}); if ( this.dlgSearch ) { @@ -453,6 +464,7 @@ define([ this.leftMenu.btnComments.setDisabled(disable); this.leftMenu.btnChat.setDisabled(disable); /** coauthoring end **/ + this.leftMenu.btnPlugins.setDisabled(disable); if (disableFileMenu) this.leftMenu.getMenu('file').SetDisabled(disable); }, diff --git a/apps/documenteditor/main/app/controller/Main.js b/apps/documenteditor/main/app/controller/Main.js index 796d60a2c3..cf31edd2d3 100644 --- a/apps/documenteditor/main/app/controller/Main.js +++ b/apps/documenteditor/main/app/controller/Main.js @@ -138,6 +138,7 @@ define([ // Initialize api gateway this.editorConfig = {}; this.appOptions = {}; + this.plugins = undefined; Common.Gateway.on('init', _.bind(this.loadConfig, this)); Common.Gateway.on('showmessage', _.bind(this.onExternalMessage, this)); Common.Gateway.on('opendocument', _.bind(this.loadDocument, this)); @@ -242,6 +243,8 @@ define([ this.appOptions.canBackToFolder = (this.editorConfig.canBackToFolder!==false) && (typeof (this.editorConfig.customization) == 'object') && (typeof (this.editorConfig.customization.goback) == 'object') && !_.isEmpty(this.editorConfig.customization.goback.url); this.appOptions.canBack = this.editorConfig.nativeApp !== true && this.appOptions.canBackToFolder === true; + this.appOptions.canPlugins = false; + this.plugins = this.editorConfig.plugins; this.getApplication() .getController('Viewport') @@ -816,7 +819,8 @@ define([ fontsController = application.getController('Common.Controllers.Fonts'), rightmenuController = application.getController('RightMenu'), leftmenuController = application.getController('LeftMenu'), - chatController = application.getController('Common.Controllers.Chat'); + chatController = application.getController('Common.Controllers.Chat'), + pluginsController = application.getController('Common.Controllers.Plugins'); leftmenuController.getView('LeftMenu').getMenu('file').loadDocument({doc:me.document}); leftmenuController.setMode(me.appOptions).createDelayedElements().setApi(me.api); @@ -825,6 +829,10 @@ define([ application.getController('Common.Controllers.ExternalDiagramEditor').setApi(this.api).loadConfig({config:this.editorConfig, customization: this.editorConfig.customization}); application.getController('Common.Controllers.ExternalMergeEditor').setApi(this.api).loadConfig({config:this.editorConfig, customization: this.editorConfig.customization}); + pluginsController.setApi(this.api); + this.updatePluginsList(this.plugins); + this.api.asc_registerCallback('asc_onPluginsInit', _.bind(this.updatePluginsList, this)); + documentHolderController.setApi(me.api); documentHolderController.createDelayedElements(); statusbarController.createDelayedElements(); @@ -1686,6 +1694,60 @@ define([ if (url) this.iframePrint.src = url; }, + updatePluginsList: function(plugins) { + var pluginStore = this.getApplication().getCollection('Common.Collections.Plugins'), + isEdit = this.appOptions.isEdit; + if (pluginStore && plugins) { + var arr = []; + plugins.pluginsData.forEach(function(item){ + var variations = item.variations, + variationsArr = []; + variations.forEach(function(itemVar){ + var isSupported = false; + for (var i=0; i0) + arr.push(new Common.Models.Plugin({ + name : item.name, + guid: item.guid, + variations: variationsArr, + currentVariation: 0 + })); + }); + + pluginStore.reset(arr); + + this.appOptions.pluginsPath = (plugins.url); + this.appOptions.canPlugins = (arr.length>0); + } else { + this.appOptions.pluginsPath = ''; + this.appOptions.canPlugins = false; + } + if (this.appOptions.canPlugins) + this.getApplication().getController('Common.Controllers.Plugins').setMode(this.appOptions); + this.getApplication().getController('LeftMenu').enablePlugins(); + }, + leavePageText: 'You have unsaved changes in this document. Click \'Stay on this Page\' then \'Save\' to save them. Click \'Leave this Page\' to discard all the unsaved changes.', defaultTitleText: 'ONLYOFFICE Document Editor', criticalErrorTitle: 'Error', diff --git a/apps/documenteditor/main/app/template/LeftMenu.template b/apps/documenteditor/main/app/template/LeftMenu.template index 039703e464..2bf5364f90 100644 --- a/apps/documenteditor/main/app/template/LeftMenu.template +++ b/apps/documenteditor/main/app/template/LeftMenu.template @@ -8,11 +8,13 @@ +