Commit 61a39c89 authored by Oleg Korshul's avatar Oleg Korshul

plugins

parent a5896310
......@@ -181,32 +181,32 @@
var fragments = wb.cellEditor.copySelection();
_data = wb.cellEditor._getFragmentsText(fragments);
_clipboard.pushData(c_oAscClipboardDataFormat.Text, _data)
_clipboard.pushData(AscCommon.c_oAscClipboardDataFormat.Text, _data)
}
else
{
//TEXT
if (c_oAscClipboardDataFormat.Text & _formats)
if (AscCommon.c_oAscClipboardDataFormat.Text & _formats)
{
//_data = ;
//_clipboard.pushData(c_oAscClipboardDataFormat.Text, _data)
//_clipboard.pushData(AscCommon.c_oAscClipboardDataFormat.Text, _data)
}
//HTML
if(c_oAscClipboardDataFormat.Html & _formats)
if(AscCommon.c_oAscClipboardDataFormat.Html & _formats)
{
_data = this._getHtml(activeRange, ws);
_clipboard.pushData(c_oAscClipboardDataFormat.Html, _data.html)
_clipboard.pushData(AscCommon.c_oAscClipboardDataFormat.Html, _data.html)
}
//INTERNAL
if(c_oAscClipboardDataFormat.Internal & _formats)
if(AscCommon.c_oAscClipboardDataFormat.Internal & _formats)
{
if(_data && _data.base64)
_data = _data.base64;
else
_data = this._getBinaryForCopy(worksheetView);
_clipboard.pushData(c_oAscClipboardDataFormat.Internal, _data)
_clipboard.pushData(AscCommon.c_oAscClipboardDataFormat.Internal, _data)
}
}
},
......
......@@ -164,6 +164,13 @@ window["NativeCorrectImageUrlOnPaste"] = function(url)
return window["AscDesktopEditor"]["LocalFileGetImageUrl"](url);
};
window["UpdateInstallPlugins"] = function()
{
var _plugins = JSON.parse(window["AscDesktopEditor"]["GetInstallPlugins"]());
var _editor = window["Asc"]["editor"] ? window["Asc"]["editor"] : window.editor;
_editor.asc_fireCallback("asc_onPluginsInit", _plugins);
};
AscCommon.InitDragAndDrop = function(oHtmlElement, callback) {
if ("undefined" != typeof(FileReader) && null != oHtmlElement) {
oHtmlElement["ondragover"] = function (e) {
......
......@@ -686,10 +686,10 @@ baseEditorsApi.prototype.asc_pluginsRegister = function(basePath, plugins)
if (null != this.pluginsManager)
this.pluginsManager.register(basePath, plugins);
};
baseEditorsApi.prototype.asc_pluginRun = function(guid, data)
baseEditorsApi.prototype.asc_pluginRun = function(guid, variation, data)
{
if (null != this.pluginsManager)
this.pluginsManager.run(guid, data);
this.pluginsManager.run(guid, variation, data);
};
baseEditorsApi.prototype.asc_pluginResize = function(guid, data, width, height)
{
......
......@@ -22,6 +22,7 @@ var c_oAscClipboardDataFormat = {
Internal : 4,
HtmlElement : 8
};
AscCommon.c_oAscClipboardDataFormat = c_oAscClipboardDataFormat;
function CClipboardBase()
{
......@@ -70,7 +71,7 @@ CClipboardBase.prototype =
}
else
{
this.Api.asc_CheckCopy(this, c_oAscClipboardDataFormat.Text | c_oAscClipboardDataFormat.Html | c_oAscClipboardDataFormat.Internal);
this.Api.asc_CheckCopy(this, AscCommon.c_oAscClipboardDataFormat.Text | AscCommon.c_oAscClipboardDataFormat.Html | AscCommon.c_oAscClipboardDataFormat.Internal);
e.preventDefault();
return false;
}
......@@ -90,7 +91,7 @@ CClipboardBase.prototype =
}
else
{
this.Api.asc_CheckCopy(this, c_oAscClipboardDataFormat.Text | c_oAscClipboardDataFormat.Html | c_oAscClipboardDataFormat.Internal);
this.Api.asc_CheckCopy(this, AscCommon.c_oAscClipboardDataFormat.Text | AscCommon.c_oAscClipboardDataFormat.Html | AscCommon.c_oAscClipboardDataFormat.Internal);
}
this.Api.asc_SelectionCut();
......@@ -123,7 +124,7 @@ CClipboardBase.prototype =
{
window.setTimeout( function() {
g_clipboardBase.Api.asc_PasteData(c_oAscClipboardDataFormat.HtmlElement, g_clipboardBase.CommonDiv);
g_clipboardBase.Api.asc_PasteData(AscCommon.c_oAscClipboardDataFormat.HtmlElement, g_clipboardBase.CommonDiv);
g_clipboardBase.CommonDiv_End();
g_clipboardBase.Paste_End();
......@@ -139,7 +140,7 @@ CClipboardBase.prototype =
var _internal = this.ClosureParams.getData("text/x-custom");
if (_internal && _internal != "" && _internal.indexOf("asc_internalData;") == 0)
{
this.Api.asc_PasteData(c_oAscClipboardDataFormat.Internal, _internal.substr("asc_internalData;".length));
this.Api.asc_PasteData(AscCommon.c_oAscClipboardDataFormat.Internal, _internal.substr("asc_internalData;".length));
g_clipboardBase.Paste_End();
return false;
}
......@@ -543,7 +544,7 @@ CClipboardBase.prototype =
this.CommonDiv_Start();
this.ClosureParams.isDivCopy = true;
this.Api.asc_CheckCopy(this, c_oAscClipboardDataFormat.Html);
this.Api.asc_CheckCopy(this, AscCommon.c_oAscClipboardDataFormat.Html);
this.ClosureParams.isDivCopy = false;
this.CommonDiv_Select();
......@@ -596,7 +597,7 @@ CClipboardBase.prototype =
{
ifr.style.display = "block";
this.Api.asc_PasteData(c_oAscClipboardDataFormat.HtmlElement, frameWindow.document.body, ifr);
this.Api.asc_PasteData(AscCommon.c_oAscClipboardDataFormat.HtmlElement, frameWindow.document.body, ifr);
}
}
......@@ -625,7 +626,7 @@ CClipboardBase.prototype =
{
if (this.ClosureParams.isDivCopy === true)
{
if (_format == c_oAscClipboardDataFormat.Html)
if (_format == AscCommon.c_oAscClipboardDataFormat.Html)
this.CommonDiv.innerHTML = _data;
return;
}
......@@ -633,13 +634,13 @@ CClipboardBase.prototype =
var _data_format = "";
switch (_format)
{
case c_oAscClipboardDataFormat.Html:
case AscCommon.c_oAscClipboardDataFormat.Html:
_data_format = "text/html";
break;
case c_oAscClipboardDataFormat.Text:
case AscCommon.c_oAscClipboardDataFormat.Text:
_data_format = "text/plain";
break;
case c_oAscClipboardDataFormat.Internal:
case AscCommon.c_oAscClipboardDataFormat.Internal:
_data_format = "text/x-custom";
break;
default:
......
......@@ -1454,9 +1454,10 @@ window['Asc']['c_oAscMaxCellOrCommentLength'] = window['Asc'].c_oAscMaxCellOrCom
// ----------------------------- plugins ------------------------------- //
var EPluginDataType =
{
none : 0,
text : 1,
ole : 2
none : "none",
text : "text",
ole : "ole",
html : "html"
};
window["Asc"]["EPluginDataType"] = EPluginDataType;
......@@ -1464,16 +1465,21 @@ window['Asc']['c_oAscMaxCellOrCommentLength'] = window['Asc'].c_oAscMaxCellOrCom
prot['none'] = prot.none;
prot['text'] = prot.text;
prot['ole'] = prot.ole;
prot['html'] = prot.html;
function CPlugin()
function CPluginVariation()
{
this.name = "";
this.guid = "";
this.description = "";
this.url = "";
this.index = 0; // сверху не выставляем. оттуда в каком порядке пришли - в таком порядке и работают
this.icons = ["1x", "2x"];
this.isViewer = false;
this.EditorsSupport = ["word", "cell", "slide"];
this.isVisual = false;
this.isVisual = false; // визуальный ли
this.isModal = false; // модальное ли окно (используется только для визуального)
this.isInsideMode = false; // отрисовка не в окне а внутри редактора (в панели) (используется только для визуального немодального)
this.initDataType = EPluginDataType.none;
this.initData = "";
......@@ -1482,28 +1488,55 @@ window['Asc']['c_oAscMaxCellOrCommentLength'] = window['Asc'].c_oAscMaxCellOrCom
this.buttons = [{"text":"Ok","primary":true},{"text":"Cancel","primary":false}];
}
CPluginVariation.prototype["get_Description"] = function() { return this.description; };
CPluginVariation.prototype["set_Description"] = function(value) { this.description = value; } ;
CPluginVariation.prototype["get_Url"] = function() { return this.url; };
CPluginVariation.prototype["set_Url"] = function(value) { this.url = value; };
CPluginVariation.prototype["get_Icons"] = function() { return this.icons; };
CPluginVariation.prototype["set_Icons"] = function(value) { this.icons = value; };
CPluginVariation.prototype["get_Viewer"] = function() { return this.isViewer; };
CPluginVariation.prototype["set_Viewer"] = function(value) { this.isViewer = value; };
CPluginVariation.prototype["get_EditorsSupport"] = function() { return this.EditorsSupport; };
CPluginVariation.prototype["set_EditorsSupport"] = function(value) { this.EditorsSupport = value; };
CPluginVariation.prototype["get_Visual"] = function() { return this.isVisual; };
CPluginVariation.prototype["set_Visual"] = function(value) { this.isVisual = value; };
CPluginVariation.prototype["get_Modal"] = function() { return this.isModal; };
CPluginVariation.prototype["set_Modal"] = function(value) { this.isModal = value; };
CPluginVariation.prototype["get_InsideMode"] = function() { return this.isInsideMode; };
CPluginVariation.prototype["set_InsideMode"] = function(value) { this.isInsideMode = value; };
CPluginVariation.prototype["get_InitDataType"] = function() { return this.initDataType; };
CPluginVariation.prototype["set_InitDataType"] = function(value) { this.initDataType = value; };
CPluginVariation.prototype["get_InitData"] = function() { return this.initData; };
CPluginVariation.prototype["set_InitData"] = function(value) { this.initData = value; };
CPluginVariation.prototype["get_UpdateOleOnResize"] = function() { return this.isUpdateOleOnResize; };
CPluginVariation.prototype["set_UpdateOleOnResize"] = function(value) { this.isUpdateOleOnResize = value; };
CPluginVariation.prototype["get_Buttons"] = function() { return this.buttons; };
CPluginVariation.prototype["set_Buttons"] = function(value) { this.buttons = value; };
function CPlugin()
{
this.name = "";
this.guid = "";
this.variations = [];
}
CPlugin.prototype["get_Name"] = function() { return this.name; };
CPlugin.prototype["set_Name"] = function(value) { this.name = value; } ;
CPlugin.prototype["get_Guid"] = function() { return this.guid; };
CPlugin.prototype["set_Guid"] = function(value) { this.guid = value; };
CPlugin.prototype["get_Url"] = function() { return this.url; };
CPlugin.prototype["set_Url"] = function(value) { this.url = value; };
CPlugin.prototype["get_Icons"] = function() { return this.icons; };
CPlugin.prototype["set_Icons"] = function(value) { this.icons = value; };
CPlugin.prototype["get_Visual"] = function() { return this.isVisual; };
CPlugin.prototype["set_Visual"] = function(value) { this.isVisual = value; };
CPlugin.prototype["get_InitDataType"] = function() { return this.initDataType; };
CPlugin.prototype["set_InitDataType"] = function(value) { this.initDataType = value; };
CPlugin.prototype["get_InitData"] = function() { return this.initData; };
CPlugin.prototype["set_InitData"] = function(value) { this.initData = value; };
CPlugin.prototype["get_UpdateOleOnResize"] = function() { return this.isUpdateOleOnResize; };
CPlugin.prototype["set_UpdateOleOnResize"] = function(value) { this.isUpdateOleOnResize = value; };
CPlugin.prototype["get_Buttons"] = function() { return this.buttons; };
CPlugin.prototype["set_Buttons"] = function(value) { this.buttons = value; };
CPlugin.prototype["get_Variations"] = function() { return this.variations; };
CPlugin.prototype["set_Variations"] = function(value) { this.variations = value; };
window["Asc"]["CPluginVariation"] = CPluginVariation;
window["Asc"]["CPlugin"] = CPlugin;
// --------------------------------------------------------------------- //
})(window);
......@@ -20,7 +20,7 @@
for (var i = 0; i < plugins.length; i++)
this.plugins.push(plugins[i]);
},
run : function(guid, data)
run : function(guid, variation, data)
{
if (null != this.current)
{
......@@ -28,6 +28,7 @@
{
this.runAndCloseData = {};
this.runAndCloseData.guid = guid;
this.runAndCloseData.variation = variation;
this.runAndCloseData.data = data;
}
// закрываем
......@@ -46,7 +47,7 @@
if (this.current == null)
return false;
this.startData = data;
this.startData = (data == null) ? "" : data;
this.show();
},
runResize : function(guid, data, width, height)
......@@ -54,7 +55,7 @@
},
close : function()
{
if (!this.current.isVisual) {
if (!this.current.variations[0].isVisual) {
var _div = document.getElementById("plugin_iframe");
if (_div)
_div.parentNode.removeChild(_div);
......@@ -63,7 +64,7 @@
if (this.runAndCloseData)
{
this.run(this.runAndCloseData.guid, this.runAndCloseData.data);
this.run(this.runAndCloseData.guid, this.runAndCloseData.variation, this.runAndCloseData.data);
this.runAndCloseData = null;
}
this.api.asc_fireCallback("asc_onPluginClose");
......@@ -71,7 +72,7 @@
show : function()
{
if (this.current.isVisual)
if (this.current.variations[0].isVisual)
{
this.api.asc_fireCallback("asc_onPluginShow", this.current);
}
......@@ -80,7 +81,7 @@
var ifr = document.createElement("iframe");
ifr.name = "plugin_iframe";
ifr.id = "plugin_iframe";
ifr.src = this.path + this.current.url;
ifr.src = this.path + this.current.variations[0].url;
ifr.style.position = 'absolute';
ifr.style.top = '-100px';
ifr.style.left = '0px';
......@@ -104,7 +105,7 @@
init : function()
{
var _data = "";
switch (this.current.initDataType)
switch (this.current.variations[0].initDataType)
{
case Asc.EPluginDataType.text:
{
......@@ -205,47 +206,141 @@
// потом удалить!!!
function TEST_PLUGINS()
{
var plugin1 = new Asc.CPlugin();
plugin1.name = "chess (fen)";
plugin1.guid = "{FFE1F462-1EA2-4391-990D-4CC84940B754}";
plugin1.url = "chess/index.html";
plugin1.icons = ["chess/icon.png", "chess/icon@2x.png"];
plugin1.isVisual = true;
plugin1.initDataType = Asc.EPluginDataType.ole;
plugin1.isUpdateOleOnResize = true;
plugin1.buttons = [{"text":"Ok","primary":true},{"text":"Cancel","primary":false}];
var plugin2 = new Asc.CPlugin();
plugin2.name = "glavred";
plugin2.guid = "{B631E142-E40B-4B4C-90B9-2D00222A286E}";
plugin2.url = "glavred/index.html";
plugin2.icons = ["glavred/icon.png", "glavred/icon@2x.png"];
plugin2.isVisual = true;
plugin2.initDataType = Asc.EPluginDataType.text;
plugin2.isUpdateOleOnResize = false;
plugin2.buttons = [{"text":"Ok","primary":true}];
var plugin3 = new Asc.CPlugin();
plugin3.name = "bold";
plugin3.guid = "{14E46CC2-5E56-429C-9D55-1032B596D928}";
plugin3.url = "bold/index.html";
plugin3.icons = ["bold/icon.png", "bold/icon@2x.png"];
plugin3.isVisual = false;
plugin3.initDataType = Asc.EPluginDataType.none;
plugin3.isUpdateOleOnResize = false;
plugin3.buttons = [];
var plugin4 = new Asc.CPlugin();
plugin4.name = "speech";
plugin4.guid = "{D71C2EF0-F15B-47C7-80E9-86D671F9C595}";
plugin4.url = "speech/index.html";
plugin4.icons = ["speech/icon.png", "speech/icon@2x.png"];
plugin4.isVisual = false;
plugin4.initDataType = Asc.EPluginDataType.text;
plugin4.isUpdateOleOnResize = false;
plugin4.buttons = [];
var _plugins = [plugin1, plugin2, plugin3, plugin4];
var _plugins = [
{
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 } ]
}
]
},
{
name : "bold",
guid : "{14E46CC2-5E56-429C-9D55-1032B596D928}",
variations : [
{
description : "bold",
url : "bold/index.html",
icons : ["bold/icon.png", "bold/icon@2x.png"],
isViewer : false,
EditorsSupport : ["word", "cell", "slide"],
isVisual : false,
isModal : false,
isInsideMode : false,
initDataType : "none",
initData : "",
isUpdateOleOnResize : false,
buttons : []
}
]
},
{
name : "speech",
guid : "{D71C2EF0-F15B-47C7-80E9-86D671F9C595}",
variations : [
{
description : "speech",
url : "speech/index.html",
icons : ["speech/icon.png", "speech/icon@2x.png"],
isViewer : true,
EditorsSupport : ["word", "cell", "slide"],
isVisual : false,
isModal : false,
isInsideMode : false,
initDataType : "text",
initData : "",
isUpdateOleOnResize : false,
buttons : [ ]
}
]
},
{
name : "youtube",
guid : "{38E022EA-AD92-45FC-B22B-49DF39746DB4}",
variations : [
{
description : "youtube",
url : "youtube/index.html",
icons : ["youtube/icon.png", "youtube/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 } ]
}
]
}
];
window.g_asc_plugins.api.asc_pluginsRegister("../../../../sdkjs-plugins/", _plugins);
// добавляем кнопки (тест)
......
......@@ -1220,7 +1220,7 @@ CGraphicObjects.prototype =
{
if(false === this.document.Document_Is_SelectionLocked(changestype_Drawing_Props))
{
editor.asc_pluginRun(oleObject.m_sApplicationId, oleObject.m_sData);
editor.asc_pluginRun(oleObject.m_sApplicationId, 0, oleObject.m_sData);
}
this.changeCurrentState(new AscFormat.NullState(this));
this.document.OnMouseUp(e, x, y, pageIndex);
......
......@@ -10,6 +10,8 @@ Asc['asc_docs_api'].prototype._OfflineAppDocumentStartLoad = function()
{
this.asc_registerCallback('asc_onDocumentContentReady', function(){
DesktopOfflineUpdateLocalName(editor);
setTimeout(function(){window["UpdateInstallPlugins"]();}, 10);
});
AscCommon.History.UserSaveMode = true;
......
......@@ -1911,18 +1911,18 @@ asc_docs_api.prototype.asc_CheckCopy = function(_clipboard /* CClipboardData */,
{
if (!this.WordControl.m_oLogicDocument)
{
var _text_object = (c_oAscClipboardDataFormat.Text & _formats) ? { Text : "" } : null;
var _text_object = (AscCommon.c_oAscClipboardDataFormat.Text & _formats) ? { Text : "" } : null;
var _html_data = this.WordControl.m_oDrawingDocument.m_oDocumentRenderer.Copy(_text_object);
//TEXT
if (c_oAscClipboardDataFormat.Text & _formats)
if (AscCommon.c_oAscClipboardDataFormat.Text & _formats)
{
_clipboard.pushData(c_oAscClipboardDataFormat.Text, _text_object.Text);
_clipboard.pushData(AscCommon.c_oAscClipboardDataFormat.Text, _text_object.Text);
}
//HTML
if(c_oAscClipboardDataFormat.Html & _formats)
if(AscCommon.c_oAscClipboardDataFormat.Html & _formats)
{
_clipboard.pushData(c_oAscClipboardDataFormat.Html, _html_data);
_clipboard.pushData(AscCommon.c_oAscClipboardDataFormat.Html, _html_data);
}
return;
}
......@@ -1930,22 +1930,22 @@ asc_docs_api.prototype.asc_CheckCopy = function(_clipboard /* CClipboardData */,
var sBase64 = null, _data;
//TEXT
if (c_oAscClipboardDataFormat.Text & _formats)
if (AscCommon.c_oAscClipboardDataFormat.Text & _formats)
{
_data = this.WordControl.m_oLogicDocument.Get_SelectedText();
_clipboard.pushData(c_oAscClipboardDataFormat.Text, _data)
_clipboard.pushData(AscCommon.c_oAscClipboardDataFormat.Text, _data)
}
//HTML
if(c_oAscClipboardDataFormat.Html & _formats)
if(AscCommon.c_oAscClipboardDataFormat.Html & _formats)
{
var oCopyProcessor = new AscCommon.CopyProcessor(this);
sBase64 = oCopyProcessor.Start();
_data = oCopyProcessor.getInnerHtml();
_clipboard.pushData(c_oAscClipboardDataFormat.Html, _data)
_clipboard.pushData(AscCommon.c_oAscClipboardDataFormat.Html, _data)
}
//INTERNAL
if(c_oAscClipboardDataFormat.Internal & _formats)
if(AscCommon.c_oAscClipboardDataFormat.Internal & _formats)
{
if(sBase64 === null)
{
......@@ -1954,7 +1954,7 @@ asc_docs_api.prototype.asc_CheckCopy = function(_clipboard /* CClipboardData */,
}
_data = sBase64;
_clipboard.pushData(c_oAscClipboardDataFormat.Internal, _data)
_clipboard.pushData(AscCommon.c_oAscClipboardDataFormat.Internal, _data)
}
};
......@@ -1977,10 +1977,10 @@ asc_docs_api.prototype.asc_PasteData = function(_format, data1, data2)
this.WordControl.m_oLogicDocument.Create_NewHistoryPoint(AscDFH.historydescription_Document_PasteHotKey);
switch (_format)
{
case c_oAscClipboardDataFormat.HtmlElement:
case AscCommon.c_oAscClipboardDataFormat.HtmlElement:
AscCommon.Editor_Paste_Exec(this, data1, data2);
break;
case c_oAscClipboardDataFormat.Internal:
case AscCommon.c_oAscClipboardDataFormat.Internal:
AscCommon.Editor_Paste_Exec(this, null, null, data1);
break;
default:
......
......@@ -1521,15 +1521,15 @@
ApiSection.prototype.SetType = function(sType)
{
if ("oddPage" === sType)
this.Section.Set_Type(Asc.c_oAscSectionBreakType.OddPage);
this.Section.Set_Type(c_oAscSectionBreakType.OddPage);
else if ("evenPage" === sType)
this.Section.Set_Type(Asc.c_oAscSectionBreakType.EvenPage);
this.Section.Set_Type(c_oAscSectionBreakType.EvenPage);
else if ("continuous" === sType)
this.Section.Set_Type(Asc.c_oAscSectionBreakType.Continuous);
this.Section.Set_Type(c_oAscSectionBreakType.Continuous);
else if ("nextColumn" === sType)
this.Section.Set_Type(Asc.c_oAscSectionBreakType.Column);
this.Section.Set_Type(c_oAscSectionBreakType.Column);
else if ("nextPage" === sType)
this.Section.Set_Type(Asc.c_oAscSectionBreakType.NextPage);
this.Section.Set_Type(c_oAscSectionBreakType.NextPage);
};
/**
* Specify all text columns in the current section are of equal width.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment