diff --git a/codex-editor.js b/codex-editor.js index 8d62ea9d..edaee88d 100644 --- a/codex-editor.js +++ b/codex-editor.js @@ -1,3 +1,3 @@ -var codex=codex||{};codex.editor=function(e){function t(o){if(n[o])return n[o].exports;var r=n[o]={exports:{},id:o,loaded:!1};return e[o].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";e.exports=function(e){e.version="1.6.4",e.scriptPrefix="cdx-script-";var t=function(){e.core=n(1),e.tools=n(2),e.ui=n(3),e.transport=n(4),e.renderer=n(5),e.saver=n(6),e.content=n(7),e.toolbar=n(8),e.callback=n(12),e.draw=n(13),e.caret=n(14),e.notifications=n(15),e.parser=n(16),e.sanitizer=n(17),e.listeners=n(19),e.destroyer=n(20),e.paste=n(21)};return e.settings={tools:["paragraph","header","picture","list","quote","code","twitter","instagram","smile"],holderId:"codex-editor",initialBlockPlugin:"paragraph"},e.nodes={holder:null,wrapper:null,toolbar:null,inlineToolbar:{wrapper:null,buttons:null,actions:null},toolbox:null,notifications:null,plusButton:null,showSettingsButton:null,showTrashButton:null,blockSettings:null,pluginSettings:null,defaultSettings:null,toolbarButtons:{},redactor:null},e.state={jsonOutput:[],blocks:[],inputs:[]},e.tools={},e.start=function(n){t(),e.core.prepare(n).then(e.ui.prepare).then(e.tools.prepare).then(e.sanitizer.prepare).then(e.paste.prepare).then(e.transport.prepare).then(e.renderer.makeBlocksFromData).then(e.ui.saveInputs).catch(function(t){e.core.log("Initialization failed with error: %o","warn",t)})},e}({})},function(e,t){"use strict";var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};e.exports=function(e){var t=codex.editor;e.prepare=function(e){return new Promise(function(o,r){e&&(t.settings.tools=e.tools||t.settings.tools),e.data&&(t.state.blocks=e.data),e.initialBlockPlugin&&(t.settings.initialBlockPlugin=e.initialBlockPlugin),e.sanitizer&&(t.settings.sanitizer=e.sanitizer),t.hideToolbar=e.hideToolbar,t.nodes.holder=document.getElementById(e.holderId||t.settings.holderId),void 0===n(t.nodes.holder)||null===t.nodes.holder?r(Error("Holder wasn't found by ID: #"+e.holderId)):o()})},e.log=function(e,t,n){t=t||"log",n?e="[codex-editor]: "+e:(n=e||"undefined",e="[codex-editor]: %o");try{"console"in window&&window.console[t]&&(n?window.console[t](e,n):window.console[t](e))}catch(e){}},e.insertAfter=function(e,t){e.parentNode.insertBefore(t,e.nextSibling)},e.nodeTypes={TAG:1,TEXT:3,COMMENT:8},e.keys={BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,LEFT:37,UP:38,DOWN:40,RIGHT:39,DELETE:46,META:91},e.isDomNode=function(e){return e&&"object"===("undefined"==typeof e?"undefined":n(e))&&e.nodeType&&e.nodeType==this.nodeTypes.TAG},e.isEmpty=function(e){return 0===Object.keys(e).length},e.ajax=function(e){if(e&&e.url){var t,n,r,i=window.XMLHttpRequest?new XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP");if(e.async=!0,e.type=e.type||"GET",e.data=e.data||"",e["content-type"]=e["content-type"]||"application/json; charset=utf-8","GET"==e.type&&e.data)e.url=/\?/.test(e.url)?e.url+"&"+e.data:e.url+"?"+e.data;else{t="";for(r in e.data)t+=r+"="+encodeURIComponent(e.data[r])+"&"}e.withCredentials&&(i.withCredentials=!0);var a=void 0,s=void 0;if("function"!=typeof e.beforeSend||(s=e.beforeSend.call(),s!==!1))return i.open(e.type,e.url,e.async),n=o(e.data),n||("POST"!==e.type?i.setRequestHeader("Content-type",e["content-type"]):i.setRequestHeader("Content-type","application/x-www-form-urlencoded")),i.setRequestHeader("X-Requested-With","XMLHttpRequest"),a=s||i,"function"==typeof e.progress&&(i.upload.onprogress=e.progress.bind(a)),i.onreadystatechange=function(){4===i.readyState&&(200===i.status?"function"==typeof e.success&&e.success.call(a,i.responseText):"function"==typeof e.error&&e.error.call(a,i.responseText,i.status))},n?i.send(e.data):i.send(t),i}},e.importScript=function(e,n){return new Promise(function(o,r){var i=void 0;n?document.getElementById(t.scriptPrefix+n)&&o(e):r("Instance name is missed"),i=document.createElement("SCRIPT"),i.async=!0,i.defer=!0,i.id=t.scriptPrefix+n,i.onload=function(){o(e)},i.onerror=function(){r(e)},i.src=e,document.head.appendChild(i)})};var o=function(e){return e instanceof FormData};return e.isNativeInput=function(e){var t=["INPUT","TEXTAREA"];return t.indexOf(e.tagName)!=-1},e}({})},function(e,t){"use strict";e.exports=function(){function e(){return new Promise(function(e,o){Promise.resolve().then(function(){var t=[],o=n.tools;for(var r in o){var i=o[r];i.prepare&&"function"!=typeof i.prepare||!i.prepare||t.push(i)}return t.length||e(),t}).then(t).then(function(){n.core.log("Plugins loaded","info"),e()}).catch(function(e){o(e)})})}function t(e){return new Promise(function(t){e.reduce(function(r,i,a){return r.then(function(){return new Promise(function(r){o(i).then(r).then(function(){i.available=!0}).catch(function(e){n.core.log("Plugin «"+i.type+"» was not loaded. Preparation failed because %o","warn",e),i.available=!1,i.loadingMessage=e,r()}).then(function(){a==e.length-1&&t()})})})},Promise.resolve())})}var n=codex.editor,o=function(e){return e.prepare(e.config||{})};return{prepare:e}}()},function(e,t){"use strict";e.exports=function(e){var t=codex.editor;e.className={BLOCK_CLASSNAME:"ce-block",BLOCK_CONTENT:"ce-block__content",BLOCK_STRETCHED:"ce-block--stretched",BLOCK_HIGHLIGHTED:"ce-block--focused",SETTINGS_ITEM:"ce-settings__item"},e.prepare=function(){return new Promise(function(e){var n=t.draw.wrapper(),r=t.draw.redactor(),i=o();n.appendChild(i),n.appendChild(r),t.nodes.wrapper=n,t.nodes.redactor=r,t.nodes.holder.appendChild(n),e()}).then(s).then(n).then(c).then(a).then(l).catch(function(){t.core.log("Can't draw editor interface")})};var n=function(){var e=t.draw.inlineToolbar();t.nodes.inlineToolbar.wrapper=e,t.nodes.inlineToolbar.buttons=t.draw.inlineToolbarButtons(),t.nodes.inlineToolbar.actions=t.draw.inlineToolbarActions(),t.nodes.inlineToolbar.wrapper.appendChild(t.nodes.inlineToolbar.buttons),t.nodes.inlineToolbar.wrapper.appendChild(t.nodes.inlineToolbar.actions),t.nodes.wrapper.appendChild(t.nodes.inlineToolbar.wrapper)},o=function(){var e=t.draw.toolbar(),n=i(),o=r();return e.appendChild(n),e.appendChild(o),t.nodes.toolbar=e,e},r=function(){var e=t.draw.toolbarContent(),n=t.draw.toolbox(),o=t.draw.plusButton();return e.appendChild(o),e.appendChild(n),t.nodes.toolbox=n,t.nodes.plusButton=o,e},i=function(){var e=t.draw.blockSettings(),n=t.draw.blockButtons(),o=t.draw.defaultSettings(),r=t.draw.settingsButton(),i=t.toolbar.settings.makeRemoveBlockButton(),a=t.draw.pluginsSettings();return e.appendChild(a),e.appendChild(o),n.appendChild(r),n.appendChild(i),n.appendChild(e),t.nodes.blockSettings=e,t.nodes.pluginSettings=a,t.nodes.defaultSettings=o,t.nodes.showSettingsButton=r,t.nodes.showTrashButton=i,n},a=function(){t.nodes.notifications=t.notifications.createHolder()},s=function(){var e,n,o;for(n in t.settings.tools)e=t.settings.tools[n],t.tools[n]=e,e.iconClassname||!e.displayInToolbox?"function"==typeof e.render?e.displayInToolbox&&(o=t.draw.toolbarButton(n,e.iconClassname),t.nodes.toolbox.appendChild(o),t.nodes.toolbarButtons[n]=o):t.core.log("render method missed. Tool %o skipped","warn",n):t.core.log("Toolbar icon classname missed. Tool %o skipped","warn",n)},c=function(){var e,n,o={bold:{icon:"ce-icon-bold",command:"bold"},italic:{icon:"ce-icon-italic",command:"italic"},underline:{icon:"ce-icon-underline",command:"underline"},link:{icon:"ce-icon-link",command:"createLink"}};for(var r in o)n=o[r],e=t.draw.toolbarButtonInline(r,n.icon),t.nodes.inlineToolbar.buttons.appendChild(e),t.ui.setInlineToolbarButtonBehaviour(e,n.command)},l=function(){t.core.log("ui.bindEvents fired","info"),t.listeners.add(document,"keydown",t.callback.globalKeydown,!1),t.listeners.add(t.nodes.redactor,"keydown",t.callback.redactorKeyDown,!1),t.listeners.add(document,"keyup",t.callback.globalKeyup,!1),t.listeners.add(t.nodes.redactor,"click",t.callback.redactorClicked,!1),t.listeners.add(t.nodes.plusButton,"click",t.callback.plusButtonClicked,!1),t.listeners.add(t.nodes.showSettingsButton,"click",t.callback.showSettingsButtonClicked,!1);for(var e in t.nodes.toolbarButtons)t.listeners.add(t.nodes.toolbarButtons[e],"click",t.callback.toolbarButtonClicked,!1)};return e.addBlockHandlers=function(e){e&&(t.listeners.add(e,"keydown",t.callback.blockKeydown,!1),t.listeners.add(e,"paste",t.paste.blockPasteCallback,!1),t.listeners.add(e,"mouseup",t.toolbar.inline.show,!1))},e.saveInputs=function(){var e=t.nodes.redactor;t.state.inputs=[];var n=e.querySelectorAll("[contenteditable], input, textarea");Array.prototype.map.call(n,function(e){e.type&&"text"!=e.type&&"textarea"!=e.type||t.state.inputs.push(e)})},e.addInitialBlock=function(){var e,n=t.settings.initialBlockPlugin;return t.tools[n]?(e=t.tools[n].render(),e.setAttribute("data-placeholder","Расскажите свою историю..."),t.content.insertBlock({type:n,block:e}),void t.content.workingNodeChanged(e)):void t.core.log("Plugin %o was not implemented and can't be used as initial block","warn",n)},e.setInlineToolbarButtonBehaviour=function(e,n){t.listeners.add(e,"mousedown",function(e){t.toolbar.inline.toolClicked(e,n)},!1)},e}({})},function(e,t){"use strict";e.exports=function(e){var t=codex.editor,n=null;return e.input=null,e.arguments=null,e.prepare=function(){var e=t.draw.node("INPUT","",{type:"file"});t.listeners.add(e,"change",t.transport.fileSelected),t.transport.input=e},e.clearInput=function(){e.input=null,e.prepare()},e.fileSelected=function(){var o,r=this,i=r.files,a=new FormData;if(t.transport.arguments.multiple===!0)for(o=0;o"+e.split("\n\n").join("

")+"

"};return e.getEditableParent=function(e){for(;e&&"true"!=e.contentEditable;)e=e.parentNode;return e},e}({})},function(e,t,n){"use strict";e.exports=function(e){var t=codex.editor;return e.settings=n(9),e.inline=n(10),e.toolbox=n(11),e.defaultToolbarHeight=49,e.defaultOffset=34,e.opened=!1,e.current=null,e.open=function(){t.hideToolbar||(t.nodes.toolbar.classList.add("opened"),this.opened=!0)},e.close=function(){t.nodes.toolbar.classList.remove("opened"),e.opened=!1,e.current=null;for(var n in t.nodes.toolbarButtons)t.nodes.toolbarButtons[n].classList.remove("selected");t.toolbar.toolbox.close(),t.toolbar.settings.close()},e.toggle=function(){this.opened?this.close():this.open()},e.hidePlusButton=function(){t.nodes.plusButton.classList.add("hide")},e.showPlusButton=function(){t.nodes.plusButton.classList.remove("hide")},e.move=function(){if(t.toolbar.toolbox.close(),t.content.currentNode){var e=t.content.currentNode.offsetTop-t.toolbar.defaultToolbarHeight/2+t.toolbar.defaultOffset;t.nodes.toolbar.style.transform="translate3D(0, "+Math.floor(e)+"px, 0)",t.toolbar.settings.hideRemoveActions()}},e}({})},function(e,t){"use strict";e.exports=function(e){var t=codex.editor;return e.opened=!1,e.setting=null,e.actions=null,e.open=function(e){if(t.tools[e]&&t.tools[e].makeSettings){var n=t.tools[e].makeSettings();t.nodes.pluginSettings.appendChild(n)}else t.core.log("Plugin «"+e+"» has no settings","warn"),t.nodes.pluginSettings.innerHTML="Плагин «"+e+"» не имеет настроек";t.nodes.blockSettings.classList.add("opened"),this.opened=!0},e.close=function(){t.nodes.blockSettings.classList.remove("opened"),t.nodes.pluginSettings.innerHTML="",this.opened=!1},e.toggle=function(e){this.opened?this.close():this.open(e)},e.makeRemoveBlockButton=function(){var e=t.draw.node("SPAN","ce-toolbar__remove-btn",{}),n=t.draw.node("SPAN","ce-toolbar__remove-setting",{innerHTML:''}),o=t.draw.node("DIV","ce-toolbar__remove-confirmation",{}),r=t.draw.node("DIV","ce-toolbar__remove-confirm",{textContent:"Удалить блок"}),i=t.draw.node("DIV","ce-toolbar__remove-cancel",{textContent:"Отмена"});return t.listeners.add(n,"click",t.toolbar.settings.removeButtonClicked,!1),t.listeners.add(r,"click",t.toolbar.settings.confirmRemovingRequest,!1),t.listeners.add(i,"click",t.toolbar.settings.cancelRemovingRequest,!1),o.appendChild(r),o.appendChild(i),e.appendChild(n),e.appendChild(o),t.toolbar.settings.setting=n,t.toolbar.settings.actions=o,e},e.removeButtonClicked=function(){var e=t.toolbar.settings.actions;e.classList.contains("opened")?t.toolbar.settings.hideRemoveActions():t.toolbar.settings.showRemoveActions(),t.toolbar.toolbox.close(),t.toolbar.settings.close()},e.cancelRemovingRequest=function(){t.toolbar.settings.actions.classList.remove("opened")},e.confirmRemovingRequest=function(){var e,n=t.content.currentNode;n.remove(),e=t.nodes.redactor.childNodes.length,0===e&&(t.content.currentNode=null,t.ui.addInitialBlock()),t.ui.saveInputs(),t.toolbar.close()},e.showRemoveActions=function(){t.toolbar.settings.actions.classList.add("opened")},e.hideRemoveActions=function(){t.toolbar.settings.actions.classList.remove("opened")},e}({})},function(e,t){"use strict";e.exports=function(e){var t=codex.editor;e.buttonsOpened=null,e.actionsOpened=null,e.wrappersOffset=null,e.storedSelection=null,e.show=function(){var n,o=t.content.currentNode,r=o.dataset.tool;if(n=t.tools[r],n.showInlineToolbar){var i=e.getSelectionText(),a=t.nodes.inlineToolbar.wrapper;i.length>0&&(t.toolbar.inline.move(),a.classList.add("opened"),t.toolbar.inline.showButtons())}},e.close=function(){var e=t.nodes.inlineToolbar.wrapper;e.classList.remove("opened")},e.move=function(){this.wrappersOffset||(this.wrappersOffset=this.getWrappersOffset());var e,n,o=this.getSelectionCoords(),r=0,i=t.nodes.inlineToolbar.wrapper;0===i.offsetHeight&&(r=40),e=o.x-this.wrappersOffset.left,n=o.y+window.scrollY-this.wrappersOffset.top-r-i.offsetHeight,i.style.transform="translate3D("+Math.floor(e)+"px, "+Math.floor(n)+"px, 0)",t.toolbar.inline.closeButtons(),t.toolbar.inline.closeAction()},e.toolClicked=function(e,n){switch(n){case"createLink":t.toolbar.inline.createLinkAction(e,n);break;default:t.toolbar.inline.defaultToolAction(n)}t.nodes.inlineToolbar.buttons.childNodes.forEach(t.toolbar.inline.hightlight)},e.getWrappersOffset=function(){var e=t.nodes.wrapper,n=this.getOffset(e);return this.wrappersOffset=n,n},e.getOffset=function(e){for(var t=0,n=0;e&&!isNaN(e.offsetLeft)&&!isNaN(e.offsetTop);)t+=e.offsetLeft+e.clientLeft,n+=e.offsetTop+e.clientTop,e=e.offsetParent;return{top:n,left:t}},e.getSelectionCoords=function(){var e,t=document.selection,n=0,o=0;if(t)"Control"!=t.type&&(e=t.createRange(),e.collapse(!0),n=e.boundingLeft,o=e.boundingTop);else if(window.getSelection&&(t=window.getSelection(),t.rangeCount&&(e=t.getRangeAt(0).cloneRange(),e.getClientRects))){e.collapse(!0);var r=e.getClientRects()[0];if(!r)return;n=r.left,o=r.top}return{x:n,y:o}},e.getSelectionText=function(){var e="";return window.getSelection&&(e=window.getSelection().toString()),e},e.showButtons=function(){var e=t.nodes.inlineToolbar.buttons;e.classList.add("opened"),t.toolbar.inline.buttonsOpened=!0,t.nodes.inlineToolbar.buttons.childNodes.forEach(t.toolbar.inline.hightlight)},e.closeButtons=function(){var e=t.nodes.inlineToolbar.buttons;e.classList.remove("opened"),t.toolbar.inline.buttonsOpened=!1},e.showActions=function(){var e=t.nodes.inlineToolbar.actions;e.classList.add("opened"),t.toolbar.inline.actionsOpened=!0},e.closeAction=function(){var e=t.nodes.inlineToolbar.actions;e.innerHTML="",e.classList.remove("opened"),t.toolbar.inline.actionsOpened=!1};var n=function(e){if(e.keyCode==t.core.keys.ENTER){var n=t.content.currentNode,o=t.toolbar.inline.storedSelection;t.toolbar.inline.restoreSelection(n,o),t.toolbar.inline.setAnchor(this.value),e.preventDefault(),e.stopImmediatePropagation(),t.toolbar.inline.clearRange()}};return e.createLinkAction=function(e){var o=this.isLinkActive(),r=t.content.currentNode,i=t.toolbar.inline.saveSelection(r);if(t.toolbar.inline.storedSelection=i,o)t.toolbar.inline.restoreSelection(r,i),t.toolbar.inline.defaultToolAction("unlink");else{var a=t.draw.inputForLink();t.nodes.inlineToolbar.actions.appendChild(a),t.toolbar.inline.closeButtons(),t.toolbar.inline.showActions(),a.focus(),e.preventDefault(),t.listeners.add(a,"keydown",n,!1)}},e.isLinkActive=function(){var e=!1;return t.nodes.inlineToolbar.buttons.childNodes.forEach(function(t){var n=t.dataset.type;"link"==n&&t.classList.contains("hightlighted")&&(e=!0)}),e},e.defaultToolAction=function(e){document.execCommand(e,!1,null)},e.setAnchor=function(e){document.execCommand("createLink",!1,e),t.toolbar.inline.closeAction()},e.saveSelection=function(e){var t,n=window.getSelection().getRangeAt(0),o=n.cloneRange();return o.selectNodeContents(e),o.setEnd(n.startContainer,n.startOffset),t=o.toString().length,{start:t,end:t+n.toString().length}},e.restoreSelection=function(e,t){var n=document.createRange(),o=0;n.setStart(e,0),n.collapse(!0);for(var r,i,a=[e],s=!1,c=!1;!c&&(r=a.pop());)if(3==r.nodeType)i=o+r.length,!s&&t.start>=o&&t.start<=i&&(n.setStart(r,t.start-o),s=!0),s&&t.end>=o&&t.end<=i&&(n.setEnd(r,t.end-o),c=!0),o=i;else for(var l=r.childNodes.length;l--;)a.push(r.childNodes[l]);var d=window.getSelection();d.removeAllRanges(),d.addRange(n)},e.clearRange=function(){var e=window.getSelection();e.removeAllRanges()},e.hightlight=function(e){var n=e.dataset.type;document.queryCommandState(n)?t.toolbar.inline.setButtonHighlighted(e):t.toolbar.inline.removeButtonsHighLight(e);var o=window.getSelection(),r=o.anchorNode.parentNode;"A"==r.tagName&&"link"==n&&t.toolbar.inline.setButtonHighlighted(e)},e.setButtonHighlighted=function(e){if(e.classList.add("hightlighted"),"link"==e.dataset.type){var t=e.childNodes[0];t.classList.remove("ce-icon-link"),t.classList.add("ce-icon-unlink")}},e.removeButtonsHighLight=function(e){if(e.classList.remove("hightlighted"),"link"==e.dataset.type){var t=e.childNodes[0];t.classList.remove("ce-icon-unlink"),t.classList.add("ce-icon-link")}},e}({})},function(e,t){"use strict";e.exports=function(e){var t=codex.editor;return e.opened=!1,e.open=function(){t.toolbar.settings.opened&&t.toolbar.settings.close(),t.nodes.toolbox.classList.add("opened"),t.nodes.plusButton.classList.add("clicked"),t.toolbar.toolbox.opened=!0},e.close=function(){t.nodes.toolbox.classList.remove("opened"),t.nodes.plusButton.classList.remove("clicked"),t.toolbar.toolbox.opened=!1,t.toolbar.current=null},e.leaf=function(){var e=t.toolbar.current,n=Object.keys(t.tools),o=t.nodes.toolbarButtons,r=0,i=void 0,a=void 0,s=void 0;if(e)for(r=(n.indexOf(e)+1)%n.length,a=n[r];!t.tools[a].displayInToolbox;)r=(r+1)%n.length,a=n[r];else for(s in t.tools){if(t.tools[s].displayInToolbox)break;r++}i=n[r];for(var c in o)o[c].classList.remove("selected");o[i].classList.add("selected"),t.toolbar.current=i},e.toolClicked=function(e){var n,o,r,i=["image","link","list","instagram","twitter","embed"],a=t.tools[t.toolbar.current],s=t.content.currentNode,c=t.caret.inputIndex;n=a.render(),r={block:n,type:a.type,stretched:!1},s&&i.indexOf(s.dataset.tool)===-1&&""===s.textContent.trim()?t.content.switchBlock(s,n,a.type):(t.content.insertBlock(r),c++),o=a.appendCallback,o&&"function"==typeof o&&o.call(e),window.setTimeout(function(){t.caret.setToBlock(c)},10),t.content.workingNodeChanged(),t.toolbar.move()},e}({})},function(e,t){"use strict";e.exports=function(e){var t=codex.editor;e.globalKeydown=function(e){switch(e.keyCode){case t.core.keys.ENTER:o(e)}},e.redactorKeyDown=function(e){switch(e.keyCode){case t.core.keys.TAB:n(e);break;case t.core.keys.ENTER:i(e);break;case t.core.keys.ESC:a(e);break;default:c(e)}},e.globalKeyup=function(e){switch(e.keyCode){case t.core.keys.UP:case t.core.keys.LEFT:case t.core.keys.RIGHT:case t.core.keys.DOWN:s(e)}};var n=function(e){e.preventDefault();var n=t.content.currentNode.querySelectorAll("textarea, input"),o=!0,r=!t.content.currentNode.textContent.trim();Array.prototype.map.call(n,function(e){"textarea"!=e.type&&"text"!=e.type||(o=o&&!e.value.trim())});var i=r&&o;i&&(t.toolbar.opened||t.toolbar.open(),t.toolbar.opened&&!t.toolbar.toolbox.opened?t.toolbar.toolbox.open():t.toolbar.toolbox.leaf())},o=function(){t.content.editorAreaHightlighted&&(t.caret.inputIndex=-1,r())},r=function(){var e=t.settings.initialBlockPlugin;t.content.insertBlock({type:e,block:t.tools[e].render()},!0),t.toolbar.move(),t.toolbar.open()},i=function(e){"true"==e.target.contentEditable&&t.caret.saveCurrentInputIndex();var n=t.caret.getCurrentInputIndex()||0,o=t.content.currentNode,r=o.dataset.tool,i=t.toolbar.opened&&t.toolbar.current&&e.target==t.state.inputs[n],a=t.tools[r].enableLineBreaks,s=t.settings.initialBlockPlugin;if(i)return e.preventDefault(),t.toolbar.toolbox.toolClicked(e),t.toolbar.close(),e.stopPropagation(),void e.stopImmediatePropagation();if(e.shiftKey||a)return e.stopPropagation(),void e.stopImmediatePropagation();var c=window.getSelection(),l=c.anchorNode,d=t.caret.position.atTheEnd(),u=!1;if(e.shiftKey&&!a)return t.callback.enterPressedOnBlock(t.content.currentBlock,e),void e.preventDefault();if(u=l&&"true"!=l.parentNode.contentEditable,l.nodeType!=t.core.nodeTypes.TEXT||u||d){var p=t.content.isLastNode(l);p&&d&&(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),t.core.log("ENTER clicked in last textNode. Create new BLOCK"),t.content.insertBlock({type:s,block:t.tools[s].render()},!0),t.toolbar.move(),t.toolbar.open(),t.toolbar.showPlusButton())}else e.preventDefault(),t.core.log("Splitting Text node..."),t.content.splitBlock(n),t.state.inputs[n+1].textContent.trim()||t.toolbar.showPlusButton();t.ui.saveInputs()},a=function(e){t.toolbar.close(),t.toolbar.toolbox.close(),e.preventDefault()},s=function(e){t.content.workingNodeChanged(),t.toolbar.close(),t.toolbar.move()},c=function(){t.toolbar.close(),t.toolbar.inline.actionsOpened||(t.toolbar.inline.close(),t.content.clearMark())};e.redactorClicked=function(e){l(),t.content.workingNodeChanged(e.target),t.ui.saveInputs();var n,o=t.toolbar.inline.getSelectionText();if(0===o.length&&t.toolbar.inline.close(),"true"==e.target.contentEditable&&t.caret.saveCurrentInputIndex(),null===t.content.currentNode){var r=t.state.inputs.length>0?t.state.inputs.length-1:0;if(t.state.inputs.length&&(n=t.content.getFirstLevelBlock(t.state.inputs[r])),t.state.inputs.length&&""===t.state.inputs[r].textContent&&n.dataset.tool==t.settings.initialBlockPlugin)t.caret.setToBlock(r);else{var i=t.settings.initialBlockPlugin;t.content.insertBlock({type:i,block:t.tools[i].render()}),1===t.state.inputs.length?t.caret.setToBlock(r):t.caret.setToNextBlock(r)}}else t.toolbar.settings.close(),t.toolbar.toolbox.close();t.toolbar.move(),t.toolbar.open();var a=!t.content.currentNode.textContent.trim(),s=t.content.currentNode.dataset.tool,c=s==t.settings.initialBlockPlugin;t.toolbar.hidePlusButton(),a||t.content.markBlock(),c&&a&&t.toolbar.showPlusButton()};var l=function(){var e=window.getSelection(),n=e.anchorNode,o=!1;if(0===e.rangeCount)t.content.editorAreaHightlighted=!0;else{for(t.core.isDomNode(n)||(n=n.parentNode),"true"==n.contentEditable&&(o=!0);"true"!=n.contentEditable&&(n=n.parentNode,"true"==n.contentEditable&&(o=!0),n!=document.body););t.content.editorAreaHightlighted=!o}};e.toolbarButtonClicked=function(e){var n=this;t.toolbar.current=n.dataset.type,t.toolbar.toolbox.toolClicked(e),t.toolbar.close()},e.plusButtonClicked=function(){t.nodes.toolbox.classList.contains("opened")?t.toolbar.toolbox.close():t.toolbar.toolbox.open()},e.blockKeydown=function(e){var n=e.target;switch(e.keyCode){case t.core.keys.DOWN:case t.core.keys.RIGHT:d(e);break;case t.core.keys.BACKSPACE:p(n,e);break;case t.core.keys.UP:case t.core.keys.LEFT:u(e)}};var d=function(e){var n,o=window.getSelection(),r=t.state.inputs,i=o.anchorNode;if(!i)return!1;for(;"true"!=i.contentEditable;)n=i.parentNode,i=n;for(var a=0;i!=r[a];)a++;if(!i.textContent)return void t.caret.setToNextBlock(a);var s,c,l=!1,d=!1;return s=i.childNodes[i.childNodes.length-1],c=t.core.isDomNode(s)?t.content.getDeepestTextNodeFromPosition(s,s.childNodes.length):s,l=o.anchorNode==c,d=c.length==o.anchorOffset,l&&d?void t.caret.setToNextBlock(a):(t.core.log("arrow [down|right] : caret does not reached the end"),!1)},u=function(e){var n,o=window.getSelection(),r=t.state.inputs,i=o.anchorNode;if(!i)return!1;if(0!==o.anchorOffset)return!1;for(;"true"!=i.contentEditable;)n=i.parentNode,i=n;for(var a=0;i!=r[a];)a++;var s,c,l=!1,d=!1;return i.textContent?(s=i.childNodes[0],c=t.core.isDomNode(s)?t.content.getDeepestTextNodeFromPosition(s,0):s,l=o.anchorNode==c,d=0===o.anchorOffset,void(l&&d&&t.caret.setToPreviousBlock(a))):void t.caret.setToPreviousBlock(a)},p=function(e,n){var o,r,i,a=t.caret.getCurrentInputIndex();if(t.core.isNativeInput(n.target)){if(""!=n.target.value.trim())return;e.remove()}if(e.textContent.trim()){if(o=t.content.getRange(),r=o.endOffset-o.startOffset,!t.caret.position.atStart()||r||!t.state.inputs[a-1])return;t.content.mergeBlocks(a)}r||e.remove(),i=t.nodes.redactor.childNodes.length,0===i?(t.content.currentNode=null,t.ui.addInitialBlock(),t.ui.saveInputs(), -window.setTimeout(function(){t.caret.setToPreviousBlock(1)},10)):0!==t.caret.inputIndex?t.caret.setToPreviousBlock(t.caret.inputIndex):t.caret.setToNextBlock(t.caret.inputIndex),t.toolbar.move(),t.toolbar.opened||t.toolbar.open(),t.ui.saveInputs(),n.preventDefault()};return e.showSettingsButtonClicked=function(e){var n=t.content.currentNode.dataset.tool;t.toolbar.settings.toggle(n),t.toolbar.toolbox.close(),t.toolbar.settings.hideRemoveActions()},e}({})},function(e,t){"use strict";e.exports=function(e){return e.wrapper=function(){var e=document.createElement("div");return e.className+="codex-editor",e},e.redactor=function(){var e=document.createElement("div");return e.className+="ce-redactor",e},e.ceBlock=function(){var e=document.createElement("DIV");return e.className+="ce_block",e},e.toolbar=function(){var e=document.createElement("div");return e.className+="ce-toolbar",e},e.toolbarContent=function(){var e=document.createElement("DIV");return e.classList.add("ce-toolbar__content"),e},e.inlineToolbar=function(){var e=document.createElement("DIV");return e.className+="ce-toolbar-inline",e},e.inlineToolbarButtons=function(){var e=document.createElement("DIV");return e.className+="ce-toolbar-inline__buttons",e},e.inlineToolbarActions=function(){var e=document.createElement("DIV");return e.className+="ce-toolbar-inline__actions",e},e.inputForLink=function(){var e=document.createElement("INPUT");return e.type="input",e.className+="inputForLink",e.placeholder="Вставьте ссылку ...",e.setAttribute("form","defaultForm"),e.setAttribute("autofocus","autofocus"),e},e.blockButtons=function(){var e=document.createElement("div");return e.className+="ce-toolbar__actions",e},e.blockSettings=function(){var e=document.createElement("div");return e.className+="ce-settings",e},e.defaultSettings=function(){var e=document.createElement("div");return e.classList.add("ce-settings_default"),e},e.pluginsSettings=function(){var e=document.createElement("div");return e.classList.add("ce-settings_plugin"),e},e.plusButton=function(){var e=document.createElement("span");return e.className="ce-toolbar__plus",e},e.settingsButton=function(){var e=document.createElement("span");return e.className="ce-toolbar__settings-btn",e.innerHTML='',e},e.toolbox=function(){var e=document.createElement("div");return e.className="ce-toolbar__tools",e},e.toolbarButton=function(e,t){var n=document.createElement("li"),o=document.createElement("i"),r=document.createElement("span");return n.dataset.type=e,n.setAttribute("title",e),o.classList.add(t),r.classList.add("ce_toolbar_tools--title"),n.appendChild(o),n.appendChild(r),n},e.toolbarButtonInline=function(e,t){var n=document.createElement("BUTTON"),o=document.createElement("I");return n.type="button",n.dataset.type=e,o.classList.add(t),n.appendChild(o),n},e.block=function(e,t){var n=document.createElement(e);return n.innerHTML=t||"",n},e.node=function(e,t,n){var o=document.createElement(e);if(t&&(o.className=t),n)for(var r in n)o[r]=n[r];return o},e.unavailableBlock=function(){var e=document.createElement("DIV");return e.classList.add("cdx-unavailable-block"),e},e}({})},function(e,t){"use strict";e.exports=function(e){var t=codex.editor;return e.inputIndex=null,e.offset=null,e.focusedNodeIndex=null,e.set=function(n,o,r){r=r||e.offset||0,o=o||e.focusedNodeIndex||0;var i,a=n.childNodes;if(i=0===a.length?n:a[o],"true"!=n.contentEditable)return void n.focus();t.core.isDomNode(i)&&(i=t.content.getDeepestTextNodeFromPosition(i,i.childNodes.length));var s=document.createRange(),c=window.getSelection();window.setTimeout(function(){s.setStart(i,r),s.setEnd(i,r),c.removeAllRanges(),c.addRange(s),t.caret.saveCurrentInputIndex()},20)},e.saveCurrentInputIndex=function(){var n,o=window.getSelection(),r=t.state.inputs,i=o.anchorNode;if(i){for(;"true"!=i.contentEditable;)n=i.parentNode,i=n;for(var a=0;i!=r[a];)a++;e.inputIndex=a}},e.getCurrentInputIndex=function(){return e.inputIndex},e.setToNextBlock=function(e){var n=t.state.inputs,o=n[e+1];if(!o)return void t.core.log("We are reached the end");if(!o.childNodes.length){var r=document.createTextNode("");o.appendChild(r)}t.caret.inputIndex=e+1,t.caret.set(o,0,0),t.content.workingNodeChanged(o)},e.setToBlock=function(e){var n=t.state.inputs,o=n[e];if(o){if(!o.childNodes.length){var r=document.createTextNode("");o.appendChild(r)}t.caret.inputIndex=e,t.caret.set(o,0,0),t.content.workingNodeChanged(o)}},e.setToPreviousBlock=function(e){e=e||0;var n,o,r,i=t.state.inputs,a=i[e-1];return a?(n=t.content.getDeepestTextNodeFromPosition(a,a.childNodes.length),o=n.length,a.childNodes.length||(r=document.createTextNode(""),a.appendChild(r)),t.caret.inputIndex=e-1,t.caret.set(a,a.childNodes.length-1,o),void t.content.workingNodeChanged(i[e-1])):void t.core.log("We are reached first node")},e.position={atStart:function(){var e=window.getSelection(),n=e.anchorOffset,o=e.anchorNode,r=t.content.getFirstLevelBlock(o),i=r.childNodes[0];t.core.isDomNode(o)||(o=o.parentNode);var a=o===i.childNodes[0],s=0===n;return a&&s},atTheEnd:function(){var e=window.getSelection(),t=e.anchorOffset,n=e.anchorNode;return!n||!n.length||t===n.length}},e.insertNode=function(e){var t,n,o=e;e instanceof window.DocumentFragment&&(o=e.lastChild),t=window.getSelection(),n=t.getRangeAt(0),n.deleteContents(),n.insertNode(e),n.setStartAfter(o),n.collapse(!0),t.removeAllRanges(),t.addRange(n)},e}({})},function(e,t){"use strict";e.exports=function(e){var t=codex.editor,n=[],o=function(e){n.push(e);for(var t=0;t5;)"confirm"!=n[t].type&&"prompt"!=n[t].type?(n[t].close(),n.splice(t,1)):t++};return e.createHolder=function(){var e=t.draw.node("DIV","cdx-notifications-block");return t.nodes.notifications=document.body.appendChild(e),e},e.errorThrown=function(e,n){t.notifications.notification({message:"This action is not available currently",type:n.type})},e.notification=function(e){function n(e){if(!e||!e.message)return void t.core.log("Can't create notification. Message is missed");e.type=e.type||"alert",e.time=1e3*e.time||1e4;var n=t.draw.node("DIV","cdx-notification"),o=t.draw.node("DIV","cdx-notification__message"),r=t.draw.node("INPUT","cdx-notification__input"),f=t.draw.node("SPAN","cdx-notification__ok-btn"),g=t.draw.node("SPAN","cdx-notification__cancel-btn");o.textContent=e.message,f.textContent=e.okMsg||"ОК",g.textContent=e.cancelMsg||"Отмена",t.listeners.add(f,"click",u),t.listeners.add(g,"click",p),n.appendChild(o),"prompt"==e.type&&n.appendChild(r),n.appendChild(f),"prompt"!=e.type&&"confirm"!=e.type||n.appendChild(g),n.classList.add("cdx-notification-"+e.type),n.dataset.type=e.type,a=n,c=e.type,l=e.confirm,s=e.cancel,d=r,"prompt"!=e.type&&"confirm"!=e.type&&window.setTimeout(i,e.time)}function r(){t.nodes.notifications.appendChild(a),d.focus(),t.nodes.notifications.classList.add("cdx-notification__notification-appending"),window.setTimeout(function(){t.nodes.notifications.classList.remove("cdx-notification__notification-appending")},100),o({type:c,close:i})}function i(){a.remove()}var a=null,s=null,c=null,l=null,d=null,u=function(){if(i(),"function"==typeof l)return"prompt"==c?void l(d.value):void l()},p=function(){i(),"function"==typeof s&&s()};return e&&(n(e),r()),{create:n,send:r,close:i}},e.clear=function(){t.nodes.notifications.innerHTML="",n=[]},e}({})},function(e,t){"use strict";e.exports=function(e){var t=codex.editor;return e.insertPastedContent=function(e,n){t.content.insertBlock({type:e.type,block:e.render({text:n.innerHTML})})},e.isFirstLevelBlock=function(e){return e.nodeType==t.core.nodeTypes.TAG&&e.classList.contains(t.ui.className.BLOCK_CLASSNAME)},e}({})},function(e,t,n){"use strict";e.exports=function(e){var t=n(18),o=codex.editor;e.prepare=function(){o.settings.sanitizer&&!o.core.isEmpty(o.settings.sanitizer)&&(r.CUSTOM=o.settings.sanitizer)};var r={CUSTOM:null,BASIC:{tags:{p:{},a:{href:!0,target:"_blank",rel:"nofollow"},i:{},b:{},strong:{},em:{},span:{}}}};e.Config=r;var i=function(e){var n=e||r.CUSTOM||r.BASIC;return new t(n)};return e.clean=function(e,t){var n=i(t);return n.clean(e)},e}({})},function(e,t,n){var o,r;!function(i,a){o=a,r="function"==typeof o?o.call(t,n,t,e):o,!(void 0!==r&&(e.exports=r))}(this,function(){function e(e){var t=e.tags,n=Object.keys(t),o=n.map(function(e){return typeof t[e]}).every(function(e){return"object"===e||"boolean"===e||"function"===e});if(!o)throw new Error("The configuration was invalid");this.config=e}function t(e){return s.indexOf(e.nodeName)!==-1}function n(e){return c.indexOf(e.nodeName)!==-1}function o(e){return document.createTreeWalker(e,NodeFilter.SHOW_TEXT|NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_COMMENT,null,!1)}function r(e,t,n){return"function"==typeof e.tags[t]?e.tags[t](n):e.tags[t]}function i(e,t){return"undefined"==typeof t||"boolean"==typeof t&&!t}function a(e,t,n){var o=e.name.toLowerCase();return t!==!0&&("function"==typeof t[o]?!t[o](e.value,n):"undefined"==typeof t[o]||(t[o]===!1||"string"==typeof t[o]&&t[o]!==e.value))}var s=["P","LI","TD","TH","DIV","H1","H2","H3","H4","H5","H6","PRE"],c=["A","B","STRONG","I","EM","SUB","SUP","U","STRIKE"];return e.prototype.clean=function(e){var t=document.createElement("div");return t.innerHTML=e,this._sanitize(t),t.innerHTML},e.prototype._sanitize=function(e){var s=o(e),c=s.firstChild();if(c)do if(!c._sanitized)if(c.nodeType!==Node.TEXT_NODE){if(c.nodeType===Node.COMMENT_NODE){e.removeChild(c),this._sanitize(e);break}var l,d=n(c);d&&(l=Array.prototype.some.call(c.childNodes,t));var u=!!e.parentNode,p=t(e)&&t(c)&&u,f=c.nodeName.toLowerCase(),g=r(this.config,f,c),h=d&&l;if(h||i(c,g)||!this.config.keepNestedBlockElements&&p){if("SCRIPT"!==c.nodeName&&"STYLE"!==c.nodeName)for(;c.childNodes.length>0;)e.insertBefore(c.childNodes[0],c);e.removeChild(c),this._sanitize(e);break}for(var v=0;v0&&t.splice(s,1)}},e.removeAll=function(){t.map(function(t){e.remove(t.element,t.type,t.handler)})},e.get=function(t,n,o){return e.search.all(t,n,o)},e}({})},function(e,t){"use strict";var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};e.exports=function(e){var t=codex.editor;return e.removeNodes=function(){t.nodes.wrapper.remove(),t.nodes.notifications.remove()},e.destroyPlugins=function(){for(var e in t.tools)"function"==typeof t.tools[e].destroy&&t.tools[e].destroy()},e.destroyScripts=function(){for(var e=document.getElementsByTagName("SCRIPT"),n=0;n"+e.split("\n\n").join("

")+"

"};return e.getEditableParent=function(e){for(;e&&"true"!=e.contentEditable;)e=e.parentNode;return e},e}({})},function(e,t,n){"use strict";e.exports=function(e){var t=codex.editor;return e.settings=n(9),e.inline=n(10),e.toolbox=n(11),e.defaultToolbarHeight=49,e.defaultOffset=34,e.opened=!1,e.current=null,e.open=function(){t.hideToolbar||(t.nodes.toolbar.classList.add("opened"),this.opened=!0)},e.close=function(){t.nodes.toolbar.classList.remove("opened"),e.opened=!1,e.current=null;for(var n in t.nodes.toolbarButtons)t.nodes.toolbarButtons[n].classList.remove("selected");t.toolbar.toolbox.close(),t.toolbar.settings.close()},e.toggle=function(){this.opened?this.close():this.open()},e.hidePlusButton=function(){t.nodes.plusButton.classList.add("hide")},e.showPlusButton=function(){t.nodes.plusButton.classList.remove("hide")},e.move=function(){if(t.toolbar.toolbox.close(),t.content.currentNode){var e=t.content.currentNode.offsetTop-t.toolbar.defaultToolbarHeight/2+t.toolbar.defaultOffset;t.nodes.toolbar.style.transform="translate3D(0, "+Math.floor(e)+"px, 0)",t.toolbar.settings.hideRemoveActions()}},e}({})},function(e,t){"use strict";e.exports=function(e){var t=codex.editor;return e.opened=!1,e.setting=null,e.actions=null,e.open=function(e){if(t.tools[e]&&t.tools[e].makeSettings){var n=t.tools[e].makeSettings();t.nodes.pluginSettings.appendChild(n)}else t.core.log("Plugin «"+e+"» has no settings","warn"),t.nodes.pluginSettings.innerHTML="Плагин «"+e+"» не имеет настроек";t.nodes.blockSettings.classList.add("opened"),this.opened=!0},e.close=function(){t.nodes.blockSettings.classList.remove("opened"),t.nodes.pluginSettings.innerHTML="",this.opened=!1},e.toggle=function(e){this.opened?this.close():this.open(e)},e.makeRemoveBlockButton=function(){var e=t.draw.node("SPAN","ce-toolbar__remove-btn",{}),n=t.draw.node("SPAN","ce-toolbar__remove-setting",{innerHTML:''}),o=t.draw.node("DIV","ce-toolbar__remove-confirmation",{}),r=t.draw.node("DIV","ce-toolbar__remove-confirm",{textContent:"Удалить блок"}),i=t.draw.node("DIV","ce-toolbar__remove-cancel",{textContent:"Отмена"});return t.listeners.add(n,"click",t.toolbar.settings.removeButtonClicked,!1),t.listeners.add(r,"click",t.toolbar.settings.confirmRemovingRequest,!1),t.listeners.add(i,"click",t.toolbar.settings.cancelRemovingRequest,!1),o.appendChild(r),o.appendChild(i),e.appendChild(n),e.appendChild(o),t.toolbar.settings.setting=n,t.toolbar.settings.actions=o,e},e.removeButtonClicked=function(){var e=t.toolbar.settings.actions;e.classList.contains("opened")?t.toolbar.settings.hideRemoveActions():t.toolbar.settings.showRemoveActions(),t.toolbar.toolbox.close(),t.toolbar.settings.close()},e.cancelRemovingRequest=function(){t.toolbar.settings.actions.classList.remove("opened")},e.confirmRemovingRequest=function(){var e,n=t.content.currentNode;n.remove(),e=t.nodes.redactor.childNodes.length,0===e&&(t.content.currentNode=null,t.ui.addInitialBlock()),t.ui.saveInputs(),t.toolbar.close()},e.showRemoveActions=function(){t.toolbar.settings.actions.classList.add("opened")},e.hideRemoveActions=function(){t.toolbar.settings.actions.classList.remove("opened")},e}({})},function(e,t){"use strict";e.exports=function(e){var t=codex.editor;e.buttonsOpened=null,e.actionsOpened=null,e.wrappersOffset=null,e.storedSelection=null,e.show=function(){var n,o=t.content.currentNode,r=o.dataset.tool;if(n=t.tools[r],n.showInlineToolbar){var i=e.getSelectionText(),a=t.nodes.inlineToolbar.wrapper;i.length>0&&(t.toolbar.inline.move(),a.classList.add("opened"),t.toolbar.inline.showButtons())}},e.close=function(){var e=t.nodes.inlineToolbar.wrapper;e.classList.remove("opened")},e.move=function(){this.wrappersOffset||(this.wrappersOffset=this.getWrappersOffset());var e,n,o=this.getSelectionCoords(),r=0,i=t.nodes.inlineToolbar.wrapper;0===i.offsetHeight&&(r=40),e=o.x-this.wrappersOffset.left,n=o.y+window.scrollY-this.wrappersOffset.top-r-i.offsetHeight,i.style.transform="translate3D("+Math.floor(e)+"px, "+Math.floor(n)+"px, 0)",t.toolbar.inline.closeButtons(),t.toolbar.inline.closeAction()},e.toolClicked=function(e,n){switch(n){case"createLink":t.toolbar.inline.createLinkAction(e,n);break;default:t.toolbar.inline.defaultToolAction(n)}t.nodes.inlineToolbar.buttons.childNodes.forEach(t.toolbar.inline.hightlight)},e.getWrappersOffset=function(){var e=t.nodes.wrapper,n=this.getOffset(e);return this.wrappersOffset=n,n},e.getOffset=function(e){for(var t=0,n=0;e&&!isNaN(e.offsetLeft)&&!isNaN(e.offsetTop);)t+=e.offsetLeft+e.clientLeft,n+=e.offsetTop+e.clientTop,e=e.offsetParent;return{top:n,left:t}},e.getSelectionCoords=function(){var e,t=document.selection,n=0,o=0;if(t)"Control"!=t.type&&(e=t.createRange(),e.collapse(!0),n=e.boundingLeft,o=e.boundingTop);else if(window.getSelection&&(t=window.getSelection(),t.rangeCount&&(e=t.getRangeAt(0).cloneRange(),e.getClientRects))){e.collapse(!0);var r=e.getClientRects()[0];if(!r)return;n=r.left,o=r.top}return{x:n,y:o}},e.getSelectionText=function(){var e="";return window.getSelection&&(e=window.getSelection().toString()),e},e.showButtons=function(){var e=t.nodes.inlineToolbar.buttons;e.classList.add("opened"),t.toolbar.inline.buttonsOpened=!0,t.nodes.inlineToolbar.buttons.childNodes.forEach(t.toolbar.inline.hightlight)},e.closeButtons=function(){var e=t.nodes.inlineToolbar.buttons;e.classList.remove("opened"),t.toolbar.inline.buttonsOpened=!1},e.showActions=function(){var e=t.nodes.inlineToolbar.actions;e.classList.add("opened"),t.toolbar.inline.actionsOpened=!0},e.closeAction=function(){var e=t.nodes.inlineToolbar.actions;e.innerHTML="",e.classList.remove("opened"),t.toolbar.inline.actionsOpened=!1};var n=function(e){if(e.keyCode==t.core.keys.ENTER){var n=t.content.currentNode,o=t.toolbar.inline.storedSelection;t.toolbar.inline.restoreSelection(n,o),t.toolbar.inline.setAnchor(this.value),e.preventDefault(),e.stopImmediatePropagation(),t.toolbar.inline.clearRange()}};return e.createLinkAction=function(e){var o=this.isLinkActive(),r=t.content.currentNode,i=t.toolbar.inline.saveSelection(r);if(t.toolbar.inline.storedSelection=i,o)t.toolbar.inline.restoreSelection(r,i),t.toolbar.inline.defaultToolAction("unlink");else{var a=t.draw.inputForLink();t.nodes.inlineToolbar.actions.appendChild(a),t.toolbar.inline.closeButtons(),t.toolbar.inline.showActions(),a.focus(),e.preventDefault(),t.listeners.add(a,"keydown",n,!1)}},e.isLinkActive=function(){var e=!1;return t.nodes.inlineToolbar.buttons.childNodes.forEach(function(t){var n=t.dataset.type;"link"==n&&t.classList.contains("hightlighted")&&(e=!0)}),e},e.defaultToolAction=function(e){document.execCommand(e,!1,null)},e.setAnchor=function(e){document.execCommand("createLink",!1,e),t.toolbar.inline.closeAction()},e.saveSelection=function(e){var t,n=window.getSelection().getRangeAt(0),o=n.cloneRange();return o.selectNodeContents(e),o.setEnd(n.startContainer,n.startOffset),t=o.toString().length,{start:t,end:t+n.toString().length}},e.restoreSelection=function(e,t){var n=document.createRange(),o=0;n.setStart(e,0),n.collapse(!0);for(var r,i,a=[e],s=!1,c=!1;!c&&(r=a.pop());)if(3==r.nodeType)i=o+r.length,!s&&t.start>=o&&t.start<=i&&(n.setStart(r,t.start-o),s=!0),s&&t.end>=o&&t.end<=i&&(n.setEnd(r,t.end-o),c=!0),o=i;else for(var l=r.childNodes.length;l--;)a.push(r.childNodes[l]);var d=window.getSelection();d.removeAllRanges(),d.addRange(n)},e.clearRange=function(){var e=window.getSelection();e.removeAllRanges()},e.hightlight=function(e){var n=e.dataset.type;document.queryCommandState(n)?t.toolbar.inline.setButtonHighlighted(e):t.toolbar.inline.removeButtonsHighLight(e);var o=window.getSelection(),r=o.anchorNode.parentNode;"A"==r.tagName&&"link"==n&&t.toolbar.inline.setButtonHighlighted(e)},e.setButtonHighlighted=function(e){if(e.classList.add("hightlighted"),"link"==e.dataset.type){var t=e.childNodes[0];t.classList.remove("ce-icon-link"),t.classList.add("ce-icon-unlink")}},e.removeButtonsHighLight=function(e){if(e.classList.remove("hightlighted"),"link"==e.dataset.type){var t=e.childNodes[0];t.classList.remove("ce-icon-unlink"),t.classList.add("ce-icon-link")}},e}({})},function(e,t){"use strict";e.exports=function(e){var t=codex.editor;return e.opened=!1,e.open=function(){t.toolbar.settings.opened&&t.toolbar.settings.close(),t.nodes.toolbox.classList.add("opened"),t.nodes.plusButton.classList.add("clicked"),t.toolbar.toolbox.opened=!0},e.close=function(){t.nodes.toolbox.classList.remove("opened"),t.nodes.plusButton.classList.remove("clicked"),t.toolbar.toolbox.opened=!1,t.toolbar.current=null},e.leaf=function(){var e=t.toolbar.current,n=Object.keys(t.tools),o=t.nodes.toolbarButtons,r=0,i=void 0,a=void 0,s=void 0;if(e)for(r=(n.indexOf(e)+1)%n.length,a=n[r];!t.tools[a].displayInToolbox;)r=(r+1)%n.length,a=n[r];else for(s in t.tools){if(t.tools[s].displayInToolbox)break;r++}i=n[r];for(var c in o)o[c].classList.remove("selected");o[i].classList.add("selected"),t.toolbar.current=i},e.toolClicked=function(e){var n,o,r,i=["image","link","list","instagram","twitter","embed"],a=t.tools[t.toolbar.current],s=t.content.currentNode,c=t.caret.inputIndex;n=a.render(),r={block:n,type:a.type,stretched:!1},s&&i.indexOf(s.dataset.tool)===-1&&""===s.textContent.trim()?t.content.switchBlock(s,n,a.type):(t.content.insertBlock(r),c++),o=a.appendCallback,o&&"function"==typeof o&&o.call(e),window.setTimeout(function(){t.caret.setToBlock(c)},10),t.content.workingNodeChanged(),t.toolbar.move()},e}({})},function(e,t){"use strict";e.exports=function(e){var t=codex.editor;e.globalKeydown=function(e){switch(e.keyCode){case t.core.keys.ENTER:o(e)}},e.redactorKeyDown=function(e){switch(e.keyCode){case t.core.keys.TAB:n(e);break;case t.core.keys.ENTER:i(e);break;case t.core.keys.ESC:a(e);break;default:c(e)}},e.globalKeyup=function(e){switch(e.keyCode){case t.core.keys.UP:case t.core.keys.LEFT:case t.core.keys.RIGHT:case t.core.keys.DOWN:s(e)}};var n=function(e){e.preventDefault(),t.core.isBlockEmpty(t.content.currentNode)&&(t.toolbar.opened||t.toolbar.open(),t.toolbar.opened&&!t.toolbar.toolbox.opened?t.toolbar.toolbox.open():t.toolbar.toolbox.leaf())},o=function(){t.content.editorAreaHightlighted&&(t.caret.inputIndex=-1,r())},r=function(){var e=t.settings.initialBlockPlugin;t.content.insertBlock({type:e,block:t.tools[e].render()},!0),t.toolbar.move(),t.toolbar.open()},i=function(e){"true"==e.target.contentEditable&&t.caret.saveCurrentInputIndex();var n=t.caret.getCurrentInputIndex()||0,o=t.content.currentNode,r=o.dataset.tool,i=t.toolbar.opened&&t.toolbar.current&&e.target==t.state.inputs[n],a=t.tools[r].enableLineBreaks,s=t.settings.initialBlockPlugin;if(i)return e.preventDefault(),t.toolbar.toolbox.toolClicked(e),t.toolbar.close(),e.stopPropagation(),void e.stopImmediatePropagation();if(e.shiftKey||a)return e.stopPropagation(),void e.stopImmediatePropagation();var c=window.getSelection(),l=c.anchorNode,d=t.caret.position.atTheEnd(),u=!1;if(e.shiftKey&&!a)return t.callback.enterPressedOnBlock(t.content.currentBlock,e),void e.preventDefault();if(u=l&&"true"!=l.parentNode.contentEditable,l.nodeType!=t.core.nodeTypes.TEXT||u||d){var p=t.content.isLastNode(l);p&&d&&(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),t.core.log("ENTER clicked in last textNode. Create new BLOCK"),t.content.insertBlock({type:s,block:t.tools[s].render()},!0),t.toolbar.move(),t.toolbar.open(),t.toolbar.showPlusButton())}else e.preventDefault(),t.core.log("Splitting Text node..."),t.content.splitBlock(n),t.state.inputs[n+1].textContent.trim()||t.toolbar.showPlusButton();t.ui.saveInputs()},a=function(e){t.toolbar.close(),t.toolbar.toolbox.close(),e.preventDefault()},s=function(e){t.content.workingNodeChanged(),t.toolbar.close(),t.toolbar.move()},c=function(){t.toolbar.close(),t.toolbar.inline.actionsOpened||(t.toolbar.inline.close(),t.content.clearMark())};e.redactorClicked=function(e){l(),t.content.workingNodeChanged(e.target),t.ui.saveInputs();var n,o=t.toolbar.inline.getSelectionText();if(0===o.length&&t.toolbar.inline.close(),"true"==e.target.contentEditable&&t.caret.saveCurrentInputIndex(),null===t.content.currentNode){var r=t.state.inputs.length>0?t.state.inputs.length-1:0;if(t.state.inputs.length&&(n=t.content.getFirstLevelBlock(t.state.inputs[r])),t.state.inputs.length&&""===t.state.inputs[r].textContent&&n.dataset.tool==t.settings.initialBlockPlugin)t.caret.setToBlock(r);else{var i=t.settings.initialBlockPlugin;t.content.insertBlock({type:i,block:t.tools[i].render()}),1===t.state.inputs.length?t.caret.setToBlock(r):t.caret.setToNextBlock(r)}}else t.toolbar.settings.close(),t.toolbar.toolbox.close();t.toolbar.move(),t.toolbar.open();var a=!t.content.currentNode.textContent.trim(),s=t.content.currentNode.dataset.tool,c=s==t.settings.initialBlockPlugin;t.toolbar.hidePlusButton(),a||t.content.markBlock(),c&&a&&t.toolbar.showPlusButton()};var l=function(){var e=window.getSelection(),n=e.anchorNode,o=!1;if(0===e.rangeCount)t.content.editorAreaHightlighted=!0;else{for(t.core.isDomNode(n)||(n=n.parentNode),"true"==n.contentEditable&&(o=!0);"true"!=n.contentEditable&&(n=n.parentNode,"true"==n.contentEditable&&(o=!0),n!=document.body););t.content.editorAreaHightlighted=!o}};e.toolbarButtonClicked=function(e){var n=this;t.toolbar.current=n.dataset.type,t.toolbar.toolbox.toolClicked(e),t.toolbar.close()},e.plusButtonClicked=function(){t.nodes.toolbox.classList.contains("opened")?t.toolbar.toolbox.close():t.toolbar.toolbox.open()},e.blockKeydown=function(e){var n=e.target;switch(e.keyCode){case t.core.keys.DOWN:case t.core.keys.RIGHT:d(e);break;case t.core.keys.BACKSPACE:p(n,e);break;case t.core.keys.UP:case t.core.keys.LEFT:u(e)}};var d=function(e){var n,o=window.getSelection(),r=t.state.inputs,i=o.anchorNode;if(!i)return!1;for(;"true"!=i.contentEditable;)n=i.parentNode,i=n;for(var a=0;i!=r[a];)a++;if(!i.textContent)return void t.caret.setToNextBlock(a);var s,c,l=!1,d=!1;return s=i.childNodes[i.childNodes.length-1],c=t.core.isDomNode(s)?t.content.getDeepestTextNodeFromPosition(s,s.childNodes.length):s,l=o.anchorNode==c,d=c.length==o.anchorOffset,l&&d?void t.caret.setToNextBlock(a):(t.core.log("arrow [down|right] : caret does not reached the end"),!1)},u=function(e){var n,o=window.getSelection(),r=t.state.inputs,i=o.anchorNode;if(!i)return!1;if(0!==o.anchorOffset)return!1;for(;"true"!=i.contentEditable;)n=i.parentNode,i=n;for(var a=0;i!=r[a];)a++;var s,c,l=!1,d=!1;return i.textContent?(s=i.childNodes[0],c=t.core.isDomNode(s)?t.content.getDeepestTextNodeFromPosition(s,0):s,l=o.anchorNode==c,d=0===o.anchorOffset,void(l&&d&&t.caret.setToPreviousBlock(a))):void t.caret.setToPreviousBlock(a)},p=function(e,n){var o,r,i,a=t.caret.getCurrentInputIndex();if(t.core.isNativeInput(n.target)){if(""!=n.target.value.trim())return;e.remove()}if(e.textContent.trim()){if(o=t.content.getRange(),r=o.endOffset-o.startOffset,!t.caret.position.atStart()||r||!t.state.inputs[a-1])return;t.content.mergeBlocks(a)}r||e.remove(), +i=t.nodes.redactor.childNodes.length,0===i?(t.content.currentNode=null,t.ui.addInitialBlock(),t.ui.saveInputs(),window.setTimeout(function(){t.caret.setToPreviousBlock(1)},10)):0!==t.caret.inputIndex?t.caret.setToPreviousBlock(t.caret.inputIndex):t.caret.setToNextBlock(t.caret.inputIndex),t.toolbar.move(),t.toolbar.opened||t.toolbar.open(),t.ui.saveInputs(),n.preventDefault()};return e.showSettingsButtonClicked=function(e){var n=t.content.currentNode.dataset.tool;t.toolbar.settings.toggle(n),t.toolbar.toolbox.close(),t.toolbar.settings.hideRemoveActions()},e}({})},function(e,t){"use strict";e.exports=function(e){return e.wrapper=function(){var e=document.createElement("div");return e.className+="codex-editor",e},e.redactor=function(){var e=document.createElement("div");return e.className+="ce-redactor",e},e.ceBlock=function(){var e=document.createElement("DIV");return e.className+="ce_block",e},e.toolbar=function(){var e=document.createElement("div");return e.className+="ce-toolbar",e},e.toolbarContent=function(){var e=document.createElement("DIV");return e.classList.add("ce-toolbar__content"),e},e.inlineToolbar=function(){var e=document.createElement("DIV");return e.className+="ce-toolbar-inline",e},e.inlineToolbarButtons=function(){var e=document.createElement("DIV");return e.className+="ce-toolbar-inline__buttons",e},e.inlineToolbarActions=function(){var e=document.createElement("DIV");return e.className+="ce-toolbar-inline__actions",e},e.inputForLink=function(){var e=document.createElement("INPUT");return e.type="input",e.className+="inputForLink",e.placeholder="Вставьте ссылку ...",e.setAttribute("form","defaultForm"),e.setAttribute("autofocus","autofocus"),e},e.blockButtons=function(){var e=document.createElement("div");return e.className+="ce-toolbar__actions",e},e.blockSettings=function(){var e=document.createElement("div");return e.className+="ce-settings",e},e.defaultSettings=function(){var e=document.createElement("div");return e.classList.add("ce-settings_default"),e},e.pluginsSettings=function(){var e=document.createElement("div");return e.classList.add("ce-settings_plugin"),e},e.plusButton=function(){var e=document.createElement("span");return e.className="ce-toolbar__plus",e},e.settingsButton=function(){var e=document.createElement("span");return e.className="ce-toolbar__settings-btn",e.innerHTML='',e},e.toolbox=function(){var e=document.createElement("div");return e.className="ce-toolbar__tools",e},e.toolbarButton=function(e,t){var n=document.createElement("li"),o=document.createElement("i"),r=document.createElement("span");return n.dataset.type=e,n.setAttribute("title",e),o.classList.add(t),r.classList.add("ce_toolbar_tools--title"),n.appendChild(o),n.appendChild(r),n},e.toolbarButtonInline=function(e,t){var n=document.createElement("BUTTON"),o=document.createElement("I");return n.type="button",n.dataset.type=e,o.classList.add(t),n.appendChild(o),n},e.block=function(e,t){var n=document.createElement(e);return n.innerHTML=t||"",n},e.node=function(e,t,n){var o=document.createElement(e);if(t&&(o.className=t),n)for(var r in n)o[r]=n[r];return o},e.unavailableBlock=function(){var e=document.createElement("DIV");return e.classList.add("cdx-unavailable-block"),e},e}({})},function(e,t){"use strict";e.exports=function(e){var t=codex.editor;return e.inputIndex=null,e.offset=null,e.focusedNodeIndex=null,e.set=function(n,o,r){r=r||e.offset||0,o=o||e.focusedNodeIndex||0;var i,a=n.childNodes;if(i=0===a.length?n:a[o],"true"!=n.contentEditable)return void n.focus();t.core.isDomNode(i)&&(i=t.content.getDeepestTextNodeFromPosition(i,i.childNodes.length));var s=document.createRange(),c=window.getSelection();window.setTimeout(function(){s.setStart(i,r),s.setEnd(i,r),c.removeAllRanges(),c.addRange(s),t.caret.saveCurrentInputIndex()},20)},e.saveCurrentInputIndex=function(){var n,o=window.getSelection(),r=t.state.inputs,i=o.anchorNode;if(i){for(;"true"!=i.contentEditable;)n=i.parentNode,i=n;for(var a=0;i!=r[a];)a++;e.inputIndex=a}},e.getCurrentInputIndex=function(){return e.inputIndex},e.setToNextBlock=function(e){var n=t.state.inputs,o=n[e+1];if(!o)return void t.core.log("We are reached the end");if(!o.childNodes.length){var r=document.createTextNode("");o.appendChild(r)}t.caret.inputIndex=e+1,t.caret.set(o,0,0),t.content.workingNodeChanged(o)},e.setToBlock=function(e){var n=t.state.inputs,o=n[e];if(o){if(!o.childNodes.length){var r=document.createTextNode("");o.appendChild(r)}t.caret.inputIndex=e,t.caret.set(o,0,0),t.content.workingNodeChanged(o)}},e.setToPreviousBlock=function(e){e=e||0;var n,o,r,i=t.state.inputs,a=i[e-1];return a?(n=t.content.getDeepestTextNodeFromPosition(a,a.childNodes.length),o=n.length,a.childNodes.length||(r=document.createTextNode(""),a.appendChild(r)),t.caret.inputIndex=e-1,t.caret.set(a,a.childNodes.length-1,o),void t.content.workingNodeChanged(i[e-1])):void t.core.log("We are reached first node")},e.position={atStart:function(){var e=window.getSelection(),n=e.anchorOffset,o=e.anchorNode,r=t.content.getFirstLevelBlock(o),i=r.childNodes[0];t.core.isDomNode(o)||(o=o.parentNode);var a=o===i.childNodes[0],s=0===n;return a&&s},atTheEnd:function(){var e=window.getSelection(),t=e.anchorOffset,n=e.anchorNode;return!n||!n.length||t===n.length}},e.insertNode=function(e){var n,o,r=e;e.nodeType==t.core.nodeTypes.DOCUMENT_FRAGMENT&&(r=e.lastChild),n=window.getSelection(),o=n.getRangeAt(0),o.deleteContents(),o.insertNode(e),o.setStartAfter(r),o.collapse(!0),n.removeAllRanges(),n.addRange(o)},e}({})},function(e,t){"use strict";e.exports=function(e){var t=codex.editor,n=[],o=function(e){n.push(e);for(var t=0;t5;)"confirm"!=n[t].type&&"prompt"!=n[t].type?(n[t].close(),n.splice(t,1)):t++};return e.createHolder=function(){var e=t.draw.node("DIV","cdx-notifications-block");return t.nodes.notifications=document.body.appendChild(e),e},e.errorThrown=function(e,n){t.notifications.notification({message:"This action is not available currently",type:n.type})},e.notification=function(e){function n(e){if(!e||!e.message)return void t.core.log("Can't create notification. Message is missed");e.type=e.type||"alert",e.time=1e3*e.time||1e4;var n=t.draw.node("DIV","cdx-notification"),o=t.draw.node("DIV","cdx-notification__message"),r=t.draw.node("INPUT","cdx-notification__input"),f=t.draw.node("SPAN","cdx-notification__ok-btn"),g=t.draw.node("SPAN","cdx-notification__cancel-btn");o.textContent=e.message,f.textContent=e.okMsg||"ОК",g.textContent=e.cancelMsg||"Отмена",t.listeners.add(f,"click",u),t.listeners.add(g,"click",p),n.appendChild(o),"prompt"==e.type&&n.appendChild(r),n.appendChild(f),"prompt"!=e.type&&"confirm"!=e.type||n.appendChild(g),n.classList.add("cdx-notification-"+e.type),n.dataset.type=e.type,a=n,c=e.type,l=e.confirm,s=e.cancel,d=r,"prompt"!=e.type&&"confirm"!=e.type&&window.setTimeout(i,e.time)}function r(){t.nodes.notifications.appendChild(a),d.focus(),t.nodes.notifications.classList.add("cdx-notification__notification-appending"),window.setTimeout(function(){t.nodes.notifications.classList.remove("cdx-notification__notification-appending")},100),o({type:c,close:i})}function i(){a.remove()}var a=null,s=null,c=null,l=null,d=null,u=function(){if(i(),"function"==typeof l)return"prompt"==c?void l(d.value):void l()},p=function(){i(),"function"==typeof s&&s()};return e&&(n(e),r()),{create:n,send:r,close:i}},e.clear=function(){t.nodes.notifications.innerHTML="",n=[]},e}({})},function(e,t){"use strict";e.exports=function(e){var t=codex.editor;return e.insertPastedContent=function(e,n){t.content.insertBlock({type:e.type,block:e.render({text:n.innerHTML})})},e.isFirstLevelBlock=function(e){return e.nodeType==t.core.nodeTypes.TAG&&e.classList.contains(t.ui.className.BLOCK_CLASSNAME)},e}({})},function(e,t,n){"use strict";e.exports=function(e){var t=n(18),o=codex.editor;e.prepare=function(){o.settings.sanitizer&&!o.core.isEmpty(o.settings.sanitizer)&&(r.CUSTOM=o.settings.sanitizer)};var r={CUSTOM:null,BASIC:{tags:{p:{},a:{href:!0,target:"_blank",rel:"nofollow"},i:{},b:{},strong:{},em:{},span:{}}}};e.Config=r;var i=function(e){var n=e||r.CUSTOM||r.BASIC;return new t(n)};return e.clean=function(e,t){var n=i(t);return n.clean(e)},e}({})},function(e,t,n){var o,r;!function(i,a){o=a,r="function"==typeof o?o.call(t,n,t,e):o,!(void 0!==r&&(e.exports=r))}(this,function(){function e(e){var t=e.tags,n=Object.keys(t),o=n.map(function(e){return typeof t[e]}).every(function(e){return"object"===e||"boolean"===e||"function"===e});if(!o)throw new Error("The configuration was invalid");this.config=e}function t(e){return s.indexOf(e.nodeName)!==-1}function n(e){return c.indexOf(e.nodeName)!==-1}function o(e){return document.createTreeWalker(e,NodeFilter.SHOW_TEXT|NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_COMMENT,null,!1)}function r(e,t,n){return"function"==typeof e.tags[t]?e.tags[t](n):e.tags[t]}function i(e,t){return"undefined"==typeof t||"boolean"==typeof t&&!t}function a(e,t,n){var o=e.name.toLowerCase();return t!==!0&&("function"==typeof t[o]?!t[o](e.value,n):"undefined"==typeof t[o]||(t[o]===!1||"string"==typeof t[o]&&t[o]!==e.value))}var s=["P","LI","TD","TH","DIV","H1","H2","H3","H4","H5","H6","PRE"],c=["A","B","STRONG","I","EM","SUB","SUP","U","STRIKE"];return e.prototype.clean=function(e){var t=document.createElement("div");return t.innerHTML=e,this._sanitize(t),t.innerHTML},e.prototype._sanitize=function(e){var s=o(e),c=s.firstChild();if(c)do if(!c._sanitized)if(c.nodeType!==Node.TEXT_NODE){if(c.nodeType===Node.COMMENT_NODE){e.removeChild(c),this._sanitize(e);break}var l,d=n(c);d&&(l=Array.prototype.some.call(c.childNodes,t));var u=!!e.parentNode,p=t(e)&&t(c)&&u,f=c.nodeName.toLowerCase(),g=r(this.config,f,c),h=d&&l;if(h||i(c,g)||!this.config.keepNestedBlockElements&&p){if("SCRIPT"!==c.nodeName&&"STYLE"!==c.nodeName)for(;c.childNodes.length>0;)e.insertBefore(c.childNodes[0],c);e.removeChild(c),this._sanitize(e);break}for(var v=0;v0&&t.splice(s,1)}},e.removeAll=function(){t.map(function(t){e.remove(t.element,t.type,t.handler)})},e.get=function(t,n,o){return e.search.all(t,n,o)},e}({})},function(e,t){"use strict";var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};e.exports=function(e){var t=codex.editor;return e.removeNodes=function(){t.nodes.wrapper.remove(),t.nodes.notifications.remove()},e.destroyPlugins=function(){for(var e in t.tools)"function"==typeof t.tools[e].destroy&&t.tools[e].destroy()},e.destroyScripts=function(){for(var e=document.getElementsByTagName("SCRIPT"),n=0;n nodeList\r\n\t */\n\t renderer.appendBlocks = function (data) {\n\t\n\t var blocks = data.items;\n\t\n\t /**\r\n\t * Sequence of one-by-one blocks appending\r\n\t * Uses to save blocks order after async-handler\r\n\t */\n\t var nodeSequence = Promise.resolve();\n\t\n\t for (var index = 0; index < blocks.length; index++) {\n\t\n\t /** Add node to sequence at specified index */\n\t editor.renderer.appendNodeAtIndex(nodeSequence, blocks, index);\n\t }\n\t };\n\t\n\t /**\r\n\t * Append node at specified index\r\n\t */\n\t renderer.appendNodeAtIndex = function (nodeSequence, blocks, index) {\n\t\n\t /** We need to append node to sequence */\n\t nodeSequence\n\t\n\t /** first, get node async-aware */\n\t .then(function () {\n\t\n\t return editor.renderer.getNodeAsync(blocks, index);\n\t })\n\t\n\t /**\r\n\t * second, compose editor-block from JSON object\r\n\t */\n\t .then(editor.renderer.createBlockFromData)\n\t\n\t /**\r\n\t * now insert block to redactor\r\n\t */\n\t .then(function (blockData) {\n\t\n\t /**\r\n\t * blockData has 'block', 'type' and 'stretched' information\r\n\t */\n\t editor.content.insertBlock(blockData);\n\t\n\t /** Pass created block to next step */\n\t return blockData.block;\n\t })\n\t\n\t /** Log if something wrong with node */\n\t .catch(function (error) {\n\t\n\t editor.core.log('Node skipped while parsing because %o', 'error', error);\n\t });\n\t };\n\t\n\t /**\r\n\t * Asynchronously returns block data from blocksList by index\r\n\t * @return Promise to node\r\n\t */\n\t renderer.getNodeAsync = function (blocksList, index) {\n\t\n\t return Promise.resolve().then(function () {\n\t\n\t return {\n\t tool: blocksList[index],\n\t position: index\n\t };\n\t });\n\t };\n\t\n\t /**\r\n\t * Creates editor block by JSON-data\r\n\t *\r\n\t * @uses render method of each plugin\r\n\t *\r\n\t * @param {Object} toolData.tool\r\n\t * { header : {\r\n\t * text: '',\r\n\t * type: 'H3', ...\r\n\t * }\r\n\t * }\r\n\t * @param {Number} toolData.position - index in input-blocks array\r\n\t * @return {Object} with type and Element\r\n\t */\n\t renderer.createBlockFromData = function (toolData) {\n\t\n\t /** New parser */\n\t var block,\n\t tool = toolData.tool,\n\t pluginName = tool.type;\n\t\n\t /** Get first key of object that stores plugin name */\n\t // for (var pluginName in blockData) break;\n\t\n\t /** Check for plugin existance */\n\t if (!editor.tools[pluginName]) {\n\t\n\t throw Error('Plugin \\xAB' + pluginName + '\\xBB not found');\n\t }\n\t\n\t /** Check for plugin having render method */\n\t if (typeof editor.tools[pluginName].render != 'function') {\n\t\n\t throw Error('Plugin \\xAB' + pluginName + '\\xBB must have \\xABrender\\xBB method');\n\t }\n\t\n\t if (editor.tools[pluginName].available === false) {\n\t\n\t block = editor.draw.unavailableBlock();\n\t\n\t block.innerHTML = editor.tools[pluginName].loadingMessage;\n\t\n\t /**\r\n\t * Saver will extract data from initial block data by position in array\r\n\t */\n\t block.dataset.inputPosition = toolData.position;\n\t } else {\n\t\n\t /** New Parser */\n\t block = editor.tools[pluginName].render(tool.data);\n\t }\n\t\n\t /** is first-level block stretched */\n\t var stretched = editor.tools[pluginName].isStretched || false;\n\t\n\t /** Retrun type and block */\n\t return {\n\t type: pluginName,\n\t block: block,\n\t stretched: stretched\n\t };\n\t };\n\t\n\t return renderer;\n\t}({});\n\n/***/ },\n/* 6 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor Saver\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0.2\r\n\t */\n\t\n\tmodule.exports = function (saver) {\n\t\n\t var editor = codex.editor;\n\t\n\t /**\r\n\t * Saves blocks\r\n\t * @private\r\n\t */\n\t saver.saveBlocks = function () {\n\t\n\t /** Save html content of redactor to memory */\n\t editor.state.html = editor.nodes.redactor.innerHTML;\n\t\n\t /** Empty jsonOutput state */\n\t editor.state.jsonOutput = [];\n\t\n\t Promise.resolve().then(function () {\n\t\n\t return editor.nodes.redactor.childNodes;\n\t })\n\t /** Making a sequence from separate blocks */\n\t .then(editor.saver.makeQueue).then(function () {\n\t // editor.nodes.textarea.innerHTML = editor.state.html;\n\t }).catch(function (error) {\n\t\n\t editor.core.log(error);\n\t });\n\t };\n\t\n\t saver.makeQueue = function (blocks) {\n\t\n\t var queue = Promise.resolve();\n\t\n\t for (var index = 0; index < blocks.length; index++) {\n\t\n\t /** Add node to sequence at specified index */\n\t editor.saver.getBlockData(queue, blocks, index);\n\t }\n\t };\n\t\n\t /** Gets every block and makes From Data */\n\t saver.getBlockData = function (queue, blocks, index) {\n\t\n\t queue.then(function () {\n\t\n\t return editor.saver.getNodeAsync(blocks, index);\n\t }).then(editor.saver.makeFormDataFromBlocks);\n\t };\n\t\n\t /**\r\n\t * Asynchronously returns block data from blocksList by index\r\n\t * @return Promise to node\r\n\t */\n\t saver.getNodeAsync = function (blocksList, index) {\n\t\n\t return Promise.resolve().then(function () {\n\t\n\t return blocksList[index];\n\t });\n\t };\n\t\n\t saver.makeFormDataFromBlocks = function (block) {\n\t\n\t var pluginName = block.dataset.tool;\n\t\n\t /** Check for plugin existance */\n\t if (!editor.tools[pluginName]) {\n\t\n\t throw Error('Plugin \\xAB' + pluginName + '\\xBB not found');\n\t }\n\t\n\t /** Check for plugin having render method */\n\t if (typeof editor.tools[pluginName].save != 'function') {\n\t\n\t throw Error('Plugin \\xAB' + pluginName + '\\xBB must have save method');\n\t }\n\t\n\t /** Result saver */\n\t var blockContent = block.childNodes[0],\n\t pluginsContent = blockContent.childNodes[0],\n\t savedData,\n\t position,\n\t output;\n\t\n\t /** If plugin wasn't available then return data from cache */\n\t if (editor.tools[pluginName].available === false) {\n\t\n\t position = pluginsContent.dataset.inputPosition;\n\t savedData = codex.editor.state.blocks.items[position].data;\n\t } else {\n\t\n\t savedData = editor.tools[pluginName].save(pluginsContent);\n\t\n\t if (editor.tools[pluginName].validate) {\n\t\n\t var result = editor.tools[pluginName].validate(savedData);\n\t\n\t /**\r\n\t * Do not allow invalid data\r\n\t */\n\t if (!result) return;\n\t }\n\t }\n\t\n\t output = {\n\t type: pluginName,\n\t data: savedData\n\t };\n\t\n\t editor.state.jsonOutput.push(output);\n\t };\n\t\n\t return saver;\n\t}({});\n\n/***/ },\n/* 7 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor Content Module\r\n\t * Works with DOM\r\n\t *\r\n\t * @module Codex Editor content module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.3.13\r\n\t *\r\n\t * @description Module works with Elements that have been appended to the main DOM\r\n\t */\n\t\n\tmodule.exports = function (content) {\n\t\n\t var editor = codex.editor;\n\t\n\t /**\r\n\t * Links to current active block\r\n\t * @type {null | Element}\r\n\t */\n\t content.currentNode = null;\n\t\n\t /**\r\n\t * clicked in redactor area\r\n\t * @type {null | Boolean}\r\n\t */\n\t content.editorAreaHightlighted = null;\n\t\n\t /**\r\n\t * @deprecated\r\n\t * Synchronizes redactor with original textarea\r\n\t */\n\t content.sync = function () {\n\t\n\t editor.core.log('syncing...');\n\t\n\t /**\r\n\t * Save redactor content to editor.state\r\n\t */\n\t editor.state.html = editor.nodes.redactor.innerHTML;\n\t };\n\t\n\t /**\r\n\t * Appends background to the block\r\n\t *\r\n\t * @description add CSS class to highlight visually first-level block area\r\n\t */\n\t content.markBlock = function () {\n\t\n\t editor.content.currentNode.classList.add(editor.ui.className.BLOCK_HIGHLIGHTED);\n\t };\n\t\n\t /**\r\n\t * Clear background\r\n\t *\r\n\t * @description clears styles that highlights block\r\n\t */\n\t content.clearMark = function () {\n\t\n\t if (editor.content.currentNode) {\n\t\n\t editor.content.currentNode.classList.remove(editor.ui.className.BLOCK_HIGHLIGHTED);\n\t }\n\t };\n\t\n\t /**\r\n\t * Finds first-level block\r\n\t *\r\n\t * @param {Element} node - selected or clicked in redactors area node\r\n\t * @protected\r\n\t *\r\n\t * @description looks for first-level block.\r\n\t * gets parent while node is not first-level\r\n\t */\n\t content.getFirstLevelBlock = function (node) {\n\t\n\t if (!editor.core.isDomNode(node)) {\n\t\n\t node = node.parentNode;\n\t }\n\t\n\t if (node === editor.nodes.redactor || node === document.body) {\n\t\n\t return null;\n\t } else {\n\t\n\t while (!node.classList.contains(editor.ui.className.BLOCK_CLASSNAME)) {\n\t\n\t node = node.parentNode;\n\t }\n\t\n\t return node;\n\t }\n\t };\n\t\n\t /**\r\n\t * Trigger this event when working node changed\r\n\t * @param {Element} targetNode - first-level of this node will be current\r\n\t * @protected\r\n\t *\r\n\t * @description If targetNode is first-level then we set it as current else we look for parents to find first-level\r\n\t */\n\t content.workingNodeChanged = function (targetNode) {\n\t\n\t /** Clear background from previous marked block before we change */\n\t editor.content.clearMark();\n\t\n\t if (!targetNode) {\n\t\n\t return;\n\t }\n\t\n\t content.currentNode = content.getFirstLevelBlock(targetNode);\n\t };\n\t\n\t /**\r\n\t * Replaces one redactor block with another\r\n\t * @protected\r\n\t * @param {Element} targetBlock - block to replace. Mostly currentNode.\r\n\t * @param {Element} newBlock\r\n\t * @param {string} newBlockType - type of new block; we need to store it to data-attribute\r\n\t *\r\n\t * [!] Function does not saves old block content.\r\n\t * You can get it manually and pass with newBlock.innerHTML\r\n\t */\n\t content.replaceBlock = function (targetBlock, newBlock) {\n\t\n\t if (!targetBlock || !newBlock) {\n\t\n\t editor.core.log('replaceBlock: missed params');\n\t return;\n\t }\n\t\n\t /** If target-block is not a frist-level block, then we iterate parents to find it */\n\t while (!targetBlock.classList.contains(editor.ui.className.BLOCK_CLASSNAME)) {\n\t\n\t targetBlock = targetBlock.parentNode;\n\t }\n\t\n\t /** Replacing */\n\t editor.nodes.redactor.replaceChild(newBlock, targetBlock);\n\t\n\t /**\r\n\t * Set new node as current\r\n\t */\n\t editor.content.workingNodeChanged(newBlock);\n\t\n\t /**\r\n\t * Add block handlers\r\n\t */\n\t editor.ui.addBlockHandlers(newBlock);\n\t\n\t /**\r\n\t * Save changes\r\n\t */\n\t editor.ui.saveInputs();\n\t };\n\t\n\t /**\r\n\t * @protected\r\n\t *\r\n\t * Inserts new block to redactor\r\n\t * Wrapps block into a DIV with BLOCK_CLASSNAME class\r\n\t *\r\n\t * @param blockData {object}\r\n\t * @param blockData.block {Element} element with block content\r\n\t * @param blockData.type {string} block plugin\r\n\t * @param needPlaceCaret {bool} pass true to set caret in new block\r\n\t *\r\n\t */\n\t content.insertBlock = function (blockData, needPlaceCaret) {\n\t\n\t var workingBlock = editor.content.currentNode,\n\t newBlockContent = blockData.block,\n\t blockType = blockData.type,\n\t isStretched = blockData.stretched;\n\t\n\t var newBlock = composeNewBlock_(newBlockContent, blockType, isStretched);\n\t\n\t if (workingBlock) {\n\t\n\t editor.core.insertAfter(workingBlock, newBlock);\n\t } else {\n\t\n\t /**\r\n\t * If redactor is empty, append as first child\r\n\t */\n\t editor.nodes.redactor.appendChild(newBlock);\n\t }\n\t\n\t /**\r\n\t * Block handler\r\n\t */\n\t editor.ui.addBlockHandlers(newBlock);\n\t\n\t /**\r\n\t * Set new node as current\r\n\t */\n\t editor.content.workingNodeChanged(newBlock);\n\t\n\t /**\r\n\t * Save changes\r\n\t */\n\t editor.ui.saveInputs();\n\t\n\t if (needPlaceCaret) {\n\t\n\t /**\r\n\t * If we don't know input index then we set default value -1\r\n\t */\n\t var currentInputIndex = editor.caret.getCurrentInputIndex() || -1;\n\t\n\t if (currentInputIndex == -1) {\n\t\n\t var editableElement = newBlock.querySelector('[contenteditable]'),\n\t emptyText = document.createTextNode('');\n\t\n\t editableElement.appendChild(emptyText);\n\t editor.caret.set(editableElement, 0, 0);\n\t\n\t editor.toolbar.move();\n\t editor.toolbar.showPlusButton();\n\t } else {\n\t\n\t if (currentInputIndex === editor.state.inputs.length - 1) return;\n\t\n\t /** Timeout for browsers execution */\n\t window.setTimeout(function () {\n\t\n\t /** Setting to the new input */\n\t editor.caret.setToNextBlock(currentInputIndex);\n\t editor.toolbar.move();\n\t editor.toolbar.open();\n\t }, 10);\n\t }\n\t }\n\t\n\t /**\r\n\t * Block is inserted, wait for new click that defined focusing on editors area\r\n\t * @type {boolean}\r\n\t */\n\t content.editorAreaHightlighted = false;\n\t };\n\t\n\t /**\r\n\t * Replaces blocks with saving content\r\n\t * @protected\r\n\t * @param {Element} noteToReplace\r\n\t * @param {Element} newNode\r\n\t * @param {Element} blockType\r\n\t */\n\t content.switchBlock = function (blockToReplace, newBlock, tool) {\n\t\n\t tool = tool || editor.content.currentNode.dataset.tool;\n\t var newBlockComposed = composeNewBlock_(newBlock, tool);\n\t\n\t /** Replacing */\n\t editor.content.replaceBlock(blockToReplace, newBlockComposed);\n\t\n\t /** Save new Inputs when block is changed */\n\t editor.ui.saveInputs();\n\t };\n\t\n\t /**\r\n\t * Iterates between child noted and looking for #text node on deepest level\r\n\t * @protected\r\n\t *\r\n\t * @param {Element} block - node where find\r\n\t * @param {int} postiton - starting postion\r\n\t * Example: childNodex.length to find from the end\r\n\t * or 0 to find from the start\r\n\t * @return {Text} block\r\n\t * @uses DFS\r\n\t */\n\t content.getDeepestTextNodeFromPosition = function (block, position) {\n\t\n\t /**\r\n\t * Clear Block from empty and useless spaces with trim.\r\n\t * Such nodes we should remove\r\n\t */\n\t var blockChilds = block.childNodes,\n\t index,\n\t node,\n\t text;\n\t\n\t for (index = 0; index < blockChilds.length; index++) {\n\t\n\t node = blockChilds[index];\n\t\n\t if (node.nodeType == editor.core.nodeTypes.TEXT) {\n\t\n\t text = node.textContent.trim();\n\t\n\t /** Text is empty. We should remove this child from node before we start DFS\r\n\t * decrease the quantity of childs.\r\n\t */\n\t if (text === '') {\n\t\n\t block.removeChild(node);\n\t position--;\n\t }\n\t }\n\t }\n\t\n\t if (block.childNodes.length === 0) {\n\t\n\t return document.createTextNode('');\n\t }\n\t\n\t /** Setting default position when we deleted all empty nodes */\n\t if (position < 0) position = 1;\n\t\n\t var lookingFromStart = false;\n\t\n\t /** For looking from START */\n\t if (position === 0) {\n\t\n\t lookingFromStart = true;\n\t position = 1;\n\t }\n\t\n\t while (position) {\n\t\n\t /** initial verticle of node. */\n\t if (lookingFromStart) {\n\t\n\t block = block.childNodes[0];\n\t } else {\n\t\n\t block = block.childNodes[position - 1];\n\t }\n\t\n\t if (block.nodeType == editor.core.nodeTypes.TAG) {\n\t\n\t position = block.childNodes.length;\n\t } else if (block.nodeType == editor.core.nodeTypes.TEXT) {\n\t\n\t position = 0;\n\t }\n\t }\n\t\n\t return block;\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t * @param {Element} block - current plugins render\r\n\t * @param {String} tool - plugins name\r\n\t * @param {Boolean} isStretched - make stretched block or not\r\n\t *\r\n\t * @description adds necessary information to wrap new created block by first-level holder\r\n\t */\n\t var composeNewBlock_ = function composeNewBlock_(block, tool, isStretched) {\n\t\n\t var newBlock = editor.draw.node('DIV', editor.ui.className.BLOCK_CLASSNAME, {}),\n\t blockContent = editor.draw.node('DIV', editor.ui.className.BLOCK_CONTENT, {});\n\t\n\t blockContent.appendChild(block);\n\t newBlock.appendChild(blockContent);\n\t\n\t if (isStretched) {\n\t\n\t blockContent.classList.add(editor.ui.className.BLOCK_STRETCHED);\n\t }\n\t\n\t newBlock.dataset.tool = tool;\n\t return newBlock;\n\t };\n\t\n\t /**\r\n\t * Returns Range object of current selection\r\n\t * @protected\r\n\t */\n\t content.getRange = function () {\n\t\n\t var selection = window.getSelection().getRangeAt(0);\n\t\n\t return selection;\n\t };\n\t\n\t /**\r\n\t * Divides block in two blocks (after and before caret)\r\n\t *\r\n\t * @protected\r\n\t * @param {int} inputIndex - target input index\r\n\t *\r\n\t * @description splits current input content to the separate blocks\r\n\t * When enter is pressed among the words, that text will be splited.\r\n\t */\n\t content.splitBlock = function (inputIndex) {\n\t\n\t var selection = window.getSelection(),\n\t anchorNode = selection.anchorNode,\n\t anchorNodeText = anchorNode.textContent,\n\t caretOffset = selection.anchorOffset,\n\t textBeforeCaret,\n\t textNodeBeforeCaret,\n\t textAfterCaret,\n\t textNodeAfterCaret;\n\t\n\t var currentBlock = editor.content.currentNode.querySelector('[contentEditable]');\n\t\n\t textBeforeCaret = anchorNodeText.substring(0, caretOffset);\n\t textAfterCaret = anchorNodeText.substring(caretOffset);\n\t\n\t textNodeBeforeCaret = document.createTextNode(textBeforeCaret);\n\t\n\t if (textAfterCaret) {\n\t\n\t textNodeAfterCaret = document.createTextNode(textAfterCaret);\n\t }\n\t\n\t var previousChilds = [],\n\t nextChilds = [],\n\t reachedCurrent = false;\n\t\n\t if (textNodeAfterCaret) {\n\t\n\t nextChilds.push(textNodeAfterCaret);\n\t }\n\t\n\t for (var i = 0, child; !!(child = currentBlock.childNodes[i]); i++) {\n\t\n\t if (child != anchorNode) {\n\t\n\t if (!reachedCurrent) {\n\t\n\t previousChilds.push(child);\n\t } else {\n\t\n\t nextChilds.push(child);\n\t }\n\t } else {\n\t\n\t reachedCurrent = true;\n\t }\n\t }\n\t\n\t /** Clear current input */\n\t editor.state.inputs[inputIndex].innerHTML = '';\n\t\n\t /**\r\n\t * Append all childs founded before anchorNode\r\n\t */\n\t var previousChildsLength = previousChilds.length;\n\t\n\t for (i = 0; i < previousChildsLength; i++) {\n\t\n\t editor.state.inputs[inputIndex].appendChild(previousChilds[i]);\n\t }\n\t\n\t editor.state.inputs[inputIndex].appendChild(textNodeBeforeCaret);\n\t\n\t /**\r\n\t * Append text node which is after caret\r\n\t */\n\t var nextChildsLength = nextChilds.length,\n\t newNode = document.createElement('div');\n\t\n\t for (i = 0; i < nextChildsLength; i++) {\n\t\n\t newNode.appendChild(nextChilds[i]);\n\t }\n\t\n\t newNode = newNode.innerHTML;\n\t\n\t /** This type of block creates when enter is pressed */\n\t var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\n\t\n\t /**\r\n\t * Make new paragraph with text after caret\r\n\t */\n\t editor.content.insertBlock({\n\t type: NEW_BLOCK_TYPE,\n\t block: editor.tools[NEW_BLOCK_TYPE].render({\n\t text: newNode\n\t })\n\t }, true);\n\t };\n\t\n\t /**\r\n\t * Merges two blocks — current and target\r\n\t * If target index is not exist, then previous will be as target\r\n\t *\r\n\t * @protected\r\n\t * @param {int} currentInputIndex\r\n\t * @param {int} targetInputIndex\r\n\t *\r\n\t * @description gets two inputs indexes and merges into one\r\n\t */\n\t content.mergeBlocks = function (currentInputIndex, targetInputIndex) {\n\t\n\t /** If current input index is zero, then prevent method execution */\n\t if (currentInputIndex === 0) {\n\t\n\t return;\n\t }\n\t\n\t var targetInput,\n\t currentInputContent = editor.state.inputs[currentInputIndex].innerHTML;\n\t\n\t if (!targetInputIndex) {\n\t\n\t targetInput = editor.state.inputs[currentInputIndex - 1];\n\t } else {\n\t\n\t targetInput = editor.state.inputs[targetInputIndex];\n\t }\n\t\n\t targetInput.innerHTML += currentInputContent;\n\t };\n\t\n\t /**\r\n\t * Iterates all right siblings and parents, which has right siblings\r\n\t * while it does not reached the first-level block\r\n\t *\r\n\t * @param {Element} node\r\n\t * @return {boolean}\r\n\t */\n\t content.isLastNode = function (node) {\n\t\n\t // console.log('погнали перебор родителей');\n\t\n\t var allChecked = false;\n\t\n\t while (!allChecked) {\n\t\n\t // console.log('Смотрим на %o', node);\n\t // console.log('Проверим, пустые ли соседи справа');\n\t\n\t if (!allSiblingsEmpty_(node)) {\n\t\n\t // console.log('Есть непустые соседи. Узел не последний. Выходим.');\n\t return false;\n\t }\n\t\n\t node = node.parentNode;\n\t\n\t /**\r\n\t * Проверяем родителей до тех пор, пока не найдем блок первого уровня\r\n\t */\n\t if (node.classList.contains(editor.ui.className.BLOCK_CONTENT)) {\n\t\n\t allChecked = true;\n\t }\n\t }\n\t\n\t return true;\n\t };\n\t\n\t /**\r\n\t * Checks if all element right siblings is empty\r\n\t * @param node\r\n\t */\n\t var allSiblingsEmpty_ = function allSiblingsEmpty_(node) {\n\t\n\t /**\r\n\t * Нужно убедиться, что после пустого соседа ничего нет\r\n\t */\n\t var sibling = node.nextSibling;\n\t\n\t while (sibling) {\n\t\n\t if (sibling.textContent.length) {\n\t\n\t return false;\n\t }\n\t\n\t sibling = sibling.nextSibling;\n\t }\n\t\n\t return true;\n\t };\n\t\n\t /**\r\n\t * @public\r\n\t *\r\n\t * @param {string} htmlData - html content as string\r\n\t * @param {string} plainData - plain text\r\n\t * @return {string} - html content as string\r\n\t */\n\t content.wrapTextWithParagraphs = function (htmlData, plainData) {\n\t\n\t if (!htmlData.trim()) {\n\t\n\t return wrapPlainTextWithParagraphs(plainData);\n\t }\n\t\n\t var wrapper = document.createElement('DIV'),\n\t newWrapper = document.createElement('DIV'),\n\t i,\n\t paragraph,\n\t firstLevelBlocks = ['DIV', 'P'],\n\t blockTyped,\n\t node;\n\t\n\t /**\r\n\t * Make HTML Element to Wrap Text\r\n\t * It allows us to work with input data as HTML content\r\n\t */\n\t wrapper.innerHTML = htmlData;\n\t paragraph = document.createElement('P');\n\t\n\t for (i = 0; i < wrapper.childNodes.length; i++) {\n\t\n\t node = wrapper.childNodes[i];\n\t\n\t blockTyped = firstLevelBlocks.indexOf(node.tagName) != -1;\n\t\n\t /**\r\n\t * If node is first-levet\r\n\t * we add this node to our new wrapper\r\n\t */\n\t if (blockTyped) {\n\t\n\t /**\r\n\t * If we had splitted inline nodes to paragraph before\r\n\t */\n\t if (paragraph.childNodes.length) {\n\t\n\t newWrapper.appendChild(paragraph.cloneNode(true));\n\t\n\t /** empty paragraph */\n\t paragraph = null;\n\t paragraph = document.createElement('P');\n\t }\n\t\n\t newWrapper.appendChild(node.cloneNode(true));\n\t } else {\n\t\n\t /** Collect all inline nodes to one as paragraph */\n\t paragraph.appendChild(node.cloneNode(true));\n\t\n\t /** if node is last we should append this node to paragraph and paragraph to new wrapper */\n\t if (i == wrapper.childNodes.length - 1) {\n\t\n\t newWrapper.appendChild(paragraph.cloneNode(true));\n\t }\n\t }\n\t }\n\t\n\t return newWrapper.innerHTML;\n\t };\n\t\n\t /**\r\n\t * Splits strings on new line and wraps paragraphs with

tag\r\n\t * @param plainText\r\n\t * @returns {string}\r\n\t */\n\t var wrapPlainTextWithParagraphs = function wrapPlainTextWithParagraphs(plainText) {\n\t\n\t return '

' + plainText.split('\\n\\n').join('

') + '

';\n\t };\n\t\n\t /**\r\n\t * Finds closest Contenteditable parent from Element\r\n\t * @param {Element} node element looking from\r\n\t * @return {Element} node contenteditable\r\n\t */\n\t content.getEditableParent = function (node) {\n\t\n\t while (node && node.contentEditable != 'true') {\n\t\n\t node = node.parentNode;\n\t }\n\t\n\t return node;\n\t };\n\t\n\t return content;\n\t}({});\n\n/***/ },\n/* 8 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor toolbar module\r\n\t *\r\n\t * Contains:\r\n\t * - Inline toolbox\r\n\t * - Toolbox within plus button\r\n\t * - Settings section\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0\r\n\t */\n\t\n\tmodule.exports = function (toolbar) {\n\t\n\t var editor = codex.editor;\n\t\n\t toolbar.settings = __webpack_require__(9);\n\t toolbar.inline = __webpack_require__(10);\n\t toolbar.toolbox = __webpack_require__(11);\n\t\n\t /**\r\n\t * Margin between focused node and toolbar\r\n\t */\n\t toolbar.defaultToolbarHeight = 49;\n\t\n\t toolbar.defaultOffset = 34;\n\t\n\t toolbar.opened = false;\n\t\n\t toolbar.current = null;\n\t\n\t /**\r\n\t * @protected\r\n\t */\n\t toolbar.open = function () {\n\t\n\t if (editor.hideToolbar) {\n\t\n\t return;\n\t }\n\t\n\t editor.nodes.toolbar.classList.add('opened');\n\t this.opened = true;\n\t };\n\t\n\t /**\r\n\t * @protected\r\n\t */\n\t toolbar.close = function () {\n\t\n\t editor.nodes.toolbar.classList.remove('opened');\n\t\n\t toolbar.opened = false;\n\t toolbar.current = null;\n\t\n\t for (var button in editor.nodes.toolbarButtons) {\n\t\n\t editor.nodes.toolbarButtons[button].classList.remove('selected');\n\t }\n\t\n\t /** Close toolbox when toolbar is not displayed */\n\t editor.toolbar.toolbox.close();\n\t editor.toolbar.settings.close();\n\t };\n\t\n\t toolbar.toggle = function () {\n\t\n\t if (!this.opened) {\n\t\n\t this.open();\n\t } else {\n\t\n\t this.close();\n\t }\n\t };\n\t\n\t toolbar.hidePlusButton = function () {\n\t\n\t editor.nodes.plusButton.classList.add('hide');\n\t };\n\t\n\t toolbar.showPlusButton = function () {\n\t\n\t editor.nodes.plusButton.classList.remove('hide');\n\t };\n\t\n\t /**\r\n\t * Moving toolbar to the specified node\r\n\t */\n\t toolbar.move = function () {\n\t\n\t /** Close Toolbox when we move toolbar */\n\t editor.toolbar.toolbox.close();\n\t\n\t if (!editor.content.currentNode) {\n\t\n\t return;\n\t }\n\t\n\t var newYCoordinate = editor.content.currentNode.offsetTop - editor.toolbar.defaultToolbarHeight / 2 + editor.toolbar.defaultOffset;\n\t\n\t editor.nodes.toolbar.style.transform = 'translate3D(0, ' + Math.floor(newYCoordinate) + 'px, 0)';\n\t\n\t /** Close trash actions */\n\t editor.toolbar.settings.hideRemoveActions();\n\t };\n\t\n\t return toolbar;\n\t}({});\n\n/***/ },\n/* 9 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Toolbar settings\r\n\t *\r\n\t * @version 1.0.4\r\n\t */\n\t\n\tmodule.exports = function (settings) {\n\t\n\t var editor = codex.editor;\n\t\n\t settings.opened = false;\n\t\n\t settings.setting = null;\n\t settings.actions = null;\n\t\n\t /**\r\n\t * Append and open settings\r\n\t */\n\t settings.open = function (toolType) {\n\t\n\t /**\r\n\t * Append settings content\r\n\t * It's stored in tool.settings\r\n\t */\n\t if (!editor.tools[toolType] || !editor.tools[toolType].makeSettings) {\n\t\n\t editor.core.log('Plugin \\xAB' + toolType + '\\xBB has no settings', 'warn');\n\t editor.nodes.pluginSettings.innerHTML = '\\u041F\\u043B\\u0430\\u0433\\u0438\\u043D \\xAB' + toolType + '\\xBB \\u043D\\u0435 \\u0438\\u043C\\u0435\\u0435\\u0442 \\u043D\\u0430\\u0441\\u0442\\u0440\\u043E\\u0435\\u043A';\n\t } else {\n\t\n\t /**\r\n\t * Draw settings block\r\n\t */\n\t var settingsBlock = editor.tools[toolType].makeSettings();\n\t\n\t editor.nodes.pluginSettings.appendChild(settingsBlock);\n\t }\n\t\n\t /** Open settings block */\n\t editor.nodes.blockSettings.classList.add('opened');\n\t this.opened = true;\n\t };\n\t\n\t /**\r\n\t * Close and clear settings\r\n\t */\n\t settings.close = function () {\n\t\n\t editor.nodes.blockSettings.classList.remove('opened');\n\t editor.nodes.pluginSettings.innerHTML = '';\n\t\n\t this.opened = false;\n\t };\n\t\n\t /**\r\n\t * @param {string} toolType - plugin type\r\n\t */\n\t settings.toggle = function (toolType) {\n\t\n\t if (!this.opened) {\n\t\n\t this.open(toolType);\n\t } else {\n\t\n\t this.close();\n\t }\n\t };\n\t\n\t /**\r\n\t * Here we will draw buttons and add listeners to components\r\n\t */\n\t settings.makeRemoveBlockButton = function () {\n\t\n\t var removeBlockWrapper = editor.draw.node('SPAN', 'ce-toolbar__remove-btn', {}),\n\t settingButton = editor.draw.node('SPAN', 'ce-toolbar__remove-setting', { innerHTML: '' }),\n\t actionWrapper = editor.draw.node('DIV', 'ce-toolbar__remove-confirmation', {}),\n\t confirmAction = editor.draw.node('DIV', 'ce-toolbar__remove-confirm', { textContent: 'Удалить блок' }),\n\t cancelAction = editor.draw.node('DIV', 'ce-toolbar__remove-cancel', { textContent: 'Отмена' });\n\t\n\t editor.listeners.add(settingButton, 'click', editor.toolbar.settings.removeButtonClicked, false);\n\t\n\t editor.listeners.add(confirmAction, 'click', editor.toolbar.settings.confirmRemovingRequest, false);\n\t\n\t editor.listeners.add(cancelAction, 'click', editor.toolbar.settings.cancelRemovingRequest, false);\n\t\n\t actionWrapper.appendChild(confirmAction);\n\t actionWrapper.appendChild(cancelAction);\n\t\n\t removeBlockWrapper.appendChild(settingButton);\n\t removeBlockWrapper.appendChild(actionWrapper);\n\t\n\t /** Save setting */\n\t editor.toolbar.settings.setting = settingButton;\n\t editor.toolbar.settings.actions = actionWrapper;\n\t\n\t return removeBlockWrapper;\n\t };\n\t\n\t settings.removeButtonClicked = function () {\n\t\n\t var action = editor.toolbar.settings.actions;\n\t\n\t if (action.classList.contains('opened')) {\n\t\n\t editor.toolbar.settings.hideRemoveActions();\n\t } else {\n\t\n\t editor.toolbar.settings.showRemoveActions();\n\t }\n\t\n\t editor.toolbar.toolbox.close();\n\t editor.toolbar.settings.close();\n\t };\n\t\n\t settings.cancelRemovingRequest = function () {\n\t\n\t editor.toolbar.settings.actions.classList.remove('opened');\n\t };\n\t\n\t settings.confirmRemovingRequest = function () {\n\t\n\t var currentBlock = editor.content.currentNode,\n\t firstLevelBlocksCount;\n\t\n\t currentBlock.remove();\n\t\n\t firstLevelBlocksCount = editor.nodes.redactor.childNodes.length;\n\t\n\t /**\r\n\t * If all blocks are removed\r\n\t */\n\t if (firstLevelBlocksCount === 0) {\n\t\n\t /** update currentNode variable */\n\t editor.content.currentNode = null;\n\t\n\t /** Inserting new empty initial block */\n\t editor.ui.addInitialBlock();\n\t }\n\t\n\t editor.ui.saveInputs();\n\t\n\t editor.toolbar.close();\n\t };\n\t\n\t settings.showRemoveActions = function () {\n\t\n\t editor.toolbar.settings.actions.classList.add('opened');\n\t };\n\t\n\t settings.hideRemoveActions = function () {\n\t\n\t editor.toolbar.settings.actions.classList.remove('opened');\n\t };\n\t\n\t return settings;\n\t}({});\n\n/***/ },\n/* 10 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Inline toolbar\r\n\t *\r\n\t * Contains from tools:\r\n\t * Bold, Italic, Underline and Anchor\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0\r\n\t */\n\t\n\tmodule.exports = function (inline) {\n\t\n\t var editor = codex.editor;\n\t\n\t inline.buttonsOpened = null;\n\t inline.actionsOpened = null;\n\t inline.wrappersOffset = null;\n\t\n\t /**\r\n\t * saving selection that need for execCommand for styling\r\n\t *\r\n\t */\n\t inline.storedSelection = null;\n\t\n\t /**\r\n\t * @protected\r\n\t *\r\n\t * Open inline toobar\r\n\t */\n\t inline.show = function () {\n\t\n\t var currentNode = editor.content.currentNode,\n\t tool = currentNode.dataset.tool,\n\t plugin;\n\t\n\t /**\r\n\t * tool allowed to open inline toolbar\r\n\t */\n\t plugin = editor.tools[tool];\n\t\n\t if (!plugin.showInlineToolbar) return;\n\t\n\t var selectedText = inline.getSelectionText(),\n\t toolbar = editor.nodes.inlineToolbar.wrapper;\n\t\n\t if (selectedText.length > 0) {\n\t\n\t /** Move toolbar and open */\n\t editor.toolbar.inline.move();\n\t\n\t /** Open inline toolbar */\n\t toolbar.classList.add('opened');\n\t\n\t /** show buttons of inline toolbar */\n\t editor.toolbar.inline.showButtons();\n\t }\n\t };\n\t\n\t /**\r\n\t * @protected\r\n\t *\r\n\t * Closes inline toolbar\r\n\t */\n\t inline.close = function () {\n\t\n\t var toolbar = editor.nodes.inlineToolbar.wrapper;\n\t\n\t toolbar.classList.remove('opened');\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Moving toolbar\r\n\t */\n\t inline.move = function () {\n\t\n\t if (!this.wrappersOffset) {\n\t\n\t this.wrappersOffset = this.getWrappersOffset();\n\t }\n\t\n\t var coords = this.getSelectionCoords(),\n\t defaultOffset = 0,\n\t toolbar = editor.nodes.inlineToolbar.wrapper,\n\t newCoordinateX,\n\t newCoordinateY;\n\t\n\t if (toolbar.offsetHeight === 0) {\n\t\n\t defaultOffset = 40;\n\t }\n\t\n\t newCoordinateX = coords.x - this.wrappersOffset.left;\n\t newCoordinateY = coords.y + window.scrollY - this.wrappersOffset.top - defaultOffset - toolbar.offsetHeight;\n\t\n\t toolbar.style.transform = 'translate3D(' + Math.floor(newCoordinateX) + 'px, ' + Math.floor(newCoordinateY) + 'px, 0)';\n\t\n\t /** Close everything */\n\t editor.toolbar.inline.closeButtons();\n\t editor.toolbar.inline.closeAction();\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Tool Clicked\r\n\t */\n\t\n\t inline.toolClicked = function (event, type) {\n\t\n\t /**\r\n\t * For simple tools we use default browser function\r\n\t * For more complicated tools, we should write our own behavior\r\n\t */\n\t switch (type) {\n\t case 'createLink':\n\t editor.toolbar.inline.createLinkAction(event, type);break;\n\t default:\n\t editor.toolbar.inline.defaultToolAction(type);break;\n\t }\n\t\n\t /**\r\n\t * highlight buttons\r\n\t * after making some action\r\n\t */\n\t editor.nodes.inlineToolbar.buttons.childNodes.forEach(editor.toolbar.inline.hightlight);\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Saving wrappers offset in DOM\r\n\t */\n\t inline.getWrappersOffset = function () {\n\t\n\t var wrapper = editor.nodes.wrapper,\n\t offset = this.getOffset(wrapper);\n\t\n\t this.wrappersOffset = offset;\n\t return offset;\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Calculates offset of DOM element\r\n\t *\r\n\t * @param el\r\n\t * @returns {{top: number, left: number}}\r\n\t */\n\t inline.getOffset = function (el) {\n\t\n\t var _x = 0;\n\t var _y = 0;\n\t\n\t while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {\n\t\n\t _x += el.offsetLeft + el.clientLeft;\n\t _y += el.offsetTop + el.clientTop;\n\t el = el.offsetParent;\n\t }\n\t return { top: _y, left: _x };\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Calculates position of selected text\r\n\t * @returns {{x: number, y: number}}\r\n\t */\n\t inline.getSelectionCoords = function () {\n\t\n\t var sel = document.selection,\n\t range;\n\t var x = 0,\n\t y = 0;\n\t\n\t if (sel) {\n\t\n\t if (sel.type != 'Control') {\n\t\n\t range = sel.createRange();\n\t range.collapse(true);\n\t x = range.boundingLeft;\n\t y = range.boundingTop;\n\t }\n\t } else if (window.getSelection) {\n\t\n\t sel = window.getSelection();\n\t\n\t if (sel.rangeCount) {\n\t\n\t range = sel.getRangeAt(0).cloneRange();\n\t if (range.getClientRects) {\n\t\n\t range.collapse(true);\n\t var rect = range.getClientRects()[0];\n\t\n\t if (!rect) {\n\t\n\t return;\n\t }\n\t\n\t x = rect.left;\n\t y = rect.top;\n\t }\n\t }\n\t }\n\t return { x: x, y: y };\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Returns selected text as String\r\n\t * @returns {string}\r\n\t */\n\t inline.getSelectionText = function () {\n\t\n\t var selectedText = '';\n\t\n\t // all modern browsers and IE9+\n\t if (window.getSelection) {\n\t\n\t selectedText = window.getSelection().toString();\n\t }\n\t\n\t return selectedText;\n\t };\n\t\n\t /** Opens buttons block */\n\t inline.showButtons = function () {\n\t\n\t var buttons = editor.nodes.inlineToolbar.buttons;\n\t\n\t buttons.classList.add('opened');\n\t\n\t editor.toolbar.inline.buttonsOpened = true;\n\t\n\t /** highlight buttons */\n\t editor.nodes.inlineToolbar.buttons.childNodes.forEach(editor.toolbar.inline.hightlight);\n\t };\n\t\n\t /** Makes buttons disappear */\n\t inline.closeButtons = function () {\n\t\n\t var buttons = editor.nodes.inlineToolbar.buttons;\n\t\n\t buttons.classList.remove('opened');\n\t\n\t editor.toolbar.inline.buttonsOpened = false;\n\t };\n\t\n\t /** Open buttons defined action if exist */\n\t inline.showActions = function () {\n\t\n\t var action = editor.nodes.inlineToolbar.actions;\n\t\n\t action.classList.add('opened');\n\t\n\t editor.toolbar.inline.actionsOpened = true;\n\t };\n\t\n\t /** Close actions block */\n\t inline.closeAction = function () {\n\t\n\t var action = editor.nodes.inlineToolbar.actions;\n\t\n\t action.innerHTML = '';\n\t action.classList.remove('opened');\n\t editor.toolbar.inline.actionsOpened = false;\n\t };\n\t\n\t /**\r\n\t * Callback for keydowns in inline toolbar \"Insert link...\" input\r\n\t */\n\t var inlineToolbarAnchorInputKeydown_ = function inlineToolbarAnchorInputKeydown_(event) {\n\t\n\t if (event.keyCode != editor.core.keys.ENTER) {\n\t\n\t return;\n\t }\n\t\n\t var editable = editor.content.currentNode,\n\t storedSelection = editor.toolbar.inline.storedSelection;\n\t\n\t editor.toolbar.inline.restoreSelection(editable, storedSelection);\n\t editor.toolbar.inline.setAnchor(this.value);\n\t\n\t /**\r\n\t * Preventing events that will be able to happen\r\n\t */\n\t event.preventDefault();\n\t event.stopImmediatePropagation();\n\t\n\t editor.toolbar.inline.clearRange();\n\t };\n\t\n\t /** Action for link creation or for setting anchor */\n\t inline.createLinkAction = function (event) {\n\t\n\t var isActive = this.isLinkActive();\n\t\n\t var editable = editor.content.currentNode,\n\t storedSelection = editor.toolbar.inline.saveSelection(editable);\n\t\n\t /** Save globally selection */\n\t editor.toolbar.inline.storedSelection = storedSelection;\n\t\n\t if (isActive) {\n\t\n\t /**\r\n\t * Changing stored selection. if we want to remove anchor from word\r\n\t * we should remove anchor from whole word, not only selected part.\r\n\t * The solution is than we get the length of current link\r\n\t * Change start position to - end of selection minus length of anchor\r\n\t */\n\t editor.toolbar.inline.restoreSelection(editable, storedSelection);\n\t\n\t editor.toolbar.inline.defaultToolAction('unlink');\n\t } else {\n\t\n\t /** Create input and close buttons */\n\t var action = editor.draw.inputForLink();\n\t\n\t editor.nodes.inlineToolbar.actions.appendChild(action);\n\t\n\t editor.toolbar.inline.closeButtons();\n\t editor.toolbar.inline.showActions();\n\t\n\t /**\r\n\t * focus to input\r\n\t * Solution: https://developer.mozilla.org/ru/docs/Web/API/HTMLElement/focus\r\n\t * Prevents event after showing input and when we need to focus an input which is in unexisted form\r\n\t */\n\t action.focus();\n\t event.preventDefault();\n\t\n\t /** Callback to link action */\n\t editor.listeners.add(action, 'keydown', inlineToolbarAnchorInputKeydown_, false);\n\t }\n\t };\n\t\n\t inline.isLinkActive = function () {\n\t\n\t var isActive = false;\n\t\n\t editor.nodes.inlineToolbar.buttons.childNodes.forEach(function (tool) {\n\t\n\t var dataType = tool.dataset.type;\n\t\n\t if (dataType == 'link' && tool.classList.contains('hightlighted')) {\n\t\n\t isActive = true;\n\t }\n\t });\n\t\n\t return isActive;\n\t };\n\t\n\t /** default action behavior of tool */\n\t inline.defaultToolAction = function (type) {\n\t\n\t document.execCommand(type, false, null);\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Sets URL\r\n\t *\r\n\t * @param {String} url - URL\r\n\t */\n\t inline.setAnchor = function (url) {\n\t\n\t document.execCommand('createLink', false, url);\n\t\n\t /** Close after URL inserting */\n\t editor.toolbar.inline.closeAction();\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Saves selection\r\n\t */\n\t inline.saveSelection = function (containerEl) {\n\t\n\t var range = window.getSelection().getRangeAt(0),\n\t preSelectionRange = range.cloneRange(),\n\t start;\n\t\n\t preSelectionRange.selectNodeContents(containerEl);\n\t preSelectionRange.setEnd(range.startContainer, range.startOffset);\n\t\n\t start = preSelectionRange.toString().length;\n\t\n\t return {\n\t start: start,\n\t end: start + range.toString().length\n\t };\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Sets to previous selection (Range)\r\n\t *\r\n\t * @param {Element} containerEl - editable element where we restore range\r\n\t * @param {Object} savedSel - range basic information to restore\r\n\t */\n\t inline.restoreSelection = function (containerEl, savedSel) {\n\t\n\t var range = document.createRange(),\n\t charIndex = 0;\n\t\n\t range.setStart(containerEl, 0);\n\t range.collapse(true);\n\t\n\t var nodeStack = [containerEl],\n\t node,\n\t foundStart = false,\n\t stop = false,\n\t nextCharIndex;\n\t\n\t while (!stop && (node = nodeStack.pop())) {\n\t\n\t if (node.nodeType == 3) {\n\t\n\t nextCharIndex = charIndex + node.length;\n\t\n\t if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {\n\t\n\t range.setStart(node, savedSel.start - charIndex);\n\t foundStart = true;\n\t }\n\t if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {\n\t\n\t range.setEnd(node, savedSel.end - charIndex);\n\t stop = true;\n\t }\n\t charIndex = nextCharIndex;\n\t } else {\n\t\n\t var i = node.childNodes.length;\n\t\n\t while (i--) {\n\t\n\t nodeStack.push(node.childNodes[i]);\n\t }\n\t }\n\t }\n\t\n\t var sel = window.getSelection();\n\t\n\t sel.removeAllRanges();\n\t sel.addRange(range);\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Removes all ranges from window selection\r\n\t */\n\t inline.clearRange = function () {\n\t\n\t var selection = window.getSelection();\n\t\n\t selection.removeAllRanges();\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * sets or removes hightlight\r\n\t */\n\t inline.hightlight = function (tool) {\n\t\n\t var dataType = tool.dataset.type;\n\t\n\t if (document.queryCommandState(dataType)) {\n\t\n\t editor.toolbar.inline.setButtonHighlighted(tool);\n\t } else {\n\t\n\t editor.toolbar.inline.removeButtonsHighLight(tool);\n\t }\n\t\n\t /**\r\n\t *\r\n\t * hightlight for anchors\r\n\t */\n\t var selection = window.getSelection(),\n\t tag = selection.anchorNode.parentNode;\n\t\n\t if (tag.tagName == 'A' && dataType == 'link') {\n\t\n\t editor.toolbar.inline.setButtonHighlighted(tool);\n\t }\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Mark button if text is already executed\r\n\t */\n\t inline.setButtonHighlighted = function (button) {\n\t\n\t button.classList.add('hightlighted');\n\t\n\t /** At link tool we also change icon */\n\t if (button.dataset.type == 'link') {\n\t\n\t var icon = button.childNodes[0];\n\t\n\t icon.classList.remove('ce-icon-link');\n\t icon.classList.add('ce-icon-unlink');\n\t }\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Removes hightlight\r\n\t */\n\t inline.removeButtonsHighLight = function (button) {\n\t\n\t button.classList.remove('hightlighted');\n\t\n\t /** At link tool we also change icon */\n\t if (button.dataset.type == 'link') {\n\t\n\t var icon = button.childNodes[0];\n\t\n\t icon.classList.remove('ce-icon-unlink');\n\t icon.classList.add('ce-icon-link');\n\t }\n\t };\n\t\n\t return inline;\n\t}({});\n\n/***/ },\n/* 11 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor toolbox\r\n\t *\r\n\t * All tools be able to appended here\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0\r\n\t */\n\t\n\tmodule.exports = function (toolbox) {\n\t\n\t var editor = codex.editor;\n\t\n\t toolbox.opened = false;\n\t\n\t /** Shows toolbox */\n\t toolbox.open = function () {\n\t\n\t /** Close setting if toolbox is opened */\n\t if (editor.toolbar.settings.opened) {\n\t\n\t editor.toolbar.settings.close();\n\t }\n\t\n\t /** display toolbox */\n\t editor.nodes.toolbox.classList.add('opened');\n\t\n\t /** Animate plus button */\n\t editor.nodes.plusButton.classList.add('clicked');\n\t\n\t /** toolbox state */\n\t editor.toolbar.toolbox.opened = true;\n\t };\n\t\n\t /** Closes toolbox */\n\t toolbox.close = function () {\n\t\n\t /** Makes toolbox disapear */\n\t editor.nodes.toolbox.classList.remove('opened');\n\t\n\t /** Rotate plus button */\n\t editor.nodes.plusButton.classList.remove('clicked');\n\t\n\t /** toolbox state */\n\t editor.toolbar.toolbox.opened = false;\n\t\n\t editor.toolbar.current = null;\n\t };\n\t\n\t toolbox.leaf = function () {\n\t\n\t var currentTool = editor.toolbar.current,\n\t tools = Object.keys(editor.tools),\n\t barButtons = editor.nodes.toolbarButtons,\n\t nextToolIndex = 0,\n\t toolToSelect = void 0,\n\t visibleTool = void 0,\n\t tool = void 0;\n\t\n\t if (!currentTool) {\n\t\n\t /** Get first tool from object*/\n\t for (tool in editor.tools) {\n\t\n\t if (editor.tools[tool].displayInToolbox) {\n\t\n\t break;\n\t }\n\t\n\t nextToolIndex++;\n\t }\n\t } else {\n\t\n\t nextToolIndex = (tools.indexOf(currentTool) + 1) % tools.length;\n\t visibleTool = tools[nextToolIndex];\n\t\n\t while (!editor.tools[visibleTool].displayInToolbox) {\n\t\n\t nextToolIndex = (nextToolIndex + 1) % tools.length;\n\t visibleTool = tools[nextToolIndex];\n\t }\n\t }\n\t\n\t toolToSelect = tools[nextToolIndex];\n\t\n\t for (var button in barButtons) {\n\t\n\t barButtons[button].classList.remove('selected');\n\t }\n\t\n\t barButtons[toolToSelect].classList.add('selected');\n\t editor.toolbar.current = toolToSelect;\n\t };\n\t\n\t /**\r\n\t * Transforming selected node type into selected toolbar element type\r\n\t * @param {event} event\r\n\t */\n\t toolbox.toolClicked = function (event) {\n\t\n\t /**\r\n\t * UNREPLACEBLE_TOOLS this types of tools are forbidden to replace even they are empty\r\n\t */\n\t var UNREPLACEBLE_TOOLS = ['image', 'link', 'list', 'instagram', 'twitter', 'embed'],\n\t tool = editor.tools[editor.toolbar.current],\n\t workingNode = editor.content.currentNode,\n\t currentInputIndex = editor.caret.inputIndex,\n\t newBlockContent,\n\t appendCallback,\n\t blockData;\n\t\n\t /** Make block from plugin */\n\t newBlockContent = tool.render();\n\t\n\t /** information about block */\n\t blockData = {\n\t block: newBlockContent,\n\t type: tool.type,\n\t stretched: false\n\t };\n\t\n\t if (workingNode && UNREPLACEBLE_TOOLS.indexOf(workingNode.dataset.tool) === -1 && workingNode.textContent.trim() === '') {\n\t\n\t /** Replace current block */\n\t editor.content.switchBlock(workingNode, newBlockContent, tool.type);\n\t } else {\n\t\n\t /** Insert new Block from plugin */\n\t editor.content.insertBlock(blockData);\n\t\n\t /** increase input index */\n\t currentInputIndex++;\n\t }\n\t\n\t /** Fire tool append callback */\n\t appendCallback = tool.appendCallback;\n\t\n\t if (appendCallback && typeof appendCallback == 'function') {\n\t\n\t appendCallback.call(event);\n\t }\n\t\n\t window.setTimeout(function () {\n\t\n\t /** Set caret to current block */\n\t editor.caret.setToBlock(currentInputIndex);\n\t }, 10);\n\t\n\t /**\r\n\t * Changing current Node\r\n\t */\n\t editor.content.workingNodeChanged();\n\t\n\t /**\r\n\t * Move toolbar when node is changed\r\n\t */\n\t editor.toolbar.move();\n\t };\n\t\n\t return toolbox;\n\t}({});\n\n/***/ },\n/* 12 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * @module Codex Editor Callbacks module\r\n\t * @description Module works with editor added Elements\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.4.0\r\n\t */\n\t\n\tmodule.exports = function (callbacks) {\n\t\n\t var editor = codex.editor;\n\t\n\t /**\r\n\t * used by UI module\r\n\t * @description Routes all keydowns on document\r\n\t * @param {Object} event\r\n\t */\n\t callbacks.globalKeydown = function (event) {\n\t\n\t switch (event.keyCode) {\n\t case editor.core.keys.ENTER:\n\t enterKeyPressed_(event);break;\n\t }\n\t };\n\t\n\t /**\r\n\t * used by UI module\r\n\t * @description Routes all keydowns on redactors area\r\n\t * @param {Object} event\r\n\t */\n\t callbacks.redactorKeyDown = function (event) {\n\t\n\t switch (event.keyCode) {\n\t case editor.core.keys.TAB:\n\t tabKeyPressedOnRedactorsZone_(event);break;\n\t case editor.core.keys.ENTER:\n\t enterKeyPressedOnRedactorsZone_(event);break;\n\t case editor.core.keys.ESC:\n\t escapeKeyPressedOnRedactorsZone_(event);break;\n\t default:\n\t defaultKeyPressedOnRedactorsZone_(event);break;\n\t }\n\t };\n\t\n\t /**\r\n\t * used by UI module\r\n\t * @description Routes all keyup events\r\n\t * @param {Object} event\r\n\t */\n\t callbacks.globalKeyup = function (event) {\n\t\n\t switch (event.keyCode) {\n\t case editor.core.keys.UP:\n\t case editor.core.keys.LEFT:\n\t case editor.core.keys.RIGHT:\n\t case editor.core.keys.DOWN:\n\t arrowKeyPressed_(event);break;\n\t }\n\t };\n\t\n\t /**\r\n\t * @param {Object} event\r\n\t * @private\r\n\t *\r\n\t * Handles behaviour when tab pressed\r\n\t * @description if Content is empty show toolbox (if it is closed) or leaf tools\r\n\t * uses Toolbars toolbox module to handle the situation\r\n\t */\n\t var tabKeyPressedOnRedactorsZone_ = function tabKeyPressedOnRedactorsZone_(event) {\n\t\n\t /**\r\n\t * Wait for solution. Would like to know the behaviour\r\n\t * @todo Add spaces\r\n\t */\n\t event.preventDefault();\n\t\n\t var nativeInputs = editor.content.currentNode.querySelectorAll('textarea, input'),\n\t nativeInputsAreEmpty = true,\n\t textContentIsEmpty = !editor.content.currentNode.textContent.trim();\n\t\n\t Array.prototype.map.call(nativeInputs, function (input) {\n\t\n\t if (input.type == 'textarea' || input.type == 'text') {\n\t\n\t nativeInputsAreEmpty = nativeInputsAreEmpty && !input.value.trim();\n\t }\n\t });\n\t\n\t var blockIsEmpty = textContentIsEmpty && nativeInputsAreEmpty;\n\t\n\t if (!blockIsEmpty) {\n\t\n\t return;\n\t }\n\t\n\t if (!editor.toolbar.opened) {\n\t\n\t editor.toolbar.open();\n\t }\n\t\n\t if (editor.toolbar.opened && !editor.toolbar.toolbox.opened) {\n\t\n\t editor.toolbar.toolbox.open();\n\t } else {\n\t\n\t editor.toolbar.toolbox.leaf();\n\t }\n\t };\n\t\n\t /**\r\n\t * Handles global EnterKey Press\r\n\t * @see enterPressedOnBlock_\r\n\t * @param {Object} event\r\n\t */\n\t var enterKeyPressed_ = function enterKeyPressed_() {\n\t\n\t if (editor.content.editorAreaHightlighted) {\n\t\n\t /**\r\n\t * it means that we lose input index, saved index before is not correct\r\n\t * therefore we need to set caret when we insert new block\r\n\t */\n\t editor.caret.inputIndex = -1;\n\t\n\t enterPressedOnBlock_();\n\t }\n\t };\n\t\n\t /**\r\n\t * Callback for enter key pressing in first-level block area\r\n\t *\r\n\t * @param {Event} event\r\n\t * @private\r\n\t *\r\n\t * @description Inserts new block with initial type from settings\r\n\t */\n\t var enterPressedOnBlock_ = function enterPressedOnBlock_() {\n\t\n\t var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\n\t\n\t editor.content.insertBlock({\n\t type: NEW_BLOCK_TYPE,\n\t block: editor.tools[NEW_BLOCK_TYPE].render()\n\t }, true);\n\t\n\t editor.toolbar.move();\n\t editor.toolbar.open();\n\t };\n\t\n\t /**\r\n\t * ENTER key handler\r\n\t *\r\n\t * @param {Object} event\r\n\t * @private\r\n\t *\r\n\t * @description Makes new block with initial type from settings\r\n\t */\n\t var enterKeyPressedOnRedactorsZone_ = function enterKeyPressedOnRedactorsZone_(event) {\n\t\n\t if (event.target.contentEditable == 'true') {\n\t\n\t /** Update input index */\n\t editor.caret.saveCurrentInputIndex();\n\t }\n\t\n\t var currentInputIndex = editor.caret.getCurrentInputIndex() || 0,\n\t workingNode = editor.content.currentNode,\n\t tool = workingNode.dataset.tool,\n\t isEnterPressedOnToolbar = editor.toolbar.opened && editor.toolbar.current && event.target == editor.state.inputs[currentInputIndex];\n\t\n\t /** The list of tools which needs the default browser behaviour */\n\t var enableLineBreaks = editor.tools[tool].enableLineBreaks;\n\t\n\t /** This type of block creates when enter is pressed */\n\t var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\n\t\n\t /**\r\n\t * When toolbar is opened, select tool instead of making new paragraph\r\n\t */\n\t if (isEnterPressedOnToolbar) {\n\t\n\t event.preventDefault();\n\t\n\t editor.toolbar.toolbox.toolClicked(event);\n\t\n\t editor.toolbar.close();\n\t\n\t /**\r\n\t * Stop other listeners callback executions\r\n\t */\n\t event.stopPropagation();\n\t event.stopImmediatePropagation();\n\t\n\t return;\n\t }\n\t\n\t /**\r\n\t * Allow paragraph lineBreaks with shift enter\r\n\t * Or if shiftkey pressed and enter and enabledLineBreaks, the let new block creation\r\n\t */\n\t if (event.shiftKey || enableLineBreaks) {\n\t\n\t event.stopPropagation();\n\t event.stopImmediatePropagation();\n\t return;\n\t }\n\t\n\t var currentSelection = window.getSelection(),\n\t currentSelectedNode = currentSelection.anchorNode,\n\t caretAtTheEndOfText = editor.caret.position.atTheEnd(),\n\t isTextNodeHasParentBetweenContenteditable = false;\n\t\n\t /**\r\n\t * Allow making new

in same block by SHIFT+ENTER and forbids to prevent default browser behaviour\r\n\t */\n\t if (event.shiftKey && !enableLineBreaks) {\n\t\n\t editor.callback.enterPressedOnBlock(editor.content.currentBlock, event);\n\t event.preventDefault();\n\t return;\n\t }\n\t\n\t /**\r\n\t * Workaround situation when caret at the Text node that has some wrapper Elements\r\n\t * Split block cant handle this.\r\n\t * We need to save default behavior\r\n\t */\n\t isTextNodeHasParentBetweenContenteditable = currentSelectedNode && currentSelectedNode.parentNode.contentEditable != 'true';\n\t\n\t /**\r\n\t * Split blocks when input has several nodes and caret placed in textNode\r\n\t */\n\t if (currentSelectedNode.nodeType == editor.core.nodeTypes.TEXT && !isTextNodeHasParentBetweenContenteditable && !caretAtTheEndOfText) {\n\t\n\t event.preventDefault();\n\t\n\t editor.core.log('Splitting Text node...');\n\t\n\t editor.content.splitBlock(currentInputIndex);\n\t\n\t /** Show plus button when next input after split is empty*/\n\t if (!editor.state.inputs[currentInputIndex + 1].textContent.trim()) {\n\t\n\t editor.toolbar.showPlusButton();\n\t }\n\t } else {\n\t\n\t var islastNode = editor.content.isLastNode(currentSelectedNode);\n\t\n\t if (islastNode && caretAtTheEndOfText) {\n\t\n\t event.preventDefault();\n\t event.stopPropagation();\n\t event.stopImmediatePropagation();\n\t\n\t editor.core.log('ENTER clicked in last textNode. Create new BLOCK');\n\t\n\t editor.content.insertBlock({\n\t type: NEW_BLOCK_TYPE,\n\t block: editor.tools[NEW_BLOCK_TYPE].render()\n\t }, true);\n\t\n\t editor.toolbar.move();\n\t editor.toolbar.open();\n\t\n\t /** Show plus button with empty block */\n\t editor.toolbar.showPlusButton();\n\t }\n\t }\n\t\n\t /** get all inputs after new appending block */\n\t editor.ui.saveInputs();\n\t };\n\t\n\t /**\r\n\t * Escape behaviour\r\n\t * @param event\r\n\t * @private\r\n\t *\r\n\t * @description Closes toolbox and toolbar. Prevents default behaviour\r\n\t */\n\t var escapeKeyPressedOnRedactorsZone_ = function escapeKeyPressedOnRedactorsZone_(event) {\n\t\n\t /** Close all toolbar */\n\t editor.toolbar.close();\n\t\n\t /** Close toolbox */\n\t editor.toolbar.toolbox.close();\n\t\n\t event.preventDefault();\n\t };\n\t\n\t /**\r\n\t * @param {Event} event\r\n\t * @private\r\n\t *\r\n\t * closes and moves toolbar\r\n\t */\n\t var arrowKeyPressed_ = function arrowKeyPressed_(event) {\n\t\n\t editor.content.workingNodeChanged();\n\t\n\t /* Closing toolbar */\n\t editor.toolbar.close();\n\t editor.toolbar.move();\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t * @param {Event} event\r\n\t *\r\n\t * @description Closes all opened bars from toolbar.\r\n\t * If block is mark, clears highlightning\r\n\t */\n\t var defaultKeyPressedOnRedactorsZone_ = function defaultKeyPressedOnRedactorsZone_() {\n\t\n\t editor.toolbar.close();\n\t\n\t if (!editor.toolbar.inline.actionsOpened) {\n\t\n\t editor.toolbar.inline.close();\n\t editor.content.clearMark();\n\t }\n\t };\n\t\n\t /**\r\n\t * Handler when clicked on redactors area\r\n\t *\r\n\t * @protected\r\n\t * @param event\r\n\t *\r\n\t * @description Detects clicked area. If it is first-level block area, marks as detected and\r\n\t * on next enter press will be inserted new block\r\n\t * Otherwise, save carets position (input index) and put caret to the editable zone.\r\n\t *\r\n\t * @see detectWhenClickedOnFirstLevelBlockArea_\r\n\t *\r\n\t */\n\t callbacks.redactorClicked = function (event) {\n\t\n\t detectWhenClickedOnFirstLevelBlockArea_();\n\t\n\t editor.content.workingNodeChanged(event.target);\n\t editor.ui.saveInputs();\n\t\n\t var selectedText = editor.toolbar.inline.getSelectionText(),\n\t firstLevelBlock;\n\t\n\t /** If selection range took off, then we hide inline toolbar */\n\t if (selectedText.length === 0) {\n\t\n\t editor.toolbar.inline.close();\n\t }\n\t\n\t /** Update current input index in memory when caret focused into existed input */\n\t if (event.target.contentEditable == 'true') {\n\t\n\t editor.caret.saveCurrentInputIndex();\n\t }\n\t\n\t if (editor.content.currentNode === null) {\n\t\n\t /**\r\n\t * If inputs in redactor does not exits, then we put input index 0 not -1\r\n\t */\n\t var indexOfLastInput = editor.state.inputs.length > 0 ? editor.state.inputs.length - 1 : 0;\n\t\n\t /** If we have any inputs */\n\t if (editor.state.inputs.length) {\n\t\n\t /** getting firstlevel parent of input */\n\t firstLevelBlock = editor.content.getFirstLevelBlock(editor.state.inputs[indexOfLastInput]);\n\t }\n\t\n\t /** If input is empty, then we set caret to the last input */\n\t if (editor.state.inputs.length && editor.state.inputs[indexOfLastInput].textContent === '' && firstLevelBlock.dataset.tool == editor.settings.initialBlockPlugin) {\n\t\n\t editor.caret.setToBlock(indexOfLastInput);\n\t } else {\n\t\n\t /** Create new input when caret clicked in redactors area */\n\t var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\n\t\n\t editor.content.insertBlock({\n\t type: NEW_BLOCK_TYPE,\n\t block: editor.tools[NEW_BLOCK_TYPE].render()\n\t });\n\t\n\t /** If there is no inputs except inserted */\n\t if (editor.state.inputs.length === 1) {\n\t\n\t editor.caret.setToBlock(indexOfLastInput);\n\t } else {\n\t\n\t /** Set caret to this appended input */\n\t editor.caret.setToNextBlock(indexOfLastInput);\n\t }\n\t }\n\t } else {\n\t\n\t /** Close all panels */\n\t editor.toolbar.settings.close();\n\t editor.toolbar.toolbox.close();\n\t }\n\t\n\t /**\r\n\t * Move toolbar and open\r\n\t */\n\t editor.toolbar.move();\n\t editor.toolbar.open();\n\t\n\t var inputIsEmpty = !editor.content.currentNode.textContent.trim(),\n\t currentNodeType = editor.content.currentNode.dataset.tool,\n\t isInitialType = currentNodeType == editor.settings.initialBlockPlugin;\n\t\n\t /** Hide plus buttons */\n\t editor.toolbar.hidePlusButton();\n\t\n\t if (!inputIsEmpty) {\n\t\n\t /** Mark current block */\n\t editor.content.markBlock();\n\t }\n\t\n\t if (isInitialType && inputIsEmpty) {\n\t\n\t /** Show plus button */\n\t editor.toolbar.showPlusButton();\n\t }\n\t };\n\t\n\t /**\r\n\t * This method allows to define, is caret in contenteditable element or not.\r\n\t *\r\n\t * @private\r\n\t *\r\n\t * @description Otherwise, if we get TEXT node from range container, that will means we have input index.\r\n\t * In this case we use default browsers behaviour (if plugin allows that) or overwritten action.\r\n\t * Therefore, to be sure that we've clicked first-level block area, we should have currentNode, which always\r\n\t * specifies to the first-level block. Other cases we just ignore.\r\n\t */\n\t var detectWhenClickedOnFirstLevelBlockArea_ = function detectWhenClickedOnFirstLevelBlockArea_() {\n\t\n\t var selection = window.getSelection(),\n\t anchorNode = selection.anchorNode,\n\t flag = false;\n\t\n\t if (selection.rangeCount === 0) {\n\t\n\t editor.content.editorAreaHightlighted = true;\n\t } else {\n\t\n\t if (!editor.core.isDomNode(anchorNode)) {\n\t\n\t anchorNode = anchorNode.parentNode;\n\t }\n\t\n\t /** Already founded, without loop */\n\t if (anchorNode.contentEditable == 'true') {\n\t\n\t flag = true;\n\t }\n\t\n\t while (anchorNode.contentEditable != 'true') {\n\t\n\t anchorNode = anchorNode.parentNode;\n\t\n\t if (anchorNode.contentEditable == 'true') {\n\t\n\t flag = true;\n\t }\n\t\n\t if (anchorNode == document.body) {\n\t\n\t break;\n\t }\n\t }\n\t\n\t /** If editable element founded, flag is \"TRUE\", Therefore we return \"FALSE\" */\n\t editor.content.editorAreaHightlighted = !flag;\n\t }\n\t };\n\t\n\t /**\r\n\t * Toolbar button click handler\r\n\t *\r\n\t * @param {Object} event - cursor to the button\r\n\t * @protected\r\n\t *\r\n\t * @description gets current tool and calls render method\r\n\t */\n\t callbacks.toolbarButtonClicked = function (event) {\n\t\n\t var button = this;\n\t\n\t editor.toolbar.current = button.dataset.type;\n\t\n\t editor.toolbar.toolbox.toolClicked(event);\n\t editor.toolbar.close();\n\t };\n\t\n\t /**\r\n\t * Show or Hide toolbox when plus button is clicked\r\n\t */\n\t callbacks.plusButtonClicked = function () {\n\t\n\t if (!editor.nodes.toolbox.classList.contains('opened')) {\n\t\n\t editor.toolbar.toolbox.open();\n\t } else {\n\t\n\t editor.toolbar.toolbox.close();\n\t }\n\t };\n\t\n\t /**\r\n\t * Block handlers for KeyDown events\r\n\t *\r\n\t * @protected\r\n\t * @param {Object} event\r\n\t *\r\n\t * Handles keydowns on block\r\n\t * @see blockRightOrDownArrowPressed_\r\n\t * @see backspacePressed_\r\n\t * @see blockLeftOrUpArrowPressed_\r\n\t */\n\t callbacks.blockKeydown = function (event) {\n\t\n\t var block = event.target; // event.target is input\n\t\n\t switch (event.keyCode) {\n\t\n\t case editor.core.keys.DOWN:\n\t case editor.core.keys.RIGHT:\n\t blockRightOrDownArrowPressed_(event);\n\t break;\n\t\n\t case editor.core.keys.BACKSPACE:\n\t backspacePressed_(block, event);\n\t break;\n\t\n\t case editor.core.keys.UP:\n\t case editor.core.keys.LEFT:\n\t blockLeftOrUpArrowPressed_(event);\n\t break;\n\t\n\t }\n\t };\n\t\n\t /**\r\n\t * RIGHT or DOWN keydowns on block\r\n\t *\r\n\t * @param {Object} event\r\n\t * @private\r\n\t *\r\n\t * @description watches the selection and gets closest editable element.\r\n\t * Uses method getDeepestTextNodeFromPosition to get the last node of next block\r\n\t * Sets caret if it is contenteditable\r\n\t */\n\t var blockRightOrDownArrowPressed_ = function blockRightOrDownArrowPressed_(event) {\n\t\n\t var selection = window.getSelection(),\n\t inputs = editor.state.inputs,\n\t focusedNode = selection.anchorNode,\n\t focusedNodeHolder;\n\t\n\t /** Check for caret existance */\n\t if (!focusedNode) {\n\t\n\t return false;\n\t }\n\t\n\t /** Looking for closest (parent) contentEditable element of focused node */\n\t while (focusedNode.contentEditable != 'true') {\n\t\n\t focusedNodeHolder = focusedNode.parentNode;\n\t focusedNode = focusedNodeHolder;\n\t }\n\t\n\t /** Input index in DOM level */\n\t var editableElementIndex = 0;\n\t\n\t while (focusedNode != inputs[editableElementIndex]) {\n\t\n\t editableElementIndex++;\n\t }\n\t\n\t /**\r\n\t * Founded contentEditable element doesn't have childs\r\n\t * Or maybe New created block\r\n\t */\n\t if (!focusedNode.textContent) {\n\t\n\t editor.caret.setToNextBlock(editableElementIndex);\n\t return;\n\t }\n\t\n\t /**\r\n\t * Do nothing when caret doesn not reaches the end of last child\r\n\t */\n\t var caretInLastChild = false,\n\t caretAtTheEndOfText = false;\n\t\n\t var lastChild, deepestTextnode;\n\t\n\t lastChild = focusedNode.childNodes[focusedNode.childNodes.length - 1];\n\t\n\t if (editor.core.isDomNode(lastChild)) {\n\t\n\t deepestTextnode = editor.content.getDeepestTextNodeFromPosition(lastChild, lastChild.childNodes.length);\n\t } else {\n\t\n\t deepestTextnode = lastChild;\n\t }\n\t\n\t caretInLastChild = selection.anchorNode == deepestTextnode;\n\t caretAtTheEndOfText = deepestTextnode.length == selection.anchorOffset;\n\t\n\t if (!caretInLastChild || !caretAtTheEndOfText) {\n\t\n\t editor.core.log('arrow [down|right] : caret does not reached the end');\n\t return false;\n\t }\n\t\n\t editor.caret.setToNextBlock(editableElementIndex);\n\t };\n\t\n\t /**\r\n\t * LEFT or UP keydowns on block\r\n\t *\r\n\t * @param {Object} event\r\n\t * @private\r\n\t *\r\n\t * watches the selection and gets closest editable element.\r\n\t * Uses method getDeepestTextNodeFromPosition to get the last node of previous block\r\n\t * Sets caret if it is contenteditable\r\n\t *\r\n\t */\n\t var blockLeftOrUpArrowPressed_ = function blockLeftOrUpArrowPressed_(event) {\n\t\n\t var selection = window.getSelection(),\n\t inputs = editor.state.inputs,\n\t focusedNode = selection.anchorNode,\n\t focusedNodeHolder;\n\t\n\t /** Check for caret existance */\n\t if (!focusedNode) {\n\t\n\t return false;\n\t }\n\t\n\t /**\r\n\t * LEFT or UP not at the beginning\r\n\t */\n\t if (selection.anchorOffset !== 0) {\n\t\n\t return false;\n\t }\n\t\n\t /** Looking for parent contentEditable block */\n\t while (focusedNode.contentEditable != 'true') {\n\t\n\t focusedNodeHolder = focusedNode.parentNode;\n\t focusedNode = focusedNodeHolder;\n\t }\n\t\n\t /** Input index in DOM level */\n\t var editableElementIndex = 0;\n\t\n\t while (focusedNode != inputs[editableElementIndex]) {\n\t\n\t editableElementIndex++;\n\t }\n\t\n\t /**\r\n\t * Do nothing if caret is not at the beginning of first child\r\n\t */\n\t var caretInFirstChild = false,\n\t caretAtTheBeginning = false;\n\t\n\t var firstChild, deepestTextnode;\n\t\n\t /**\r\n\t * Founded contentEditable element doesn't have childs\r\n\t * Or maybe New created block\r\n\t */\n\t if (!focusedNode.textContent) {\n\t\n\t editor.caret.setToPreviousBlock(editableElementIndex);\n\t return;\n\t }\n\t\n\t firstChild = focusedNode.childNodes[0];\n\t\n\t if (editor.core.isDomNode(firstChild)) {\n\t\n\t deepestTextnode = editor.content.getDeepestTextNodeFromPosition(firstChild, 0);\n\t } else {\n\t\n\t deepestTextnode = firstChild;\n\t }\n\t\n\t caretInFirstChild = selection.anchorNode == deepestTextnode;\n\t caretAtTheBeginning = selection.anchorOffset === 0;\n\t\n\t if (caretInFirstChild && caretAtTheBeginning) {\n\t\n\t editor.caret.setToPreviousBlock(editableElementIndex);\n\t }\n\t };\n\t\n\t /**\r\n\t * Handles backspace keydown\r\n\t *\r\n\t * @param {Element} block\r\n\t * @param {Object} event\r\n\t * @private\r\n\t *\r\n\t * @description if block is empty, delete the block and set caret to the previous block\r\n\t * If block is not empty, try to merge two blocks - current and previous\r\n\t * But it we try'n to remove first block, then we should set caret to the next block, not previous.\r\n\t * If we removed the last block, create new one\r\n\t */\n\t var backspacePressed_ = function backspacePressed_(block, event) {\n\t\n\t var currentInputIndex = editor.caret.getCurrentInputIndex(),\n\t range,\n\t selectionLength,\n\t firstLevelBlocksCount;\n\t\n\t if (editor.core.isNativeInput(event.target)) {\n\t\n\t /** If input value is empty - remove block */\n\t if (event.target.value.trim() == '') {\n\t\n\t block.remove();\n\t } else {\n\t\n\t return;\n\t }\n\t }\n\t\n\t if (block.textContent.trim()) {\n\t\n\t range = editor.content.getRange();\n\t selectionLength = range.endOffset - range.startOffset;\n\t\n\t if (editor.caret.position.atStart() && !selectionLength && editor.state.inputs[currentInputIndex - 1]) {\n\t\n\t editor.content.mergeBlocks(currentInputIndex);\n\t } else {\n\t\n\t return;\n\t }\n\t }\n\t\n\t if (!selectionLength) {\n\t\n\t block.remove();\n\t }\n\t\n\t firstLevelBlocksCount = editor.nodes.redactor.childNodes.length;\n\t\n\t /**\r\n\t * If all blocks are removed\r\n\t */\n\t if (firstLevelBlocksCount === 0) {\n\t\n\t /** update currentNode variable */\n\t editor.content.currentNode = null;\n\t\n\t /** Inserting new empty initial block */\n\t editor.ui.addInitialBlock();\n\t\n\t /** Updating inputs state after deleting last block */\n\t editor.ui.saveInputs();\n\t\n\t /** Set to current appended block */\n\t window.setTimeout(function () {\n\t\n\t editor.caret.setToPreviousBlock(1);\n\t }, 10);\n\t } else {\n\t\n\t if (editor.caret.inputIndex !== 0) {\n\t\n\t /** Target block is not first */\n\t editor.caret.setToPreviousBlock(editor.caret.inputIndex);\n\t } else {\n\t\n\t /** If we try to delete first block */\n\t editor.caret.setToNextBlock(editor.caret.inputIndex);\n\t }\n\t }\n\t\n\t editor.toolbar.move();\n\t\n\t if (!editor.toolbar.opened) {\n\t\n\t editor.toolbar.open();\n\t }\n\t\n\t /** Updating inputs state */\n\t editor.ui.saveInputs();\n\t\n\t /** Prevent default browser behaviour */\n\t event.preventDefault();\n\t };\n\t\n\t /**\r\n\t * used by UI module\r\n\t * Clicks on block settings button\r\n\t *\r\n\t * @param {Object} event\r\n\t * @protected\r\n\t * @description Opens toolbar settings\r\n\t */\n\t callbacks.showSettingsButtonClicked = function (event) {\n\t\n\t /**\r\n\t * Get type of current block\r\n\t * It uses to append settings from tool.settings property.\r\n\t * ...\r\n\t * Type is stored in data-type attribute on block\r\n\t */\n\t var currentToolType = editor.content.currentNode.dataset.tool;\n\t\n\t editor.toolbar.settings.toggle(currentToolType);\n\t\n\t /** Close toolbox when settings button is active */\n\t editor.toolbar.toolbox.close();\n\t editor.toolbar.settings.hideRemoveActions();\n\t };\n\t\n\t return callbacks;\n\t}({});\n\n/***/ },\n/* 13 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor Draw module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0.\r\n\t */\n\t\n\tmodule.exports = function (draw) {\n\t\n\t /**\r\n\t * Base editor wrapper\r\n\t */\n\t draw.wrapper = function () {\n\t\n\t var wrapper = document.createElement('div');\n\t\n\t wrapper.className += 'codex-editor';\n\t\n\t return wrapper;\n\t };\n\t\n\t /**\r\n\t * Content-editable holder\r\n\t */\n\t draw.redactor = function () {\n\t\n\t var redactor = document.createElement('div');\n\t\n\t redactor.className += 'ce-redactor';\n\t\n\t return redactor;\n\t };\n\t\n\t draw.ceBlock = function () {\n\t\n\t var block = document.createElement('DIV');\n\t\n\t block.className += 'ce_block';\n\t\n\t return block;\n\t };\n\t\n\t /**\r\n\t * Empty toolbar with toggler\r\n\t */\n\t draw.toolbar = function () {\n\t\n\t var bar = document.createElement('div');\n\t\n\t bar.className += 'ce-toolbar';\n\t\n\t return bar;\n\t };\n\t\n\t draw.toolbarContent = function () {\n\t\n\t var wrapper = document.createElement('DIV');\n\t\n\t wrapper.classList.add('ce-toolbar__content');\n\t\n\t return wrapper;\n\t };\n\t\n\t /**\r\n\t * Inline toolbar\r\n\t */\n\t draw.inlineToolbar = function () {\n\t\n\t var bar = document.createElement('DIV');\n\t\n\t bar.className += 'ce-toolbar-inline';\n\t\n\t return bar;\n\t };\n\t\n\t /**\r\n\t * Wrapper for inline toobar buttons\r\n\t */\n\t draw.inlineToolbarButtons = function () {\n\t\n\t var wrapper = document.createElement('DIV');\n\t\n\t wrapper.className += 'ce-toolbar-inline__buttons';\n\t\n\t return wrapper;\n\t };\n\t\n\t /**\r\n\t * For some actions\r\n\t */\n\t draw.inlineToolbarActions = function () {\n\t\n\t var wrapper = document.createElement('DIV');\n\t\n\t wrapper.className += 'ce-toolbar-inline__actions';\n\t\n\t return wrapper;\n\t };\n\t\n\t draw.inputForLink = function () {\n\t\n\t var input = document.createElement('INPUT');\n\t\n\t input.type = 'input';\n\t input.className += 'inputForLink';\n\t input.placeholder = 'Вставьте ссылку ...';\n\t input.setAttribute('form', 'defaultForm');\n\t\n\t input.setAttribute('autofocus', 'autofocus');\n\t\n\t return input;\n\t };\n\t\n\t /**\r\n\t * @todo Desc\r\n\t */\n\t draw.blockButtons = function () {\n\t\n\t var block = document.createElement('div');\n\t\n\t block.className += 'ce-toolbar__actions';\n\t\n\t return block;\n\t };\n\t\n\t /**\r\n\t * Block settings panel\r\n\t */\n\t draw.blockSettings = function () {\n\t\n\t var settings = document.createElement('div');\n\t\n\t settings.className += 'ce-settings';\n\t\n\t return settings;\n\t };\n\t\n\t draw.defaultSettings = function () {\n\t\n\t var div = document.createElement('div');\n\t\n\t div.classList.add('ce-settings_default');\n\t\n\t return div;\n\t };\n\t\n\t draw.pluginsSettings = function () {\n\t\n\t var div = document.createElement('div');\n\t\n\t div.classList.add('ce-settings_plugin');\n\t\n\t return div;\n\t };\n\t\n\t draw.plusButton = function () {\n\t\n\t var button = document.createElement('span');\n\t\n\t button.className = 'ce-toolbar__plus';\n\t // button.innerHTML = '';\n\t\n\t return button;\n\t };\n\t\n\t /**\r\n\t * Settings button in toolbar\r\n\t */\n\t draw.settingsButton = function () {\n\t\n\t var toggler = document.createElement('span');\n\t\n\t toggler.className = 'ce-toolbar__settings-btn';\n\t\n\t /** Toggler button*/\n\t toggler.innerHTML = '';\n\t\n\t return toggler;\n\t };\n\t\n\t /**\r\n\t * Redactor tools wrapper\r\n\t */\n\t\n\t draw.toolbox = function () {\n\t\n\t var wrapper = document.createElement('div');\n\t\n\t wrapper.className = 'ce-toolbar__tools';\n\t\n\t return wrapper;\n\t };\n\t\n\t /**\r\n\t * @protected\r\n\t *\r\n\t * Draws tool buttons for toolbox\r\n\t *\r\n\t * @param {String} type\r\n\t * @param {String} classname\r\n\t * @returns {Element}\r\n\t */\n\t draw.toolbarButton = function (type, classname) {\n\t\n\t var button = document.createElement('li'),\n\t toolIcon = document.createElement('i'),\n\t toolTitle = document.createElement('span');\n\t\n\t button.dataset.type = type;\n\t button.setAttribute('title', type);\n\t\n\t toolIcon.classList.add(classname);\n\t toolTitle.classList.add('ce_toolbar_tools--title');\n\t\n\t button.appendChild(toolIcon);\n\t button.appendChild(toolTitle);\n\t\n\t return button;\n\t };\n\t\n\t /**\r\n\t * @protected\r\n\t *\r\n\t * Draws tools for inline toolbar\r\n\t *\r\n\t * @param {String} type\r\n\t * @param {String} classname\r\n\t */\n\t draw.toolbarButtonInline = function (type, classname) {\n\t\n\t var button = document.createElement('BUTTON'),\n\t toolIcon = document.createElement('I');\n\t\n\t button.type = 'button';\n\t button.dataset.type = type;\n\t toolIcon.classList.add(classname);\n\t\n\t button.appendChild(toolIcon);\n\t\n\t return button;\n\t };\n\t\n\t /**\r\n\t * Redactor block\r\n\t */\n\t draw.block = function (tagName, content) {\n\t\n\t var node = document.createElement(tagName);\n\t\n\t node.innerHTML = content || '';\n\t\n\t return node;\n\t };\n\t\n\t /**\r\n\t * Creates Node with passed tagName and className\r\n\t * @param {string} tagName\r\n\t * @param {string} className\r\n\t * @param {object} properties - allow to assign properties\r\n\t */\n\t draw.node = function (tagName, className, properties) {\n\t\n\t var el = document.createElement(tagName);\n\t\n\t if (className) el.className = className;\n\t\n\t if (properties) {\n\t\n\t for (var name in properties) {\n\t\n\t el[name] = properties[name];\n\t }\n\t }\n\t\n\t return el;\n\t };\n\t\n\t /**\r\n\t * Unavailable plugin block\r\n\t */\n\t draw.unavailableBlock = function () {\n\t\n\t var wrapper = document.createElement('DIV');\n\t\n\t wrapper.classList.add('cdx-unavailable-block');\n\t\n\t return wrapper;\n\t };\n\t\n\t return draw;\n\t}({});\n\n/***/ },\n/* 14 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor Caret Module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0\r\n\t */\n\t\n\tmodule.exports = function (caret) {\n\t\n\t var editor = codex.editor;\n\t\n\t /**\r\n\t * @var {int} InputIndex - editable element in DOM\r\n\t */\n\t caret.inputIndex = null;\n\t\n\t /**\r\n\t * @var {int} offset - caret position in a text node.\r\n\t */\n\t caret.offset = null;\n\t\n\t /**\r\n\t * @var {int} focusedNodeIndex - we get index of child node from first-level block\r\n\t */\n\t caret.focusedNodeIndex = null;\n\t\n\t /**\r\n\t * Creates Document Range and sets caret to the element.\r\n\t * @protected\r\n\t * @uses caret.save — if you need to save caret position\r\n\t * @param {Element} el - Changed Node.\r\n\t */\n\t caret.set = function (el, index, offset) {\n\t\n\t offset = offset || caret.offset || 0;\n\t index = index || caret.focusedNodeIndex || 0;\n\t\n\t var childs = el.childNodes,\n\t nodeToSet;\n\t\n\t if (childs.length === 0) {\n\t\n\t nodeToSet = el;\n\t } else {\n\t\n\t nodeToSet = childs[index];\n\t }\n\t\n\t /** If Element is INPUT */\n\t if (el.contentEditable != 'true') {\n\t\n\t el.focus();\n\t return;\n\t }\n\t\n\t if (editor.core.isDomNode(nodeToSet)) {\n\t\n\t nodeToSet = editor.content.getDeepestTextNodeFromPosition(nodeToSet, nodeToSet.childNodes.length);\n\t }\n\t\n\t var range = document.createRange(),\n\t selection = window.getSelection();\n\t\n\t window.setTimeout(function () {\n\t\n\t range.setStart(nodeToSet, offset);\n\t range.setEnd(nodeToSet, offset);\n\t\n\t selection.removeAllRanges();\n\t selection.addRange(range);\n\t\n\t editor.caret.saveCurrentInputIndex();\n\t }, 20);\n\t };\n\t\n\t /**\r\n\t * @protected\r\n\t * Updates index of input and saves it in caret object\r\n\t */\n\t caret.saveCurrentInputIndex = function () {\n\t\n\t /** Index of Input that we paste sanitized content */\n\t var selection = window.getSelection(),\n\t inputs = editor.state.inputs,\n\t focusedNode = selection.anchorNode,\n\t focusedNodeHolder;\n\t\n\t if (!focusedNode) {\n\t\n\t return;\n\t }\n\t\n\t /** Looking for parent contentEditable block */\n\t while (focusedNode.contentEditable != 'true') {\n\t\n\t focusedNodeHolder = focusedNode.parentNode;\n\t focusedNode = focusedNodeHolder;\n\t }\n\t\n\t /** Input index in DOM level */\n\t var editableElementIndex = 0;\n\t\n\t while (focusedNode != inputs[editableElementIndex]) {\n\t\n\t editableElementIndex++;\n\t }\n\t\n\t caret.inputIndex = editableElementIndex;\n\t };\n\t\n\t /**\r\n\t * Returns current input index (caret object)\r\n\t */\n\t caret.getCurrentInputIndex = function () {\n\t\n\t return caret.inputIndex;\n\t };\n\t\n\t /**\r\n\t * @param {int} index - index of first-level block after that we set caret into next input\r\n\t */\n\t caret.setToNextBlock = function (index) {\n\t\n\t var inputs = editor.state.inputs,\n\t nextInput = inputs[index + 1];\n\t\n\t if (!nextInput) {\n\t\n\t editor.core.log('We are reached the end');\n\t return;\n\t }\n\t\n\t /**\r\n\t * When new Block created or deleted content of input\r\n\t * We should add some text node to set caret\r\n\t */\n\t if (!nextInput.childNodes.length) {\n\t\n\t var emptyTextElement = document.createTextNode('');\n\t\n\t nextInput.appendChild(emptyTextElement);\n\t }\n\t\n\t editor.caret.inputIndex = index + 1;\n\t editor.caret.set(nextInput, 0, 0);\n\t editor.content.workingNodeChanged(nextInput);\n\t };\n\t\n\t /**\r\n\t * @param {int} index - index of target input.\r\n\t * Sets caret to input with this index\r\n\t */\n\t caret.setToBlock = function (index) {\n\t\n\t var inputs = editor.state.inputs,\n\t targetInput = inputs[index];\n\t\n\t if (!targetInput) {\n\t\n\t return;\n\t }\n\t\n\t /**\r\n\t * When new Block created or deleted content of input\r\n\t * We should add some text node to set caret\r\n\t */\n\t if (!targetInput.childNodes.length) {\n\t\n\t var emptyTextElement = document.createTextNode('');\n\t\n\t targetInput.appendChild(emptyTextElement);\n\t }\n\t\n\t editor.caret.inputIndex = index;\n\t editor.caret.set(targetInput, 0, 0);\n\t editor.content.workingNodeChanged(targetInput);\n\t };\n\t\n\t /**\r\n\t * @param {int} index - index of input\r\n\t */\n\t caret.setToPreviousBlock = function (index) {\n\t\n\t index = index || 0;\n\t\n\t var inputs = editor.state.inputs,\n\t previousInput = inputs[index - 1],\n\t lastChildNode,\n\t lengthOfLastChildNode,\n\t emptyTextElement;\n\t\n\t if (!previousInput) {\n\t\n\t editor.core.log('We are reached first node');\n\t return;\n\t }\n\t\n\t lastChildNode = editor.content.getDeepestTextNodeFromPosition(previousInput, previousInput.childNodes.length);\n\t lengthOfLastChildNode = lastChildNode.length;\n\t\n\t /**\r\n\t * When new Block created or deleted content of input\r\n\t * We should add some text node to set caret\r\n\t */\n\t if (!previousInput.childNodes.length) {\n\t\n\t emptyTextElement = document.createTextNode('');\n\t previousInput.appendChild(emptyTextElement);\n\t }\n\t editor.caret.inputIndex = index - 1;\n\t editor.caret.set(previousInput, previousInput.childNodes.length - 1, lengthOfLastChildNode);\n\t editor.content.workingNodeChanged(inputs[index - 1]);\n\t };\n\t\n\t caret.position = {\n\t\n\t atStart: function atStart() {\n\t\n\t var selection = window.getSelection(),\n\t anchorOffset = selection.anchorOffset,\n\t anchorNode = selection.anchorNode,\n\t firstLevelBlock = editor.content.getFirstLevelBlock(anchorNode),\n\t pluginsRender = firstLevelBlock.childNodes[0];\n\t\n\t if (!editor.core.isDomNode(anchorNode)) {\n\t\n\t anchorNode = anchorNode.parentNode;\n\t }\n\t\n\t var isFirstNode = anchorNode === pluginsRender.childNodes[0],\n\t isOffsetZero = anchorOffset === 0;\n\t\n\t return isFirstNode && isOffsetZero;\n\t },\n\t\n\t atTheEnd: function atTheEnd() {\n\t\n\t var selection = window.getSelection(),\n\t anchorOffset = selection.anchorOffset,\n\t anchorNode = selection.anchorNode;\n\t\n\t /** Caret is at the end of input */\n\t return !anchorNode || !anchorNode.length || anchorOffset === anchorNode.length;\n\t }\n\t };\n\t\n\t /**\r\n\t * Inserts node at the caret location\r\n\t * @param node\r\n\t */\n\t caret.insertNode = function (node) {\n\t\n\t var selection,\n\t range,\n\t lastNode = node;\n\t\n\t if (node instanceof window.DocumentFragment) {\n\t\n\t lastNode = node.lastChild;\n\t }\n\t\n\t selection = window.getSelection();\n\t\n\t range = selection.getRangeAt(0);\n\t range.deleteContents();\n\t\n\t range.insertNode(node);\n\t\n\t range.setStartAfter(lastNode);\n\t range.collapse(true);\n\t\n\t selection.removeAllRanges();\n\t selection.addRange(range);\n\t };\n\t\n\t return caret;\n\t}({});\n\n/***/ },\n/* 15 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor Notification Module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0\r\n\t */\n\t\n\tmodule.exports = function (notifications) {\n\t\n\t var editor = codex.editor;\n\t\n\t var queue = [];\n\t\n\t var addToQueue = function addToQueue(settings) {\n\t\n\t queue.push(settings);\n\t\n\t var index = 0;\n\t\n\t while (index < queue.length && queue.length > 5) {\n\t\n\t if (queue[index].type == 'confirm' || queue[index].type == 'prompt') {\n\t\n\t index++;\n\t continue;\n\t }\n\t\n\t queue[index].close();\n\t queue.splice(index, 1);\n\t }\n\t };\n\t\n\t notifications.createHolder = function () {\n\t\n\t var holder = editor.draw.node('DIV', 'cdx-notifications-block');\n\t\n\t editor.nodes.notifications = document.body.appendChild(holder);\n\t\n\t return holder;\n\t };\n\t\n\t /**\r\n\t * Error notificator. Shows block with message\r\n\t * @protected\r\n\t */\n\t notifications.errorThrown = function (errorMsg, event) {\n\t\n\t editor.notifications.notification({ message: 'This action is not available currently', type: event.type });\n\t };\n\t\n\t /**\r\n\t *\r\n\t * Appends notification\r\n\t *\r\n\t * settings = {\r\n\t * type - notification type (reserved types: alert, confirm, prompt). Just add class 'cdx-notification-'+type\r\n\t * message - notification message\r\n\t * okMsg - confirm button text (default - 'Ok')\r\n\t * cancelBtn - cancel button text (default - 'Cancel'). Only for confirm and prompt types\r\n\t * confirm - function-handler for ok button click\r\n\t * cancel - function-handler for cancel button click. Only for confirm and prompt types\r\n\t * time - time (in seconds) after which notification will close (default - 10s)\r\n\t * }\r\n\t *\r\n\t * @param settings\r\n\t */\n\t notifications.notification = function (constructorSettings) {\n\t\n\t /** Private vars and methods */\n\t var notification = null,\n\t cancel = null,\n\t type = null,\n\t confirm = null,\n\t inputField = null;\n\t\n\t var confirmHandler = function confirmHandler() {\n\t\n\t close();\n\t\n\t if (typeof confirm !== 'function') {\n\t\n\t return;\n\t }\n\t\n\t if (type == 'prompt') {\n\t\n\t confirm(inputField.value);\n\t return;\n\t }\n\t\n\t confirm();\n\t };\n\t\n\t var cancelHandler = function cancelHandler() {\n\t\n\t close();\n\t\n\t if (typeof cancel !== 'function') {\n\t\n\t return;\n\t }\n\t\n\t cancel();\n\t };\n\t\n\t /** Public methods */\n\t function create(settings) {\n\t\n\t if (!(settings && settings.message)) {\n\t\n\t editor.core.log('Can\\'t create notification. Message is missed');\n\t return;\n\t }\n\t\n\t settings.type = settings.type || 'alert';\n\t settings.time = settings.time * 1000 || 10000;\n\t\n\t var wrapper = editor.draw.node('DIV', 'cdx-notification'),\n\t message = editor.draw.node('DIV', 'cdx-notification__message'),\n\t input = editor.draw.node('INPUT', 'cdx-notification__input'),\n\t okBtn = editor.draw.node('SPAN', 'cdx-notification__ok-btn'),\n\t cancelBtn = editor.draw.node('SPAN', 'cdx-notification__cancel-btn');\n\t\n\t message.textContent = settings.message;\n\t okBtn.textContent = settings.okMsg || 'ОК';\n\t cancelBtn.textContent = settings.cancelMsg || 'Отмена';\n\t\n\t editor.listeners.add(okBtn, 'click', confirmHandler);\n\t editor.listeners.add(cancelBtn, 'click', cancelHandler);\n\t\n\t wrapper.appendChild(message);\n\t\n\t if (settings.type == 'prompt') {\n\t\n\t wrapper.appendChild(input);\n\t }\n\t\n\t wrapper.appendChild(okBtn);\n\t\n\t if (settings.type == 'prompt' || settings.type == 'confirm') {\n\t\n\t wrapper.appendChild(cancelBtn);\n\t }\n\t\n\t wrapper.classList.add('cdx-notification-' + settings.type);\n\t wrapper.dataset.type = settings.type;\n\t\n\t notification = wrapper;\n\t type = settings.type;\n\t confirm = settings.confirm;\n\t cancel = settings.cancel;\n\t inputField = input;\n\t\n\t if (settings.type != 'prompt' && settings.type != 'confirm') {\n\t\n\t window.setTimeout(close, settings.time);\n\t }\n\t };\n\t\n\t function send() {\n\t\n\t editor.nodes.notifications.appendChild(notification);\n\t inputField.focus();\n\t\n\t editor.nodes.notifications.classList.add('cdx-notification__notification-appending');\n\t\n\t window.setTimeout(function () {\n\t\n\t editor.nodes.notifications.classList.remove('cdx-notification__notification-appending');\n\t }, 100);\n\t\n\t addToQueue({ type: type, close: close });\n\t };\n\t\n\t function close() {\n\t\n\t notification.remove();\n\t };\n\t\n\t if (constructorSettings) {\n\t\n\t create(constructorSettings);\n\t send();\n\t }\n\t\n\t return {\n\t create: create,\n\t send: send,\n\t close: close\n\t };\n\t };\n\t\n\t notifications.clear = function () {\n\t\n\t editor.nodes.notifications.innerHTML = '';\n\t queue = [];\n\t };\n\t\n\t return notifications;\n\t}({});\n\n/***/ },\n/* 16 */\n/***/ function(module, exports) {\n\n\t\"use strict\";\n\t\n\t/**\r\n\t * Codex Editor Parser Module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.1\r\n\t */\n\t\n\tmodule.exports = function (parser) {\n\t\n\t var editor = codex.editor;\n\t\n\t /** inserting text */\n\t parser.insertPastedContent = function (blockType, tag) {\n\t\n\t editor.content.insertBlock({\n\t type: blockType.type,\n\t block: blockType.render({\n\t text: tag.innerHTML\n\t })\n\t });\n\t };\n\t\n\t /**\r\n\t * Check DOM node for display style: separated block or child-view\r\n\t */\n\t parser.isFirstLevelBlock = function (node) {\n\t\n\t return node.nodeType == editor.core.nodeTypes.TAG && node.classList.contains(editor.ui.className.BLOCK_CLASSNAME);\n\t };\n\t\n\t return parser;\n\t}({});\n\n/***/ },\n/* 17 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Sanitizer\r\n\t */\n\t\n\tmodule.exports = function (sanitizer) {\n\t\n\t /** HTML Janitor library */\n\t var janitor = __webpack_require__(18);\n\t\n\t /** Codex Editor */\n\t var editor = codex.editor;\n\t\n\t sanitizer.prepare = function () {\n\t\n\t if (editor.settings.sanitizer && !editor.core.isEmpty(editor.settings.sanitizer)) {\n\t\n\t Config.CUSTOM = editor.settings.sanitizer;\n\t }\n\t };\n\t\n\t /**\r\n\t * Basic config\r\n\t */\n\t var Config = {\n\t\n\t /** User configuration */\n\t CUSTOM: null,\n\t\n\t BASIC: {\n\t\n\t tags: {\n\t p: {},\n\t a: {\n\t href: true,\n\t target: '_blank',\n\t rel: 'nofollow'\n\t },\n\t i: {},\n\t b: {},\n\t strong: {},\n\t em: {},\n\t span: {}\n\t }\n\t }\n\t };\n\t\n\t sanitizer.Config = Config;\n\t\n\t /**\r\n\t *\r\n\t * @param userCustomConfig\r\n\t * @returns {*}\r\n\t * @private\r\n\t *\r\n\t * @description If developer uses editor's API, then he can customize sane restrictions.\r\n\t * Or, sane config can be defined globally in editors initialization. That config will be used everywhere\r\n\t * At least, if there is no config overrides, that API uses BASIC Default configation\r\n\t */\n\t var init_ = function init_(userCustomConfig) {\n\t\n\t var configuration = userCustomConfig || Config.CUSTOM || Config.BASIC;\n\t\n\t return new janitor(configuration);\n\t };\n\t\n\t /**\r\n\t * Cleans string from unwanted tags\r\n\t * @protected\r\n\t * @param {String} dirtyString - taint string\r\n\t * @param {Object} customConfig - allowed tags\r\n\t */\n\t sanitizer.clean = function (dirtyString, customConfig) {\n\t\n\t var janitorInstance = init_(customConfig);\n\t\n\t return janitorInstance.clean(dirtyString);\n\t };\n\t\n\t return sanitizer;\n\t}({});\n\n/***/ },\n/* 18 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;(function (root, factory) {\n\t if (true) {\n\t !(__WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t } else if (typeof exports === 'object') {\n\t module.exports = factory();\n\t } else {\n\t root.HTMLJanitor = factory();\n\t }\n\t}(this, function () {\n\t\n\t /**\n\t * @param {Object} config.tags Dictionary of allowed tags.\n\t * @param {boolean} config.keepNestedBlockElements Default false.\n\t */\n\t function HTMLJanitor(config) {\n\t\n\t var tagDefinitions = config['tags'];\n\t var tags = Object.keys(tagDefinitions);\n\t\n\t var validConfigValues = tags\n\t .map(function(k) { return typeof tagDefinitions[k]; })\n\t .every(function(type) { return type === 'object' || type === 'boolean' || type === 'function'; });\n\t\n\t if(!validConfigValues) {\n\t throw new Error(\"The configuration was invalid\");\n\t }\n\t\n\t this.config = config;\n\t }\n\t\n\t // TODO: not exhaustive?\n\t var blockElementNames = ['P', 'LI', 'TD', 'TH', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'PRE'];\n\t function isBlockElement(node) {\n\t return blockElementNames.indexOf(node.nodeName) !== -1;\n\t }\n\t\n\t var inlineElementNames = ['A', 'B', 'STRONG', 'I', 'EM', 'SUB', 'SUP', 'U', 'STRIKE'];\n\t function isInlineElement(node) {\n\t return inlineElementNames.indexOf(node.nodeName) !== -1;\n\t }\n\t\n\t HTMLJanitor.prototype.clean = function (html) {\n\t var sandbox = document.createElement('div');\n\t sandbox.innerHTML = html;\n\t\n\t this._sanitize(sandbox);\n\t\n\t return sandbox.innerHTML;\n\t };\n\t\n\t HTMLJanitor.prototype._sanitize = function (parentNode) {\n\t var treeWalker = createTreeWalker(parentNode);\n\t var node = treeWalker.firstChild();\n\t if (!node) { return; }\n\t\n\t do {\n\t // Ignore nodes that have already been sanitized\n\t if (node._sanitized) {\n\t continue;\n\t }\n\t\n\t if (node.nodeType === Node.TEXT_NODE) {\n\t // If this text node is just whitespace and the previous or next element\n\t // sibling is a block element, remove it\n\t // N.B.: This heuristic could change. Very specific to a bug with\n\t // `contenteditable` in Firefox: http://jsbin.com/EyuKase/1/edit?js,output\n\t // FIXME: make this an option?\n\t if (node.data.trim() === ''\n\t && ((node.previousElementSibling && isBlockElement(node.previousElementSibling))\n\t || (node.nextElementSibling && isBlockElement(node.nextElementSibling)))) {\n\t parentNode.removeChild(node);\n\t this._sanitize(parentNode);\n\t break;\n\t } else {\n\t continue;\n\t }\n\t }\n\t\n\t // Remove all comments\n\t if (node.nodeType === Node.COMMENT_NODE) {\n\t parentNode.removeChild(node);\n\t this._sanitize(parentNode);\n\t break;\n\t }\n\t\n\t var isInline = isInlineElement(node);\n\t var containsBlockElement;\n\t if (isInline) {\n\t containsBlockElement = Array.prototype.some.call(node.childNodes, isBlockElement);\n\t }\n\t\n\t // Block elements should not be nested (e.g.

  • ...); if\n\t // they are, we want to unwrap the inner block element.\n\t var isNotTopContainer = !! parentNode.parentNode;\n\t var isNestedBlockElement =\n\t isBlockElement(parentNode) &&\n\t isBlockElement(node) &&\n\t isNotTopContainer;\n\t\n\t var nodeName = node.nodeName.toLowerCase();\n\t\n\t var allowedAttrs = getAllowedAttrs(this.config, nodeName, node);\n\t\n\t var isInvalid = isInline && containsBlockElement;\n\t\n\t // Drop tag entirely according to the whitelist *and* if the markup\n\t // is invalid.\n\t if (isInvalid || shouldRejectNode(node, allowedAttrs)\n\t || (!this.config.keepNestedBlockElements && isNestedBlockElement)) {\n\t // Do not keep the inner text of SCRIPT/STYLE elements.\n\t if (! (node.nodeName === 'SCRIPT' || node.nodeName === 'STYLE')) {\n\t while (node.childNodes.length > 0) {\n\t parentNode.insertBefore(node.childNodes[0], node);\n\t }\n\t }\n\t parentNode.removeChild(node);\n\t\n\t this._sanitize(parentNode);\n\t break;\n\t }\n\t\n\t // Sanitize attributes\n\t for (var a = 0; a < node.attributes.length; a += 1) {\n\t var attr = node.attributes[a];\n\t\n\t if (shouldRejectAttr(attr, allowedAttrs, node)) {\n\t node.removeAttribute(attr.name);\n\t // Shift the array to continue looping.\n\t a = a - 1;\n\t }\n\t }\n\t\n\t // Sanitize children\n\t this._sanitize(node);\n\t\n\t // Mark node as sanitized so it's ignored in future runs\n\t node._sanitized = true;\n\t } while ((node = treeWalker.nextSibling()));\n\t };\n\t\n\t function createTreeWalker(node) {\n\t return document.createTreeWalker(node,\n\t NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT,\n\t null, false);\n\t }\n\t\n\t function getAllowedAttrs(config, nodeName, node){\n\t if (typeof config.tags[nodeName] === 'function') {\n\t return config.tags[nodeName](node);\n\t } else {\n\t return config.tags[nodeName];\n\t }\n\t }\n\t\n\t function shouldRejectNode(node, allowedAttrs){\n\t if (typeof allowedAttrs === 'undefined') {\n\t return true;\n\t } else if (typeof allowedAttrs === 'boolean') {\n\t return !allowedAttrs;\n\t }\n\t\n\t return false;\n\t }\n\t\n\t function shouldRejectAttr(attr, allowedAttrs, node){\n\t var attrName = attr.name.toLowerCase();\n\t\n\t if (allowedAttrs === true){\n\t return false;\n\t } else if (typeof allowedAttrs[attrName] === 'function'){\n\t return !allowedAttrs[attrName](attr.value, node);\n\t } else if (typeof allowedAttrs[attrName] === 'undefined'){\n\t return true;\n\t } else if (allowedAttrs[attrName] === false) {\n\t return true;\n\t } else if (typeof allowedAttrs[attrName] === 'string') {\n\t return (allowedAttrs[attrName] !== attr.value);\n\t }\n\t\n\t return false;\n\t }\n\t\n\t return HTMLJanitor;\n\t\n\t}));\n\n\n/***/ },\n/* 19 */\n/***/ function(module, exports) {\n\n\t\"use strict\";\n\t\n\t/**\r\n\t * Codex Editor Listeners module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0\r\n\t */\n\t\n\t/**\r\n\t * Module-decorator for event listeners assignment\r\n\t */\n\tmodule.exports = function (listeners) {\n\t\n\t var allListeners = [];\n\t\n\t /**\r\n\t * Search methods\r\n\t *\r\n\t * byElement, byType and byHandler returns array of suitable listeners\r\n\t * one and all takes element, eventType, and handler and returns first (all) suitable listener\r\n\t *\r\n\t */\n\t listeners.search = function () {\n\t\n\t var byElement = function byElement(element, context) {\n\t\n\t var listenersOnElement = [];\n\t\n\t context = context || allListeners;\n\t\n\t for (var i = 0; i < context.length; i++) {\n\t\n\t var listener = context[i];\n\t\n\t if (listener.element === element) {\n\t\n\t listenersOnElement.push(listener);\n\t }\n\t }\n\t\n\t return listenersOnElement;\n\t };\n\t\n\t var byType = function byType(eventType, context) {\n\t\n\t var listenersWithType = [];\n\t\n\t context = context || allListeners;\n\t\n\t for (var i = 0; i < context.length; i++) {\n\t\n\t var listener = context[i];\n\t\n\t if (listener.type === eventType) {\n\t\n\t listenersWithType.push(listener);\n\t }\n\t }\n\t\n\t return listenersWithType;\n\t };\n\t\n\t var byHandler = function byHandler(handler, context) {\n\t\n\t var listenersWithHandler = [];\n\t\n\t context = context || allListeners;\n\t\n\t for (var i = 0; i < context.length; i++) {\n\t\n\t var listener = context[i];\n\t\n\t if (listener.handler === handler) {\n\t\n\t listenersWithHandler.push(listener);\n\t }\n\t }\n\t\n\t return listenersWithHandler;\n\t };\n\t\n\t var one = function one(element, eventType, handler) {\n\t\n\t var result = allListeners;\n\t\n\t if (element) result = byElement(element, result);\n\t\n\t if (eventType) result = byType(eventType, result);\n\t\n\t if (handler) result = byHandler(handler, result);\n\t\n\t return result[0];\n\t };\n\t\n\t var all = function all(element, eventType, handler) {\n\t\n\t var result = allListeners;\n\t\n\t if (element) result = byElement(element, result);\n\t\n\t if (eventType) result = byType(eventType, result);\n\t\n\t if (handler) result = byHandler(handler, result);\n\t\n\t return result;\n\t };\n\t\n\t return {\n\t byElement: byElement,\n\t byType: byType,\n\t byHandler: byHandler,\n\t one: one,\n\t all: all\n\t };\n\t }();\n\t\n\t listeners.add = function (element, eventType, handler, isCapture) {\n\t\n\t element.addEventListener(eventType, handler, isCapture);\n\t\n\t var data = {\n\t element: element,\n\t type: eventType,\n\t handler: handler\n\t };\n\t\n\t var alreadyAddedListener = listeners.search.one(element, eventType, handler);\n\t\n\t if (!alreadyAddedListener) {\n\t\n\t allListeners.push(data);\n\t }\n\t };\n\t\n\t listeners.remove = function (element, eventType, handler) {\n\t\n\t element.removeEventListener(eventType, handler);\n\t\n\t var existingListeners = listeners.search.all(element, eventType, handler);\n\t\n\t for (var i = 0; i < existingListeners.length; i++) {\n\t\n\t var index = allListeners.indexOf(existingListeners[i]);\n\t\n\t if (index > 0) {\n\t\n\t allListeners.splice(index, 1);\n\t }\n\t }\n\t };\n\t\n\t listeners.removeAll = function () {\n\t\n\t allListeners.map(function (current) {\n\t\n\t listeners.remove(current.element, current.type, current.handler);\n\t });\n\t };\n\t\n\t listeners.get = function (element, eventType, handler) {\n\t\n\t return listeners.search.all(element, eventType, handler);\n\t };\n\t\n\t return listeners;\n\t}({});\n\n/***/ },\n/* 20 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\tvar _typeof = typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; };\n\t\n\t/**\r\n\t * Codex Editor Destroyer module\r\n\t *\r\n\t * @auhor Codex Team\r\n\t * @version 1.0\r\n\t */\n\t\n\tmodule.exports = function (destroyer) {\n\t\n\t var editor = codex.editor;\n\t\n\t destroyer.removeNodes = function () {\n\t\n\t editor.nodes.wrapper.remove();\n\t editor.nodes.notifications.remove();\n\t };\n\t\n\t destroyer.destroyPlugins = function () {\n\t\n\t for (var tool in editor.tools) {\n\t\n\t if (typeof editor.tools[tool].destroy === 'function') {\n\t\n\t editor.tools[tool].destroy();\n\t }\n\t }\n\t };\n\t\n\t destroyer.destroyScripts = function () {\n\t\n\t var scripts = document.getElementsByTagName('SCRIPT');\n\t\n\t for (var i = 0; i < scripts.length; i++) {\n\t\n\t if (scripts[i].id.indexOf(editor.scriptPrefix) + 1) {\n\t\n\t scripts[i].remove();\n\t i--;\n\t }\n\t }\n\t };\n\t\n\t /**\r\n\t * Delete editor data from webpage.\r\n\t * You should send settings argument with boolean flags:\r\n\t * @param settings.ui- remove redactor event listeners and DOM nodes\r\n\t * @param settings.scripts - remove redactor scripts from DOM\r\n\t * @param settings.plugins - remove plugin's objects\r\n\t * @param settings.core - remove editor core. You can remove core only if UI and scripts flags is true\r\n\t * }\r\n\t *\r\n\t */\n\t destroyer.destroy = function (settings) {\n\t\n\t if (!settings || (typeof settings === 'undefined' ? 'undefined' : _typeof(settings)) !== 'object') {\n\t\n\t return;\n\t }\n\t\n\t if (settings.ui) {\n\t\n\t destroyer.removeNodes();\n\t editor.listeners.removeAll();\n\t }\n\t\n\t if (settings.scripts) {\n\t\n\t destroyer.destroyScripts();\n\t }\n\t\n\t if (settings.plugins) {\n\t\n\t destroyer.destroyPlugins();\n\t }\n\t\n\t if (settings.ui && settings.scripts && settings.core) {\n\t\n\t delete codex.editor;\n\t }\n\t };\n\t\n\t return destroyer;\n\t}({});\n\n/***/ },\n/* 21 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor Paste module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.1.1\r\n\t */\n\t\n\tmodule.exports = function (paste) {\n\t\n\t var editor = codex.editor;\n\t\n\t var patterns = [];\n\t\n\t paste.prepare = function () {\n\t\n\t var tools = editor.tools;\n\t\n\t for (var tool in tools) {\n\t\n\t if (!tools[tool].renderOnPastePatterns || !Array.isArray(tools[tool].renderOnPastePatterns)) {\n\t\n\t continue;\n\t }\n\t\n\t tools[tool].renderOnPastePatterns.map(function (pattern) {\n\t\n\t patterns.push(pattern);\n\t });\n\t }\n\t\n\t return Promise.resolve();\n\t };\n\t\n\t /**\r\n\t * Saves data\r\n\t * @param event\r\n\t */\n\t paste.pasted = function (event) {\n\t\n\t var clipBoardData = event.clipboardData || window.clipboardData,\n\t content = clipBoardData.getData('Text');\n\t\n\t var result = analize(content);\n\t\n\t if (result) {\n\t\n\t event.preventDefault();\n\t event.stopImmediatePropagation();\n\t }\n\t\n\t return result;\n\t };\n\t\n\t /**\r\n\t * Analizes pated string and calls necessary method\r\n\t */\n\t\n\t var analize = function analize(string) {\n\t\n\t var result = false,\n\t content = editor.content.currentNode,\n\t plugin = content.dataset.tool;\n\t\n\t patterns.map(function (pattern) {\n\t\n\t var execArray = pattern.regex.exec(string),\n\t match = execArray && execArray[0];\n\t\n\t if (match && match === string.trim()) {\n\t\n\t /** current block is not empty */\n\t if (content.textContent.trim() && plugin == editor.settings.initialBlockPlugin) {\n\t\n\t pasteToNewBlock_();\n\t }\n\t\n\t pattern.callback(string, pattern);\n\t result = true;\n\t }\n\t });\n\t\n\t return result;\n\t };\n\t\n\t var pasteToNewBlock_ = function pasteToNewBlock_() {\n\t\n\t /** Create new initial block */\n\t editor.content.insertBlock({\n\t\n\t type: editor.settings.initialBlockPlugin,\n\t block: editor.tools[editor.settings.initialBlockPlugin].render({\n\t text: ''\n\t })\n\t\n\t }, false);\n\t };\n\t\n\t /**\r\n\t * This method prevents default behaviour.\r\n\t *\r\n\t * @param {Object} event\r\n\t * @protected\r\n\t *\r\n\t * @description We get from clipboard pasted data, sanitize, make a fragment that contains of this sanitized nodes.\r\n\t * Firstly, we need to memorize the caret position. We can do that by getting the range of selection.\r\n\t * After all, we insert clear fragment into caret placed position. Then, we should move the caret to the last node\r\n\t */\n\t paste.blockPasteCallback = function (event) {\n\t\n\t if (!needsToHandlePasteEvent(event.target)) {\n\t\n\t return;\n\t }\n\t\n\t /** Prevent default behaviour */\n\t event.preventDefault();\n\t\n\t /** get html pasted data - dirty data */\n\t var htmlData = event.clipboardData.getData('text/html'),\n\t plainData = event.clipboardData.getData('text/plain');\n\t\n\t /** Temporary DIV that is used to work with text's paragraphs as DOM-elements*/\n\t var paragraphs = editor.draw.node('DIV', '', {}),\n\t cleanData,\n\t wrappedData;\n\t\n\t /** Create fragment, that we paste to range after proccesing */\n\t cleanData = editor.sanitizer.clean(htmlData);\n\t\n\t /**\r\n\t * We wrap pasted text with

    tags to split it logically\r\n\t * @type {string}\r\n\t */\n\t wrappedData = editor.content.wrapTextWithParagraphs(cleanData, plainData);\n\t paragraphs.innerHTML = wrappedData;\n\t\n\t /**\r\n\t * If there only one paragraph, just insert in at the caret location\r\n\t */\n\t if (paragraphs.childNodes.length == 1) {\n\t\n\t emulateUserAgentBehaviour(paragraphs.firstChild);\n\t return;\n\t }\n\t\n\t insertPastedParagraphs(paragraphs.childNodes);\n\t };\n\t\n\t /**\r\n\t * Checks if we should handle paste event on block\r\n\t * @param block\r\n\t *\r\n\t * @return {boolean}\r\n\t */\n\t var needsToHandlePasteEvent = function needsToHandlePasteEvent(block) {\n\t\n\t /** If area is input or textarea then allow default behaviour */\n\t if (editor.core.isNativeInput(block)) {\n\t\n\t return false;\n\t }\n\t\n\t var editableParent = editor.content.getEditableParent(block);\n\t\n\t /** Allow paste when event target placed in Editable element */\n\t if (!editableParent) {\n\t\n\t return false;\n\t }\n\t\n\t return true;\n\t };\n\t\n\t /**\r\n\t * Inserts new initial plugin blocks with data in paragraphs\r\n\t *\r\n\t * @param {Array} paragraphs - array of paragraphs (

    ) whit content, that should be inserted\r\n\t */\n\t var insertPastedParagraphs = function insertPastedParagraphs(paragraphs) {\n\t\n\t var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin,\n\t currentBlockContent = editor.content.currentNode.firstChild.firstChild;\n\t\n\t paragraphs.forEach(function (paragraph, index) {\n\t\n\t /** Don't allow empty paragraphs */\n\t if (paragraph.innerHTML.trim() === '') {\n\t\n\t return;\n\t }\n\t\n\t /**\r\n\t * If there was no data in working node, replace it with first paragraph of pasted text\r\n\t */\n\t if (index == 0 && currentBlockContent.innerHTML.trim() === '') {\n\t\n\t editor.content.switchBlock(editor.content.currentNode, editor.tools[NEW_BLOCK_TYPE].render({\n\t text: paragraph.innerHTML\n\t }), NEW_BLOCK_TYPE);\n\t\n\t return;\n\t }\n\t\n\t editor.content.insertBlock({\n\t type: NEW_BLOCK_TYPE,\n\t block: editor.tools[NEW_BLOCK_TYPE].render({\n\t text: paragraph.innerHTML\n\t })\n\t });\n\t\n\t editor.caret.inputIndex++;\n\t });\n\t\n\t editor.caret.setToPreviousBlock(editor.caret.getCurrentInputIndex() + 1);\n\t };\n\t\n\t /**\r\n\t * Inserts node content at the caret position\r\n\t *\r\n\t * @param {Node} node - DOM node (could be DocumentFragment), that should be inserted at the caret location\r\n\t */\n\t var emulateUserAgentBehaviour = function emulateUserAgentBehaviour(node) {\n\t\n\t var newNode;\n\t\n\t if (node.childElementCount) {\n\t\n\t newNode = document.createDocumentFragment();\n\t\n\t node.childNodes.forEach(function (current) {\n\t\n\t if (!editor.core.isDomNode(current) && current.data.trim() === '') {\n\t\n\t return;\n\t }\n\t\n\t newNode.appendChild(current.cloneNode(true));\n\t });\n\t } else {\n\t\n\t newNode = document.createTextNode(node.textContent);\n\t }\n\t\n\t editor.caret.insertNode(newNode);\n\t };\n\t\n\t return paste;\n\t}({});\n\n/***/ }\n/******/ ]);\n\n\n// WEBPACK FOOTER //\n// codex-editor.js"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(0);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 18d57d06018e2df16ec7","/**\r\n *\r\n * Codex Editor\r\n *\r\n * @author Codex Team\r\n */\r\n\r\nmodule.exports = (function (editor) {\r\n\r\n 'use strict';\r\n\r\n editor.version = VERSION;\r\n editor.scriptPrefix = 'cdx-script-';\r\n\r\n var init = function () {\r\n\r\n editor.core = require('./modules/core');\r\n editor.tools = require('./modules/tools');\r\n editor.ui = require('./modules/ui');\r\n editor.transport = require('./modules/transport');\r\n editor.renderer = require('./modules/renderer');\r\n editor.saver = require('./modules/saver');\r\n editor.content = require('./modules/content');\r\n editor.toolbar = require('./modules/toolbar/toolbar');\r\n editor.callback = require('./modules/callbacks');\r\n editor.draw = require('./modules/draw');\r\n editor.caret = require('./modules/caret');\r\n editor.notifications = require('./modules/notifications');\r\n editor.parser = require('./modules/parser');\r\n editor.sanitizer = require('./modules/sanitizer');\r\n editor.listeners = require('./modules/listeners');\r\n editor.destroyer = require('./modules/destroyer');\r\n editor.paste = require('./modules/paste');\r\n\r\n };\r\n\r\n /**\r\n * @public\r\n * holds initial settings\r\n */\r\n editor.settings = {\r\n tools : ['paragraph', 'header', 'picture', 'list', 'quote', 'code', 'twitter', 'instagram', 'smile'],\r\n holderId : 'codex-editor',\r\n\r\n // Type of block showing on empty editor\r\n initialBlockPlugin: 'paragraph'\r\n };\r\n\r\n /**\r\n * public\r\n *\r\n * Static nodes\r\n */\r\n editor.nodes = {\r\n holder : null,\r\n wrapper : null,\r\n toolbar : null,\r\n inlineToolbar : {\r\n wrapper : null,\r\n buttons : null,\r\n actions : null\r\n },\r\n toolbox : null,\r\n notifications : null,\r\n plusButton : null,\r\n showSettingsButton: null,\r\n showTrashButton : null,\r\n blockSettings : null,\r\n pluginSettings : null,\r\n defaultSettings : null,\r\n toolbarButtons : {}, // { type : DomEl, ... }\r\n redactor : null\r\n };\r\n\r\n /**\r\n * @public\r\n *\r\n * Output state\r\n */\r\n editor.state = {\r\n jsonOutput : [],\r\n blocks : [],\r\n inputs : []\r\n };\r\n\r\n /**\r\n * @public\r\n * Editor plugins\r\n */\r\n editor.tools = {};\r\n\r\n /**\r\n * Initialization\r\n * @uses Promise cEditor.core.prepare\r\n * @param {Object} userSettings\r\n * @param {Array} userSettings.tools list of plugins\r\n * @param {String} userSettings.holderId Element's id to append editor\r\n *\r\n * Load user defined tools\r\n * Tools must contain this important objects :\r\n * @param {String} type - this is a type of plugin. It can be used as plugin name\r\n * @param {String} iconClassname - this a icon in toolbar\r\n * @param {Object} make - what should plugin do, when it is clicked\r\n * @param {Object} appendCallback - callback after clicking\r\n * @param {Element} settings - what settings does it have\r\n * @param {Object} render - plugin get JSON, and should return HTML\r\n * @param {Object} save - plugin gets HTML content, returns JSON\r\n * @param {Boolean} displayInToolbox - will be displayed in toolbox. Default value is TRUE\r\n * @param {Boolean} enableLineBreaks - inserts new block or break lines. Default value is FALSE\r\n *\r\n * @example\r\n * - type : 'header',\r\n * - iconClassname : 'ce-icon-header',\r\n * - make : headerTool.make,\r\n * - appendCallback : headerTool.appendCallback,\r\n * - settings : headerTool.makeSettings(),\r\n * - render : headerTool.render,\r\n * - save : headerTool.save,\r\n * - displayInToolbox : true,\r\n * - enableLineBreaks : false\r\n */\r\n editor.start = function (userSettings) {\r\n\r\n init();\r\n\r\n editor.core.prepare(userSettings)\r\n\r\n // If all ok, make UI, bind events and parse initial-content\r\n .then(editor.ui.prepare)\r\n .then(editor.tools.prepare)\r\n .then(editor.sanitizer.prepare)\r\n .then(editor.paste.prepare)\r\n .then(editor.transport.prepare)\r\n .then(editor.renderer.makeBlocksFromData)\r\n .then(editor.ui.saveInputs)\r\n .catch(function (error) {\r\n\r\n editor.core.log('Initialization failed with error: %o', 'warn', error);\r\n\r\n });\r\n\r\n };\r\n\r\n return editor;\r\n\r\n})({});\r\n\n\n\n// WEBPACK FOOTER //\n// ./codex.js","/**\r\n * Codex Editor Core\r\n *\r\n * @author Codex Team\r\n * @version 1.1.3\r\n */\r\n\r\nmodule.exports = (function (core) {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * @public\r\n *\r\n * Editor preparing method\r\n * @return Promise\r\n */\r\n core.prepare = function (userSettings) {\r\n\r\n return new Promise(function (resolve, reject) {\r\n\r\n if ( userSettings ) {\r\n\r\n editor.settings.tools = userSettings.tools || editor.settings.tools;\r\n\r\n }\r\n\r\n if (userSettings.data) {\r\n\r\n editor.state.blocks = userSettings.data;\r\n\r\n }\r\n\r\n if (userSettings.initialBlockPlugin) {\r\n\r\n editor.settings.initialBlockPlugin = userSettings.initialBlockPlugin;\r\n\r\n }\r\n\r\n if (userSettings.sanitizer) {\r\n\r\n editor.settings.sanitizer = userSettings.sanitizer;\r\n\r\n }\r\n\r\n editor.hideToolbar = userSettings.hideToolbar;\r\n\r\n editor.nodes.holder = document.getElementById(userSettings.holderId || editor.settings.holderId);\r\n\r\n if (typeof editor.nodes.holder === undefined || editor.nodes.holder === null) {\r\n\r\n reject(Error(\"Holder wasn't found by ID: #\" + userSettings.holderId));\r\n\r\n } else {\r\n\r\n resolve();\r\n\r\n }\r\n\r\n });\r\n\r\n };\r\n\r\n /**\r\n * Logging method\r\n * @param type = ['log', 'info', 'warn']\r\n */\r\n core.log = function (msg, type, arg) {\r\n\r\n type = type || 'log';\r\n\r\n if (!arg) {\r\n\r\n arg = msg || 'undefined';\r\n msg = '[codex-editor]: %o';\r\n\r\n } else {\r\n\r\n msg = '[codex-editor]: ' + msg;\r\n\r\n }\r\n\r\n try{\r\n\r\n if ( 'console' in window && window.console[ type ] ) {\r\n\r\n if ( arg ) window.console[ type ]( msg, arg );\r\n else window.console[ type ]( msg );\r\n\r\n }\r\n\r\n }catch(e) {}\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n *\r\n * Helper for insert one element after another\r\n */\r\n core.insertAfter = function (target, element) {\r\n\r\n target.parentNode.insertBefore(element, target.nextSibling);\r\n\r\n };\r\n\r\n /**\r\n * @const\r\n *\r\n * Readable DOM-node types map\r\n */\r\n core.nodeTypes = {\r\n TAG : 1,\r\n TEXT : 3,\r\n COMMENT : 8\r\n };\r\n\r\n /**\r\n * @const\r\n * Readable keys map\r\n */\r\n core.keys = { BACKSPACE: 8, TAB: 9, ENTER: 13, SHIFT: 16, CTRL: 17, ALT: 18, ESC: 27, SPACE: 32, LEFT: 37, UP: 38, DOWN: 40, RIGHT: 39, DELETE: 46, META: 91 };\r\n\r\n /**\r\n * @protected\r\n *\r\n * Check object for DOM node\r\n */\r\n core.isDomNode = function (el) {\r\n\r\n return el && typeof el === 'object' && el.nodeType && el.nodeType == this.nodeTypes.TAG;\r\n\r\n };\r\n\r\n /**\r\n * Checks passed object for emptiness\r\n * @require ES5 - Object.keys\r\n * @param {object}\r\n */\r\n core.isEmpty = function ( obj ) {\r\n\r\n return Object.keys(obj).length === 0;\r\n\r\n };\r\n\r\n /**\r\n * Native Ajax\r\n * @param {String} settings.url - request URL\r\n * @param {function} settings.beforeSend - returned value will be passed as context to the Success, Error and Progress callbacks\r\n * @param {function} settings.success\r\n * @param {function} settings.progress\r\n */\r\n core.ajax = function (settings) {\r\n\r\n if (!settings || !settings.url) {\r\n\r\n return;\r\n\r\n }\r\n\r\n var XMLHTTP = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'),\r\n encodedString,\r\n isFormData,\r\n prop;\r\n\r\n\r\n settings.async = true;\r\n settings.type = settings.type || 'GET';\r\n settings.data = settings.data || '';\r\n settings['content-type'] = settings['content-type'] || 'application/json; charset=utf-8';\r\n\r\n if (settings.type == 'GET' && settings.data) {\r\n\r\n settings.url = /\\?/.test(settings.url) ? settings.url + '&' + settings.data : settings.url + '?' + settings.data;\r\n\r\n } else {\r\n\r\n encodedString = '';\r\n for(prop in settings.data) {\r\n\r\n encodedString += (prop + '=' + encodeURIComponent(settings.data[prop]) + '&');\r\n\r\n }\r\n\r\n }\r\n\r\n if (settings.withCredentials) {\r\n\r\n XMLHTTP.withCredentials = true;\r\n\r\n }\r\n\r\n /**\r\n * Value returned in beforeSend funtion will be passed as context to the other response callbacks\r\n * If beforeSend returns false, AJAX will be blocked\r\n */\r\n let responseContext,\r\n beforeSendResult;\r\n\r\n if (typeof settings.beforeSend === 'function') {\r\n\r\n beforeSendResult = settings.beforeSend.call();\r\n\r\n if (beforeSendResult === false) {\r\n\r\n return;\r\n\r\n }\r\n\r\n }\r\n\r\n XMLHTTP.open( settings.type, settings.url, settings.async );\r\n\r\n /**\r\n * If we send FormData, we need no content-type header\r\n */\r\n isFormData = isFormData_(settings.data);\r\n\r\n if (!isFormData) {\r\n\r\n if (settings.type !== 'POST') {\r\n\r\n XMLHTTP.setRequestHeader('Content-type', settings['content-type']);\r\n\r\n } else {\r\n\r\n XMLHTTP.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');\r\n\r\n }\r\n\r\n }\r\n\r\n XMLHTTP.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\r\n\r\n responseContext = beforeSendResult || XMLHTTP;\r\n\r\n if (typeof settings.progress === 'function') {\r\n\r\n XMLHTTP.upload.onprogress = settings.progress.bind(responseContext);\r\n\r\n }\r\n\r\n XMLHTTP.onreadystatechange = function () {\r\n\r\n if (XMLHTTP.readyState === 4) {\r\n\r\n if (XMLHTTP.status === 200) {\r\n\r\n if (typeof settings.success === 'function') {\r\n\r\n settings.success.call(responseContext, XMLHTTP.responseText);\r\n\r\n }\r\n\r\n } else {\r\n\r\n if (typeof settings.error === 'function') {\r\n\r\n settings.error.call(responseContext, XMLHTTP.responseText, XMLHTTP.status);\r\n\r\n }\r\n\r\n }\r\n\r\n }\r\n\r\n };\r\n\r\n if (isFormData) {\r\n\r\n // Sending FormData\r\n XMLHTTP.send(settings.data);\r\n\r\n } else {\r\n\r\n // POST requests\r\n XMLHTTP.send(encodedString);\r\n\r\n }\r\n\r\n return XMLHTTP;\r\n\r\n };\r\n\r\n /**\r\n * Appends script to head of document\r\n * @return Promise\r\n */\r\n core.importScript = function (scriptPath, instanceName) {\r\n\r\n return new Promise(function (resolve, reject) {\r\n\r\n let script;\r\n\r\n /** Script is already loaded */\r\n if ( !instanceName ) {\r\n\r\n reject('Instance name is missed');\r\n\r\n } else if ( document.getElementById(editor.scriptPrefix + instanceName) ) {\r\n\r\n resolve(scriptPath);\r\n\r\n }\r\n\r\n script = document.createElement('SCRIPT');\r\n script.async = true;\r\n script.defer = true;\r\n script.id = editor.scriptPrefix + instanceName;\r\n\r\n script.onload = function () {\r\n\r\n resolve(scriptPath);\r\n\r\n };\r\n\r\n script.onerror = function () {\r\n\r\n reject(scriptPath);\r\n\r\n };\r\n\r\n script.src = scriptPath;\r\n document.head.appendChild(script);\r\n\r\n });\r\n\r\n };\r\n\r\n /**\r\n * Function for checking is it FormData object to send.\r\n * @param {Object} object to check\r\n * @return boolean\r\n */\r\n var isFormData_ = function (object) {\r\n\r\n return object instanceof FormData;\r\n\r\n };\r\n\r\n /**\r\n * Check block\r\n * @param target\r\n * @description Checks target is it native input\r\n */\r\n core.isNativeInput = function (target) {\r\n\r\n var nativeInputAreas = ['INPUT', 'TEXTAREA'];\r\n\r\n return nativeInputAreas.indexOf(target.tagName) != -1;\r\n\r\n };\r\n\r\n\r\n return core;\r\n\r\n})({});\r\n\n\n\n// WEBPACK FOOTER //\n// ./modules/core.js","/**\r\n* Module working with plugins\r\n*/\r\nmodule.exports = (function () {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * Initialize plugins before using\r\n * Ex. Load scripts or call some internal methods\r\n * @return Promise\r\n */\r\n function prepare() {\r\n\r\n return new Promise(function (resolve_, reject_) {\r\n\r\n Promise.resolve()\r\n\r\n /**\r\n * Compose a sequence of plugins that requires preparation\r\n */\r\n .then(function () {\r\n\r\n let pluginsRequiresPreparation = [],\r\n allPlugins = editor.tools;\r\n\r\n for ( let pluginName in allPlugins ) {\r\n\r\n let plugin = allPlugins[pluginName];\r\n\r\n if (plugin.prepare && typeof plugin.prepare != 'function' || !plugin.prepare) {\r\n\r\n continue;\r\n\r\n }\r\n\r\n pluginsRequiresPreparation.push(plugin);\r\n\r\n }\r\n\r\n /**\r\n * If no one passed plugins requires preparation, finish prepare() and go ahead\r\n */\r\n if (!pluginsRequiresPreparation.length) {\r\n\r\n resolve_();\r\n\r\n }\r\n\r\n return pluginsRequiresPreparation;\r\n\r\n })\r\n\r\n /** Wait plugins while they prepares */\r\n .then(waitAllPluginsPreparation_)\r\n\r\n .then(function () {\r\n\r\n editor.core.log('Plugins loaded', 'info');\r\n resolve_();\r\n\r\n }).catch(function (error) {\r\n\r\n reject_(error);\r\n\r\n });\r\n\r\n });\r\n\r\n }\r\n\r\n /**\r\n * @param {array} plugins - list of tools that requires preparation\r\n * @return {Promise} resolved while all plugins will be ready or failed\r\n */\r\n function waitAllPluginsPreparation_(plugins) {\r\n\r\n /**\r\n * @calls allPluginsProcessed__ when all plugins prepared or failed\r\n */\r\n return new Promise (function (allPluginsProcessed__) {\r\n\r\n /**\r\n * pluck each element from queue\r\n * First, send resolved Promise as previous value\r\n * Each plugins \"prepare\" method returns a Promise, that's why\r\n * reduce current element will not be able to continue while can't get\r\n * a resolved Promise\r\n *\r\n * If last plugin is \"prepared\" then go to the next stage of initialization\r\n */\r\n plugins.reduce(function (previousValue, plugin, iteration) {\r\n\r\n return previousValue.then(function () {\r\n\r\n /**\r\n * Wait till plugins prepared\r\n * @calls pluginIsReady__ when plugin is ready or failed\r\n */\r\n return new Promise ( function (pluginIsReady__) {\r\n\r\n callPluginsPrepareMethod_( plugin )\r\n\r\n .then( pluginIsReady__ )\r\n .then( function () {\r\n\r\n plugin.available = true;\r\n\r\n })\r\n\r\n .catch(function (error) {\r\n\r\n editor.core.log(`Plugin «${plugin.type}» was not loaded. Preparation failed because %o`, 'warn', error);\r\n plugin.available = false;\r\n plugin.loadingMessage = error;\r\n\r\n /** Go ahead even some plugin has problems */\r\n pluginIsReady__();\r\n\r\n })\r\n\r\n .then(function () {\r\n\r\n /** If last plugin has problems then just ignore and continue */\r\n if (iteration == plugins.length - 1) {\r\n\r\n allPluginsProcessed__();\r\n\r\n }\r\n\r\n });\r\n\r\n });\r\n\r\n });\r\n\r\n }, Promise.resolve() );\r\n\r\n });\r\n\r\n }\r\n\r\n var callPluginsPrepareMethod_ = function (plugin) {\r\n\r\n return plugin.prepare( plugin.config || {} );\r\n\r\n };\r\n\r\n return {\r\n prepare: prepare\r\n };\r\n\r\n}());\n\n\n// WEBPACK FOOTER //\n// ./modules/tools.js","/**\r\n * Codex Editor UI module\r\n *\r\n * @author Codex Team\r\n * @version 1.2.0\r\n */\r\n\r\nmodule.exports = (function (ui) {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * Basic editor classnames\r\n */\r\n ui.className = {\r\n\r\n /**\r\n * @const {string} BLOCK_CLASSNAME - redactor blocks name\r\n */\r\n BLOCK_CLASSNAME : 'ce-block',\r\n\r\n /**\r\n * @const {String} wrapper for plugins content\r\n */\r\n BLOCK_CONTENT : 'ce-block__content',\r\n\r\n /**\r\n * @const {String} BLOCK_STRETCHED - makes block stretched\r\n */\r\n BLOCK_STRETCHED : 'ce-block--stretched',\r\n\r\n /**\r\n * @const {String} BLOCK_HIGHLIGHTED - adds background\r\n */\r\n BLOCK_HIGHLIGHTED : 'ce-block--focused',\r\n\r\n /**\r\n * @const {String} - for all default settings\r\n */\r\n SETTINGS_ITEM : 'ce-settings__item'\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n *\r\n * Making main interface\r\n */\r\n ui.prepare = function () {\r\n\r\n return new Promise(function (resolve) {\r\n\r\n let wrapper = editor.draw.wrapper(),\r\n redactor = editor.draw.redactor(),\r\n toolbar = makeToolBar_();\r\n\r\n wrapper.appendChild(toolbar);\r\n wrapper.appendChild(redactor);\r\n\r\n /** Save created ui-elements to static nodes state */\r\n editor.nodes.wrapper = wrapper;\r\n editor.nodes.redactor = redactor;\r\n\r\n /** Append editor wrapper with redactor zone into holder */\r\n editor.nodes.holder.appendChild(wrapper);\r\n\r\n resolve();\r\n\r\n })\r\n\r\n /** Add toolbox tools */\r\n .then(addTools_)\r\n\r\n /** Make container for inline toolbar */\r\n .then(makeInlineToolbar_)\r\n\r\n /** Add inline toolbar tools */\r\n .then(addInlineToolbarTools_)\r\n\r\n /** Draw wrapper for notifications */\r\n .then(makeNotificationHolder_)\r\n\r\n /** Add eventlisteners to redactor elements */\r\n .then(bindEvents_)\r\n\r\n .catch( function () {\r\n\r\n editor.core.log(\"Can't draw editor interface\");\r\n\r\n });\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n * Draws inline toolbar zone\r\n */\r\n var makeInlineToolbar_ = function () {\r\n\r\n var container = editor.draw.inlineToolbar();\r\n\r\n /** Append to redactor new inline block */\r\n editor.nodes.inlineToolbar.wrapper = container;\r\n\r\n /** Draw toolbar buttons */\r\n editor.nodes.inlineToolbar.buttons = editor.draw.inlineToolbarButtons();\r\n\r\n /** Buttons action or settings */\r\n editor.nodes.inlineToolbar.actions = editor.draw.inlineToolbarActions();\r\n\r\n /** Append to inline toolbar buttons as part of it */\r\n editor.nodes.inlineToolbar.wrapper.appendChild(editor.nodes.inlineToolbar.buttons);\r\n editor.nodes.inlineToolbar.wrapper.appendChild(editor.nodes.inlineToolbar.actions);\r\n\r\n editor.nodes.wrapper.appendChild(editor.nodes.inlineToolbar.wrapper);\r\n\r\n };\r\n\r\n var makeToolBar_ = function () {\r\n\r\n let toolbar = editor.draw.toolbar(),\r\n blockButtons = makeToolbarSettings_(),\r\n toolbarContent = makeToolbarContent_();\r\n\r\n /** Appending first-level block buttons */\r\n toolbar.appendChild(blockButtons);\r\n\r\n /** Append toolbarContent to toolbar */\r\n toolbar.appendChild(toolbarContent);\r\n\r\n /** Make toolbar global */\r\n editor.nodes.toolbar = toolbar;\r\n\r\n return toolbar;\r\n\r\n };\r\n\r\n var makeToolbarContent_ = function () {\r\n\r\n let toolbarContent = editor.draw.toolbarContent(),\r\n toolbox = editor.draw.toolbox(),\r\n plusButton = editor.draw.plusButton();\r\n\r\n /** Append plus button */\r\n toolbarContent.appendChild(plusButton);\r\n\r\n /** Appending toolbar tools */\r\n toolbarContent.appendChild(toolbox);\r\n\r\n /** Make Toolbox and plusButton global */\r\n editor.nodes.toolbox = toolbox;\r\n editor.nodes.plusButton = plusButton;\r\n\r\n return toolbarContent;\r\n\r\n };\r\n\r\n var makeToolbarSettings_ = function () {\r\n\r\n let blockSettings = editor.draw.blockSettings(),\r\n blockButtons = editor.draw.blockButtons(),\r\n defaultSettings = editor.draw.defaultSettings(),\r\n showSettingsButton = editor.draw.settingsButton(),\r\n showTrashButton = editor.toolbar.settings.makeRemoveBlockButton(),\r\n pluginSettings = editor.draw.pluginsSettings();\r\n\r\n /** Add default and plugins settings */\r\n blockSettings.appendChild(pluginSettings);\r\n blockSettings.appendChild(defaultSettings);\r\n\r\n /**\r\n * Make blocks buttons\r\n * This block contains settings button and remove block button\r\n */\r\n blockButtons.appendChild(showSettingsButton);\r\n blockButtons.appendChild(showTrashButton);\r\n blockButtons.appendChild(blockSettings);\r\n\r\n /** Make BlockSettings, PluginSettings, DefaultSettings global */\r\n editor.nodes.blockSettings = blockSettings;\r\n editor.nodes.pluginSettings = pluginSettings;\r\n editor.nodes.defaultSettings = defaultSettings;\r\n editor.nodes.showSettingsButton = showSettingsButton;\r\n editor.nodes.showTrashButton = showTrashButton;\r\n\r\n return blockButtons;\r\n\r\n };\r\n\r\n /** Draw notifications holder */\r\n var makeNotificationHolder_ = function () {\r\n\r\n /** Append block with notifications to the document */\r\n editor.nodes.notifications = editor.notifications.createHolder();\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n * Append tools passed in editor.tools\r\n */\r\n var addTools_ = function () {\r\n\r\n var tool,\r\n toolName,\r\n toolButton;\r\n\r\n for ( toolName in editor.settings.tools ) {\r\n\r\n tool = editor.settings.tools[toolName];\r\n\r\n editor.tools[toolName] = tool;\r\n\r\n if (!tool.iconClassname && tool.displayInToolbox) {\r\n\r\n editor.core.log('Toolbar icon classname missed. Tool %o skipped', 'warn', toolName);\r\n continue;\r\n\r\n }\r\n\r\n if (typeof tool.render != 'function') {\r\n\r\n editor.core.log('render method missed. Tool %o skipped', 'warn', toolName);\r\n continue;\r\n\r\n }\r\n\r\n if (!tool.displayInToolbox) {\r\n\r\n continue;\r\n\r\n } else {\r\n\r\n /** if tools is for toolbox */\r\n toolButton = editor.draw.toolbarButton(toolName, tool.iconClassname);\r\n\r\n editor.nodes.toolbox.appendChild(toolButton);\r\n\r\n editor.nodes.toolbarButtons[toolName] = toolButton;\r\n\r\n }\r\n\r\n }\r\n\r\n };\r\n\r\n var addInlineToolbarTools_ = function () {\r\n\r\n var tools = {\r\n\r\n bold: {\r\n icon : 'ce-icon-bold',\r\n command : 'bold'\r\n },\r\n\r\n italic: {\r\n icon : 'ce-icon-italic',\r\n command : 'italic'\r\n },\r\n\r\n underline: {\r\n icon : 'ce-icon-underline',\r\n command : 'underline'\r\n },\r\n\r\n link: {\r\n icon : 'ce-icon-link',\r\n command : 'createLink'\r\n }\r\n };\r\n\r\n var toolButton,\r\n tool;\r\n\r\n for(var name in tools) {\r\n\r\n tool = tools[name];\r\n\r\n toolButton = editor.draw.toolbarButtonInline(name, tool.icon);\r\n\r\n editor.nodes.inlineToolbar.buttons.appendChild(toolButton);\r\n /**\r\n * Add callbacks to this buttons\r\n */\r\n editor.ui.setInlineToolbarButtonBehaviour(toolButton, tool.command);\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n * Bind editor UI events\r\n */\r\n var bindEvents_ = function () {\r\n\r\n editor.core.log('ui.bindEvents fired', 'info');\r\n\r\n // window.addEventListener('error', function (errorMsg, url, lineNumber) {\r\n // editor.notifications.errorThrown(errorMsg, event);\r\n // }, false );\r\n\r\n /** All keydowns on Document */\r\n editor.listeners.add(document, 'keydown', editor.callback.globalKeydown, false);\r\n\r\n /** All keydowns on Redactor zone */\r\n editor.listeners.add(editor.nodes.redactor, 'keydown', editor.callback.redactorKeyDown, false);\r\n\r\n /** All keydowns on Document */\r\n editor.listeners.add(document, 'keyup', editor.callback.globalKeyup, false );\r\n\r\n /**\r\n * Mouse click to radactor\r\n */\r\n editor.listeners.add(editor.nodes.redactor, 'click', editor.callback.redactorClicked, false );\r\n\r\n /**\r\n * Clicks to the Plus button\r\n */\r\n editor.listeners.add(editor.nodes.plusButton, 'click', editor.callback.plusButtonClicked, false);\r\n\r\n /**\r\n * Clicks to SETTINGS button in toolbar\r\n */\r\n editor.listeners.add(editor.nodes.showSettingsButton, 'click', editor.callback.showSettingsButtonClicked, false );\r\n\r\n /** Bind click listeners on toolbar buttons */\r\n for (var button in editor.nodes.toolbarButtons) {\r\n\r\n editor.listeners.add(editor.nodes.toolbarButtons[button], 'click', editor.callback.toolbarButtonClicked, false);\r\n\r\n }\r\n\r\n };\r\n\r\n ui.addBlockHandlers = function (block) {\r\n\r\n if (!block) return;\r\n\r\n /**\r\n * Block keydowns\r\n */\r\n editor.listeners.add(block, 'keydown', editor.callback.blockKeydown, false);\r\n\r\n /**\r\n * Pasting content from another source\r\n * We have two type of sanitization\r\n * First - uses deep-first search algorithm to get sub nodes,\r\n * sanitizes whole Block_content and replaces cleared nodes\r\n * This method is deprecated\r\n * Method is used in editor.callback.blockPaste(event)\r\n *\r\n * Secont - uses Mutation observer.\r\n * Observer \"observe\" DOM changes and send changings to callback.\r\n * Callback gets changed node, not whole Block_content.\r\n * Inserted or changed node, which we've gotten have been cleared and replaced with diry node\r\n *\r\n * Method is used in editor.callback.blockPasteViaSanitize(event)\r\n *\r\n * @uses html-janitor\r\n * @example editor.callback.blockPasteViaSanitize(event), the second method.\r\n *\r\n */\r\n editor.listeners.add(block, 'paste', editor.paste.blockPasteCallback, false);\r\n\r\n editor.listeners.add(block, 'mouseup', editor.toolbar.inline.show, false);\r\n\r\n };\r\n\r\n /** getting all contenteditable elements */\r\n ui.saveInputs = function () {\r\n\r\n var redactor = editor.nodes.redactor;\r\n\r\n editor.state.inputs = [];\r\n\r\n /** Save all inputs in global variable state */\r\n var inputs = redactor.querySelectorAll('[contenteditable], input, textarea');\r\n\r\n Array.prototype.map.call(inputs, function (current) {\r\n\r\n if (!current.type || current.type == 'text' || current.type == 'textarea') {\r\n\r\n editor.state.inputs.push(current);\r\n\r\n }\r\n\r\n });\r\n\r\n };\r\n\r\n /**\r\n * Adds first initial block on empty redactor\r\n */\r\n ui.addInitialBlock = function () {\r\n\r\n var initialBlockType = editor.settings.initialBlockPlugin,\r\n initialBlock;\r\n\r\n if ( !editor.tools[initialBlockType] ) {\r\n\r\n editor.core.log('Plugin %o was not implemented and can\\'t be used as initial block', 'warn', initialBlockType);\r\n return;\r\n\r\n }\r\n\r\n initialBlock = editor.tools[initialBlockType].render();\r\n\r\n initialBlock.setAttribute('data-placeholder', 'Расскажите свою историю...');\r\n\r\n editor.content.insertBlock({\r\n type : initialBlockType,\r\n block : initialBlock\r\n });\r\n\r\n editor.content.workingNodeChanged(initialBlock);\r\n\r\n };\r\n\r\n ui.setInlineToolbarButtonBehaviour = function (button, type) {\r\n\r\n editor.listeners.add(button, 'mousedown', function (event) {\r\n\r\n editor.toolbar.inline.toolClicked(event, type);\r\n\r\n }, false);\r\n\r\n };\r\n\r\n return ui;\r\n\r\n})({});\r\n\n\n\n// WEBPACK FOOTER //\n// ./modules/ui.js","/**\r\n *\r\n * Codex.Editor Transport Module\r\n *\r\n * @copyright 2017 Codex-Team\r\n * @version 1.2.0\r\n */\r\n\r\nmodule.exports = (function (transport) {\r\n\r\n let editor = codex.editor;\r\n\r\n\r\n /**\r\n * @private {Object} current XmlHttpRequest instance\r\n */\r\n var currentRequest = null;\r\n\r\n\r\n /**\r\n * @type {null} | {DOMElement} input - keeps input element in memory\r\n */\r\n transport.input = null;\r\n\r\n /**\r\n * @property {Object} arguments - keep plugin settings and defined callbacks\r\n */\r\n transport.arguments = null;\r\n\r\n /**\r\n * Prepares input element where will be files\r\n */\r\n transport.prepare = function () {\r\n\r\n let input = editor.draw.node( 'INPUT', '', { type : 'file' } );\r\n\r\n editor.listeners.add(input, 'change', editor.transport.fileSelected);\r\n editor.transport.input = input;\r\n\r\n };\r\n\r\n /** Clear input when files is uploaded */\r\n transport.clearInput = function () {\r\n\r\n /** Remove old input */\r\n transport.input = null;\r\n\r\n /** Prepare new one */\r\n transport.prepare();\r\n\r\n };\r\n\r\n /**\r\n * Callback for file selection\r\n * @param {Event} event\r\n */\r\n transport.fileSelected = function () {\r\n\r\n var input = this,\r\n i,\r\n files = input.files,\r\n formData = new FormData();\r\n\r\n if (editor.transport.arguments.multiple === true) {\r\n\r\n for ( i = 0; i < files.length; i++) {\r\n\r\n formData.append('files[]', files[i], files[i].name);\r\n\r\n }\r\n\r\n } else {\r\n\r\n formData.append('files', files[0], files[0].name);\r\n\r\n }\r\n\r\n currentRequest = editor.core.ajax({\r\n type : 'POST',\r\n data : formData,\r\n url : editor.transport.arguments.url,\r\n beforeSend : editor.transport.arguments.beforeSend,\r\n success : editor.transport.arguments.success,\r\n error : editor.transport.arguments.error,\r\n progress : editor.transport.arguments.progress\r\n });\r\n\r\n /** Clear input */\r\n transport.clearInput();\r\n\r\n };\r\n\r\n /**\r\n * Use plugin callbacks\r\n * @protected\r\n *\r\n * @param {Object} args - can have :\r\n * @param {String} args.url - fetch URL\r\n * @param {Function} args.beforeSend - function calls before sending ajax\r\n * @param {Function} args.success - success callback\r\n * @param {Function} args.error - on error handler\r\n * @param {Function} args.progress - xhr onprogress handler\r\n * @param {Boolean} args.multiple - allow select several files\r\n * @param {String} args.accept - adds accept attribute\r\n */\r\n transport.selectAndUpload = function (args) {\r\n\r\n transport.arguments = args;\r\n\r\n if ( args.multiple === true) {\r\n\r\n transport.input.setAttribute('multiple', 'multiple');\r\n\r\n }\r\n\r\n if ( args.accept ) {\r\n\r\n transport.input.setAttribute('accept', args.accept);\r\n\r\n }\r\n\r\n transport.input.click();\r\n\r\n };\r\n\r\n transport.abort = function () {\r\n\r\n currentRequest.abort();\r\n\r\n currentRequest = null;\r\n\r\n };\r\n\r\n return transport;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/transport.js","/**\r\n * Codex Editor Renderer Module\r\n *\r\n * @author Codex Team\r\n * @version 1.0\r\n */\r\n\r\nmodule.exports = (function (renderer) {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * Asyncronously parses input JSON to redactor blocks\r\n */\r\n renderer.makeBlocksFromData = function () {\r\n\r\n /**\r\n * If redactor is empty, add first paragraph to start writing\r\n */\r\n if (editor.core.isEmpty(editor.state.blocks) || !editor.state.blocks.items.length) {\r\n\r\n editor.ui.addInitialBlock();\r\n return;\r\n\r\n }\r\n\r\n Promise.resolve()\r\n\r\n /** First, get JSON from state */\r\n .then(function () {\r\n\r\n return editor.state.blocks;\r\n\r\n })\r\n\r\n /** Then, start to iterate they */\r\n .then(editor.renderer.appendBlocks)\r\n\r\n /** Write log if something goes wrong */\r\n .catch(function (error) {\r\n\r\n editor.core.log('Error while parsing JSON: %o', 'error', error);\r\n\r\n });\r\n\r\n };\r\n\r\n /**\r\n * Parses JSON to blocks\r\n * @param {object} data\r\n * @return Primise -> nodeList\r\n */\r\n renderer.appendBlocks = function (data) {\r\n\r\n var blocks = data.items;\r\n\r\n /**\r\n * Sequence of one-by-one blocks appending\r\n * Uses to save blocks order after async-handler\r\n */\r\n var nodeSequence = Promise.resolve();\r\n\r\n for (var index = 0; index < blocks.length ; index++ ) {\r\n\r\n /** Add node to sequence at specified index */\r\n editor.renderer.appendNodeAtIndex(nodeSequence, blocks, index);\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Append node at specified index\r\n */\r\n renderer.appendNodeAtIndex = function (nodeSequence, blocks, index) {\r\n\r\n /** We need to append node to sequence */\r\n nodeSequence\r\n\r\n /** first, get node async-aware */\r\n .then(function () {\r\n\r\n return editor.renderer.getNodeAsync(blocks, index);\r\n\r\n })\r\n\r\n /**\r\n * second, compose editor-block from JSON object\r\n */\r\n .then(editor.renderer.createBlockFromData)\r\n\r\n /**\r\n * now insert block to redactor\r\n */\r\n .then(function (blockData) {\r\n\r\n /**\r\n * blockData has 'block', 'type' and 'stretched' information\r\n */\r\n editor.content.insertBlock(blockData);\r\n\r\n /** Pass created block to next step */\r\n return blockData.block;\r\n\r\n })\r\n\r\n /** Log if something wrong with node */\r\n .catch(function (error) {\r\n\r\n editor.core.log('Node skipped while parsing because %o', 'error', error);\r\n\r\n });\r\n\r\n };\r\n\r\n /**\r\n * Asynchronously returns block data from blocksList by index\r\n * @return Promise to node\r\n */\r\n renderer.getNodeAsync = function (blocksList, index) {\r\n\r\n return Promise.resolve().then(function () {\r\n\r\n return {\r\n tool : blocksList[index],\r\n position : index\r\n };\r\n\r\n });\r\n\r\n };\r\n\r\n /**\r\n * Creates editor block by JSON-data\r\n *\r\n * @uses render method of each plugin\r\n *\r\n * @param {Object} toolData.tool\r\n * { header : {\r\n * text: '',\r\n * type: 'H3', ...\r\n * }\r\n * }\r\n * @param {Number} toolData.position - index in input-blocks array\r\n * @return {Object} with type and Element\r\n */\r\n renderer.createBlockFromData = function ( toolData ) {\r\n\r\n /** New parser */\r\n var block,\r\n tool = toolData.tool,\r\n pluginName = tool.type;\r\n\r\n /** Get first key of object that stores plugin name */\r\n // for (var pluginName in blockData) break;\r\n\r\n /** Check for plugin existance */\r\n if (!editor.tools[pluginName]) {\r\n\r\n throw Error(`Plugin «${pluginName}» not found`);\r\n\r\n }\r\n\r\n /** Check for plugin having render method */\r\n if (typeof editor.tools[pluginName].render != 'function') {\r\n\r\n throw Error(`Plugin «${pluginName}» must have «render» method`);\r\n\r\n }\r\n\r\n if ( editor.tools[pluginName].available === false ) {\r\n\r\n block = editor.draw.unavailableBlock();\r\n\r\n block.innerHTML = editor.tools[pluginName].loadingMessage;\r\n\r\n /**\r\n * Saver will extract data from initial block data by position in array\r\n */\r\n block.dataset.inputPosition = toolData.position;\r\n\r\n } else {\r\n\r\n /** New Parser */\r\n block = editor.tools[pluginName].render(tool.data);\r\n\r\n }\r\n\r\n /** is first-level block stretched */\r\n var stretched = editor.tools[pluginName].isStretched || false;\r\n\r\n /** Retrun type and block */\r\n return {\r\n type : pluginName,\r\n block : block,\r\n stretched : stretched\r\n };\r\n\r\n };\r\n\r\n return renderer;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/renderer.js","/**\r\n * Codex Editor Saver\r\n *\r\n * @author Codex Team\r\n * @version 1.0.2\r\n */\r\n\r\nmodule.exports = (function (saver) {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * Saves blocks\r\n * @private\r\n */\r\n saver.saveBlocks = function () {\r\n\r\n /** Save html content of redactor to memory */\r\n editor.state.html = editor.nodes.redactor.innerHTML;\r\n\r\n /** Empty jsonOutput state */\r\n editor.state.jsonOutput = [];\r\n\r\n Promise.resolve()\r\n\r\n .then(function () {\r\n\r\n return editor.nodes.redactor.childNodes;\r\n\r\n })\r\n /** Making a sequence from separate blocks */\r\n .then(editor.saver.makeQueue)\r\n\r\n .then(function () {\r\n // editor.nodes.textarea.innerHTML = editor.state.html;\r\n })\r\n\r\n .catch( function (error) {\r\n\r\n editor.core.log(error);\r\n\r\n });\r\n\r\n };\r\n\r\n saver.makeQueue = function (blocks) {\r\n\r\n var queue = Promise.resolve();\r\n\r\n for(var index = 0; index < blocks.length; index++) {\r\n\r\n /** Add node to sequence at specified index */\r\n editor.saver.getBlockData(queue, blocks, index);\r\n\r\n }\r\n\r\n };\r\n\r\n /** Gets every block and makes From Data */\r\n saver.getBlockData = function (queue, blocks, index) {\r\n\r\n queue.then(function () {\r\n\r\n return editor.saver.getNodeAsync(blocks, index);\r\n\r\n })\r\n\r\n .then(editor.saver.makeFormDataFromBlocks);\r\n\r\n };\r\n\r\n\r\n /**\r\n * Asynchronously returns block data from blocksList by index\r\n * @return Promise to node\r\n */\r\n saver.getNodeAsync = function (blocksList, index) {\r\n\r\n return Promise.resolve().then(function () {\r\n\r\n return blocksList[index];\r\n\r\n });\r\n\r\n };\r\n\r\n saver.makeFormDataFromBlocks = function (block) {\r\n\r\n var pluginName = block.dataset.tool;\r\n\r\n /** Check for plugin existance */\r\n if (!editor.tools[pluginName]) {\r\n\r\n throw Error(`Plugin «${pluginName}» not found`);\r\n\r\n }\r\n\r\n /** Check for plugin having render method */\r\n if (typeof editor.tools[pluginName].save != 'function') {\r\n\r\n throw Error(`Plugin «${pluginName}» must have save method`);\r\n\r\n }\r\n\r\n /** Result saver */\r\n var blockContent = block.childNodes[0],\r\n pluginsContent = blockContent.childNodes[0],\r\n savedData,\r\n position,\r\n output;\r\n\r\n /** If plugin wasn't available then return data from cache */\r\n if ( editor.tools[pluginName].available === false ) {\r\n\r\n position = pluginsContent.dataset.inputPosition;\r\n savedData = codex.editor.state.blocks.items[position].data;\r\n\r\n } else {\r\n\r\n savedData = editor.tools[pluginName].save(pluginsContent);\r\n\r\n if (editor.tools[pluginName].validate) {\r\n\r\n var result = editor.tools[pluginName].validate(savedData);\r\n\r\n /**\r\n * Do not allow invalid data\r\n */\r\n if (!result)\r\n return;\r\n\r\n }\r\n\r\n }\r\n\r\n output = {\r\n type : pluginName,\r\n data : savedData\r\n };\r\n\r\n editor.state.jsonOutput.push(output);\r\n\r\n };\r\n\r\n return saver;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/saver.js","/**\r\n * Codex Editor Content Module\r\n * Works with DOM\r\n *\r\n * @module Codex Editor content module\r\n *\r\n * @author Codex Team\r\n * @version 1.3.13\r\n *\r\n * @description Module works with Elements that have been appended to the main DOM\r\n */\r\n\r\nmodule.exports = (function (content) {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * Links to current active block\r\n * @type {null | Element}\r\n */\r\n content.currentNode = null;\r\n\r\n /**\r\n * clicked in redactor area\r\n * @type {null | Boolean}\r\n */\r\n content.editorAreaHightlighted = null;\r\n\r\n /**\r\n * @deprecated\r\n * Synchronizes redactor with original textarea\r\n */\r\n content.sync = function () {\r\n\r\n editor.core.log('syncing...');\r\n\r\n /**\r\n * Save redactor content to editor.state\r\n */\r\n editor.state.html = editor.nodes.redactor.innerHTML;\r\n\r\n };\r\n\r\n /**\r\n * Appends background to the block\r\n *\r\n * @description add CSS class to highlight visually first-level block area\r\n */\r\n content.markBlock = function () {\r\n\r\n editor.content.currentNode.classList.add(editor.ui.className.BLOCK_HIGHLIGHTED);\r\n\r\n };\r\n\r\n /**\r\n * Clear background\r\n *\r\n * @description clears styles that highlights block\r\n */\r\n content.clearMark = function () {\r\n\r\n if (editor.content.currentNode) {\r\n\r\n editor.content.currentNode.classList.remove(editor.ui.className.BLOCK_HIGHLIGHTED);\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Finds first-level block\r\n *\r\n * @param {Element} node - selected or clicked in redactors area node\r\n * @protected\r\n *\r\n * @description looks for first-level block.\r\n * gets parent while node is not first-level\r\n */\r\n content.getFirstLevelBlock = function (node) {\r\n\r\n if (!editor.core.isDomNode(node)) {\r\n\r\n node = node.parentNode;\r\n\r\n }\r\n\r\n if (node === editor.nodes.redactor || node === document.body) {\r\n\r\n return null;\r\n\r\n } else {\r\n\r\n while(!node.classList.contains(editor.ui.className.BLOCK_CLASSNAME)) {\r\n\r\n node = node.parentNode;\r\n\r\n }\r\n\r\n return node;\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Trigger this event when working node changed\r\n * @param {Element} targetNode - first-level of this node will be current\r\n * @protected\r\n *\r\n * @description If targetNode is first-level then we set it as current else we look for parents to find first-level\r\n */\r\n content.workingNodeChanged = function (targetNode) {\r\n\r\n /** Clear background from previous marked block before we change */\r\n editor.content.clearMark();\r\n\r\n if (!targetNode) {\r\n\r\n return;\r\n\r\n }\r\n\r\n content.currentNode = content.getFirstLevelBlock(targetNode);\r\n\r\n };\r\n\r\n /**\r\n * Replaces one redactor block with another\r\n * @protected\r\n * @param {Element} targetBlock - block to replace. Mostly currentNode.\r\n * @param {Element} newBlock\r\n * @param {string} newBlockType - type of new block; we need to store it to data-attribute\r\n *\r\n * [!] Function does not saves old block content.\r\n * You can get it manually and pass with newBlock.innerHTML\r\n */\r\n content.replaceBlock = function (targetBlock, newBlock) {\r\n\r\n if (!targetBlock || !newBlock) {\r\n\r\n editor.core.log('replaceBlock: missed params');\r\n return;\r\n\r\n }\r\n\r\n /** If target-block is not a frist-level block, then we iterate parents to find it */\r\n while(!targetBlock.classList.contains(editor.ui.className.BLOCK_CLASSNAME)) {\r\n\r\n targetBlock = targetBlock.parentNode;\r\n\r\n }\r\n\r\n /** Replacing */\r\n editor.nodes.redactor.replaceChild(newBlock, targetBlock);\r\n\r\n /**\r\n * Set new node as current\r\n */\r\n editor.content.workingNodeChanged(newBlock);\r\n\r\n /**\r\n * Add block handlers\r\n */\r\n editor.ui.addBlockHandlers(newBlock);\r\n\r\n /**\r\n * Save changes\r\n */\r\n editor.ui.saveInputs();\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n *\r\n * Inserts new block to redactor\r\n * Wrapps block into a DIV with BLOCK_CLASSNAME class\r\n *\r\n * @param blockData {object}\r\n * @param blockData.block {Element} element with block content\r\n * @param blockData.type {string} block plugin\r\n * @param needPlaceCaret {bool} pass true to set caret in new block\r\n *\r\n */\r\n content.insertBlock = function ( blockData, needPlaceCaret ) {\r\n\r\n var workingBlock = editor.content.currentNode,\r\n newBlockContent = blockData.block,\r\n blockType = blockData.type,\r\n isStretched = blockData.stretched;\r\n\r\n var newBlock = composeNewBlock_(newBlockContent, blockType, isStretched);\r\n\r\n if (workingBlock) {\r\n\r\n editor.core.insertAfter(workingBlock, newBlock);\r\n\r\n } else {\r\n\r\n /**\r\n * If redactor is empty, append as first child\r\n */\r\n editor.nodes.redactor.appendChild(newBlock);\r\n\r\n }\r\n\r\n /**\r\n * Block handler\r\n */\r\n editor.ui.addBlockHandlers(newBlock);\r\n\r\n /**\r\n * Set new node as current\r\n */\r\n editor.content.workingNodeChanged(newBlock);\r\n\r\n /**\r\n * Save changes\r\n */\r\n editor.ui.saveInputs();\r\n\r\n\r\n if ( needPlaceCaret ) {\r\n\r\n /**\r\n * If we don't know input index then we set default value -1\r\n */\r\n var currentInputIndex = editor.caret.getCurrentInputIndex() || -1;\r\n\r\n\r\n if (currentInputIndex == -1) {\r\n\r\n\r\n var editableElement = newBlock.querySelector('[contenteditable]'),\r\n emptyText = document.createTextNode('');\r\n\r\n editableElement.appendChild(emptyText);\r\n editor.caret.set(editableElement, 0, 0);\r\n\r\n editor.toolbar.move();\r\n editor.toolbar.showPlusButton();\r\n\r\n\r\n } else {\r\n\r\n if (currentInputIndex === editor.state.inputs.length - 1)\r\n return;\r\n\r\n /** Timeout for browsers execution */\r\n window.setTimeout(function () {\r\n\r\n /** Setting to the new input */\r\n editor.caret.setToNextBlock(currentInputIndex);\r\n editor.toolbar.move();\r\n editor.toolbar.open();\r\n\r\n }, 10);\r\n\r\n }\r\n\r\n }\r\n\r\n /**\r\n * Block is inserted, wait for new click that defined focusing on editors area\r\n * @type {boolean}\r\n */\r\n content.editorAreaHightlighted = false;\r\n\r\n };\r\n\r\n /**\r\n * Replaces blocks with saving content\r\n * @protected\r\n * @param {Element} noteToReplace\r\n * @param {Element} newNode\r\n * @param {Element} blockType\r\n */\r\n content.switchBlock = function (blockToReplace, newBlock, tool) {\r\n\r\n tool = tool || editor.content.currentNode.dataset.tool;\r\n var newBlockComposed = composeNewBlock_(newBlock, tool);\r\n\r\n /** Replacing */\r\n editor.content.replaceBlock(blockToReplace, newBlockComposed);\r\n\r\n /** Save new Inputs when block is changed */\r\n editor.ui.saveInputs();\r\n\r\n };\r\n\r\n /**\r\n * Iterates between child noted and looking for #text node on deepest level\r\n * @protected\r\n *\r\n * @param {Element} block - node where find\r\n * @param {int} postiton - starting postion\r\n * Example: childNodex.length to find from the end\r\n * or 0 to find from the start\r\n * @return {Text} block\r\n * @uses DFS\r\n */\r\n content.getDeepestTextNodeFromPosition = function (block, position) {\r\n\r\n /**\r\n * Clear Block from empty and useless spaces with trim.\r\n * Such nodes we should remove\r\n */\r\n var blockChilds = block.childNodes,\r\n index,\r\n node,\r\n text;\r\n\r\n for(index = 0; index < blockChilds.length; index++) {\r\n\r\n node = blockChilds[index];\r\n\r\n if (node.nodeType == editor.core.nodeTypes.TEXT) {\r\n\r\n text = node.textContent.trim();\r\n\r\n /** Text is empty. We should remove this child from node before we start DFS\r\n * decrease the quantity of childs.\r\n */\r\n if (text === '') {\r\n\r\n block.removeChild(node);\r\n position--;\r\n\r\n }\r\n\r\n }\r\n\r\n }\r\n\r\n if (block.childNodes.length === 0) {\r\n\r\n return document.createTextNode('');\r\n\r\n }\r\n\r\n /** Setting default position when we deleted all empty nodes */\r\n if ( position < 0 )\r\n position = 1;\r\n\r\n var lookingFromStart = false;\r\n\r\n /** For looking from START */\r\n if (position === 0) {\r\n\r\n lookingFromStart = true;\r\n position = 1;\r\n\r\n }\r\n\r\n while ( position ) {\r\n\r\n /** initial verticle of node. */\r\n if ( lookingFromStart ) {\r\n\r\n block = block.childNodes[0];\r\n\r\n } else {\r\n\r\n block = block.childNodes[position - 1];\r\n\r\n }\r\n\r\n if ( block.nodeType == editor.core.nodeTypes.TAG ) {\r\n\r\n position = block.childNodes.length;\r\n\r\n } else if (block.nodeType == editor.core.nodeTypes.TEXT ) {\r\n\r\n position = 0;\r\n\r\n }\r\n\r\n }\r\n\r\n return block;\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n * @param {Element} block - current plugins render\r\n * @param {String} tool - plugins name\r\n * @param {Boolean} isStretched - make stretched block or not\r\n *\r\n * @description adds necessary information to wrap new created block by first-level holder\r\n */\r\n var composeNewBlock_ = function (block, tool, isStretched) {\r\n\r\n var newBlock = editor.draw.node('DIV', editor.ui.className.BLOCK_CLASSNAME, {}),\r\n blockContent = editor.draw.node('DIV', editor.ui.className.BLOCK_CONTENT, {});\r\n\r\n blockContent.appendChild(block);\r\n newBlock.appendChild(blockContent);\r\n\r\n if (isStretched) {\r\n\r\n blockContent.classList.add(editor.ui.className.BLOCK_STRETCHED);\r\n\r\n }\r\n\r\n newBlock.dataset.tool = tool;\r\n return newBlock;\r\n\r\n };\r\n\r\n /**\r\n * Returns Range object of current selection\r\n * @protected\r\n */\r\n content.getRange = function () {\r\n\r\n var selection = window.getSelection().getRangeAt(0);\r\n\r\n return selection;\r\n\r\n };\r\n\r\n /**\r\n * Divides block in two blocks (after and before caret)\r\n *\r\n * @protected\r\n * @param {int} inputIndex - target input index\r\n *\r\n * @description splits current input content to the separate blocks\r\n * When enter is pressed among the words, that text will be splited.\r\n */\r\n content.splitBlock = function (inputIndex) {\r\n\r\n var selection = window.getSelection(),\r\n anchorNode = selection.anchorNode,\r\n anchorNodeText = anchorNode.textContent,\r\n caretOffset = selection.anchorOffset,\r\n textBeforeCaret,\r\n textNodeBeforeCaret,\r\n textAfterCaret,\r\n textNodeAfterCaret;\r\n\r\n var currentBlock = editor.content.currentNode.querySelector('[contentEditable]');\r\n\r\n\r\n textBeforeCaret = anchorNodeText.substring(0, caretOffset);\r\n textAfterCaret = anchorNodeText.substring(caretOffset);\r\n\r\n textNodeBeforeCaret = document.createTextNode(textBeforeCaret);\r\n\r\n if (textAfterCaret) {\r\n\r\n textNodeAfterCaret = document.createTextNode(textAfterCaret);\r\n\r\n }\r\n\r\n var previousChilds = [],\r\n nextChilds = [],\r\n reachedCurrent = false;\r\n\r\n if (textNodeAfterCaret) {\r\n\r\n nextChilds.push(textNodeAfterCaret);\r\n\r\n }\r\n\r\n for ( var i = 0, child; !!(child = currentBlock.childNodes[i]); i++) {\r\n\r\n if ( child != anchorNode ) {\r\n\r\n if ( !reachedCurrent ) {\r\n\r\n previousChilds.push(child);\r\n\r\n } else {\r\n\r\n nextChilds.push(child);\r\n\r\n }\r\n\r\n } else {\r\n\r\n reachedCurrent = true;\r\n\r\n }\r\n\r\n }\r\n\r\n /** Clear current input */\r\n editor.state.inputs[inputIndex].innerHTML = '';\r\n\r\n /**\r\n * Append all childs founded before anchorNode\r\n */\r\n var previousChildsLength = previousChilds.length;\r\n\r\n for(i = 0; i < previousChildsLength; i++) {\r\n\r\n editor.state.inputs[inputIndex].appendChild(previousChilds[i]);\r\n\r\n }\r\n\r\n editor.state.inputs[inputIndex].appendChild(textNodeBeforeCaret);\r\n\r\n /**\r\n * Append text node which is after caret\r\n */\r\n var nextChildsLength = nextChilds.length,\r\n newNode = document.createElement('div');\r\n\r\n for(i = 0; i < nextChildsLength; i++) {\r\n\r\n newNode.appendChild(nextChilds[i]);\r\n\r\n }\r\n\r\n newNode = newNode.innerHTML;\r\n\r\n /** This type of block creates when enter is pressed */\r\n var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\r\n\r\n /**\r\n * Make new paragraph with text after caret\r\n */\r\n editor.content.insertBlock({\r\n type : NEW_BLOCK_TYPE,\r\n block : editor.tools[NEW_BLOCK_TYPE].render({\r\n text : newNode\r\n })\r\n }, true );\r\n\r\n };\r\n\r\n /**\r\n * Merges two blocks — current and target\r\n * If target index is not exist, then previous will be as target\r\n *\r\n * @protected\r\n * @param {int} currentInputIndex\r\n * @param {int} targetInputIndex\r\n *\r\n * @description gets two inputs indexes and merges into one\r\n */\r\n content.mergeBlocks = function (currentInputIndex, targetInputIndex) {\r\n\r\n /** If current input index is zero, then prevent method execution */\r\n if (currentInputIndex === 0) {\r\n\r\n return;\r\n\r\n }\r\n\r\n var targetInput,\r\n currentInputContent = editor.state.inputs[currentInputIndex].innerHTML;\r\n\r\n if (!targetInputIndex) {\r\n\r\n targetInput = editor.state.inputs[currentInputIndex - 1];\r\n\r\n } else {\r\n\r\n targetInput = editor.state.inputs[targetInputIndex];\r\n\r\n }\r\n\r\n targetInput.innerHTML += currentInputContent;\r\n\r\n };\r\n\r\n /**\r\n * Iterates all right siblings and parents, which has right siblings\r\n * while it does not reached the first-level block\r\n *\r\n * @param {Element} node\r\n * @return {boolean}\r\n */\r\n content.isLastNode = function (node) {\r\n\r\n // console.log('погнали перебор родителей');\r\n\r\n var allChecked = false;\r\n\r\n while ( !allChecked ) {\r\n\r\n // console.log('Смотрим на %o', node);\r\n // console.log('Проверим, пустые ли соседи справа');\r\n\r\n if ( !allSiblingsEmpty_(node) ) {\r\n\r\n // console.log('Есть непустые соседи. Узел не последний. Выходим.');\r\n return false;\r\n\r\n }\r\n\r\n node = node.parentNode;\r\n\r\n /**\r\n * Проверяем родителей до тех пор, пока не найдем блок первого уровня\r\n */\r\n if ( node.classList.contains(editor.ui.className.BLOCK_CONTENT) ) {\r\n\r\n allChecked = true;\r\n\r\n }\r\n\r\n }\r\n\r\n return true;\r\n\r\n };\r\n\r\n /**\r\n * Checks if all element right siblings is empty\r\n * @param node\r\n */\r\n var allSiblingsEmpty_ = function (node) {\r\n\r\n /**\r\n * Нужно убедиться, что после пустого соседа ничего нет\r\n */\r\n var sibling = node.nextSibling;\r\n\r\n while ( sibling ) {\r\n\r\n if (sibling.textContent.length) {\r\n\r\n return false;\r\n\r\n }\r\n\r\n sibling = sibling.nextSibling;\r\n\r\n }\r\n\r\n return true;\r\n\r\n };\r\n\r\n /**\r\n * @public\r\n *\r\n * @param {string} htmlData - html content as string\r\n * @param {string} plainData - plain text\r\n * @return {string} - html content as string\r\n */\r\n content.wrapTextWithParagraphs = function (htmlData, plainData) {\r\n\r\n if (!htmlData.trim()) {\r\n\r\n return wrapPlainTextWithParagraphs(plainData);\r\n\r\n }\r\n\r\n var wrapper = document.createElement('DIV'),\r\n newWrapper = document.createElement('DIV'),\r\n i,\r\n paragraph,\r\n firstLevelBlocks = ['DIV', 'P'],\r\n blockTyped,\r\n node;\r\n\r\n /**\r\n * Make HTML Element to Wrap Text\r\n * It allows us to work with input data as HTML content\r\n */\r\n wrapper.innerHTML = htmlData;\r\n paragraph = document.createElement('P');\r\n\r\n for (i = 0; i < wrapper.childNodes.length; i++) {\r\n\r\n node = wrapper.childNodes[i];\r\n\r\n blockTyped = firstLevelBlocks.indexOf(node.tagName) != -1;\r\n\r\n /**\r\n * If node is first-levet\r\n * we add this node to our new wrapper\r\n */\r\n if ( blockTyped ) {\r\n\r\n /**\r\n * If we had splitted inline nodes to paragraph before\r\n */\r\n if ( paragraph.childNodes.length ) {\r\n\r\n newWrapper.appendChild(paragraph.cloneNode(true));\r\n\r\n /** empty paragraph */\r\n paragraph = null;\r\n paragraph = document.createElement('P');\r\n\r\n }\r\n\r\n newWrapper.appendChild(node.cloneNode(true));\r\n\r\n } else {\r\n\r\n /** Collect all inline nodes to one as paragraph */\r\n paragraph.appendChild(node.cloneNode(true));\r\n\r\n /** if node is last we should append this node to paragraph and paragraph to new wrapper */\r\n if ( i == wrapper.childNodes.length - 1 ) {\r\n\r\n newWrapper.appendChild(paragraph.cloneNode(true));\r\n\r\n }\r\n\r\n }\r\n\r\n }\r\n\r\n return newWrapper.innerHTML;\r\n\r\n };\r\n\r\n /**\r\n * Splits strings on new line and wraps paragraphs with

    tag\r\n * @param plainText\r\n * @returns {string}\r\n */\r\n var wrapPlainTextWithParagraphs = function (plainText) {\r\n\r\n return '

    ' + plainText.split('\\n\\n').join('

    ') + '

    ';\r\n\r\n };\r\n\r\n /**\r\n * Finds closest Contenteditable parent from Element\r\n * @param {Element} node element looking from\r\n * @return {Element} node contenteditable\r\n */\r\n content.getEditableParent = function (node) {\r\n\r\n while (node && node.contentEditable != 'true') {\r\n\r\n node = node.parentNode;\r\n\r\n }\r\n\r\n return node;\r\n\r\n };\r\n\r\n return content;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/content.js","/**\r\n * Codex Editor toolbar module\r\n *\r\n * Contains:\r\n * - Inline toolbox\r\n * - Toolbox within plus button\r\n * - Settings section\r\n *\r\n * @author Codex Team\r\n * @version 1.0\r\n */\r\n\r\nmodule.exports = (function (toolbar) {\r\n\r\n let editor = codex.editor;\r\n\r\n toolbar.settings = require('./settings');\r\n toolbar.inline = require('./inline');\r\n toolbar.toolbox = require('./toolbox');\r\n\r\n /**\r\n * Margin between focused node and toolbar\r\n */\r\n toolbar.defaultToolbarHeight = 49;\r\n\r\n toolbar.defaultOffset = 34;\r\n\r\n toolbar.opened = false;\r\n\r\n toolbar.current = null;\r\n\r\n /**\r\n * @protected\r\n */\r\n toolbar.open = function () {\r\n\r\n if (editor.hideToolbar) {\r\n\r\n return;\r\n\r\n }\r\n\r\n editor.nodes.toolbar.classList.add('opened');\r\n this.opened = true;\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n */\r\n toolbar.close = function () {\r\n\r\n editor.nodes.toolbar.classList.remove('opened');\r\n\r\n toolbar.opened = false;\r\n toolbar.current = null;\r\n\r\n for (var button in editor.nodes.toolbarButtons) {\r\n\r\n editor.nodes.toolbarButtons[button].classList.remove('selected');\r\n\r\n }\r\n\r\n /** Close toolbox when toolbar is not displayed */\r\n editor.toolbar.toolbox.close();\r\n editor.toolbar.settings.close();\r\n\r\n };\r\n\r\n toolbar.toggle = function () {\r\n\r\n if ( !this.opened ) {\r\n\r\n this.open();\r\n\r\n } else {\r\n\r\n this.close();\r\n\r\n }\r\n\r\n };\r\n\r\n toolbar.hidePlusButton = function () {\r\n\r\n editor.nodes.plusButton.classList.add('hide');\r\n\r\n };\r\n\r\n toolbar.showPlusButton = function () {\r\n\r\n editor.nodes.plusButton.classList.remove('hide');\r\n\r\n };\r\n\r\n /**\r\n * Moving toolbar to the specified node\r\n */\r\n toolbar.move = function () {\r\n\r\n /** Close Toolbox when we move toolbar */\r\n editor.toolbar.toolbox.close();\r\n\r\n if (!editor.content.currentNode) {\r\n\r\n return;\r\n\r\n }\r\n\r\n var newYCoordinate = editor.content.currentNode.offsetTop - (editor.toolbar.defaultToolbarHeight / 2) + editor.toolbar.defaultOffset;\r\n\r\n editor.nodes.toolbar.style.transform = `translate3D(0, ${Math.floor(newYCoordinate)}px, 0)`;\r\n\r\n /** Close trash actions */\r\n editor.toolbar.settings.hideRemoveActions();\r\n\r\n };\r\n\r\n return toolbar;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/toolbar/toolbar.js","/**\r\n * Toolbar settings\r\n *\r\n * @version 1.0.4\r\n */\r\n\r\nmodule.exports = (function (settings) {\r\n\r\n let editor = codex.editor;\r\n\r\n settings.opened = false;\r\n\r\n settings.setting = null;\r\n settings.actions = null;\r\n\r\n /**\r\n * Append and open settings\r\n */\r\n settings.open = function (toolType) {\r\n\r\n /**\r\n * Append settings content\r\n * It's stored in tool.settings\r\n */\r\n if (!editor.tools[toolType] || !editor.tools[toolType].makeSettings ) {\r\n\r\n editor.core.log(`Plugin «${toolType}» has no settings`, 'warn');\r\n editor.nodes.pluginSettings.innerHTML = `Плагин «${toolType}» не имеет настроек`;\r\n\r\n } else {\r\n\r\n /**\r\n * Draw settings block\r\n */\r\n var settingsBlock = editor.tools[toolType].makeSettings();\r\n\r\n editor.nodes.pluginSettings.appendChild(settingsBlock);\r\n\r\n }\r\n\r\n /** Open settings block */\r\n editor.nodes.blockSettings.classList.add('opened');\r\n this.opened = true;\r\n\r\n };\r\n\r\n /**\r\n * Close and clear settings\r\n */\r\n settings.close = function () {\r\n\r\n editor.nodes.blockSettings.classList.remove('opened');\r\n editor.nodes.pluginSettings.innerHTML = '';\r\n\r\n this.opened = false;\r\n\r\n };\r\n\r\n /**\r\n * @param {string} toolType - plugin type\r\n */\r\n settings.toggle = function ( toolType ) {\r\n\r\n if ( !this.opened ) {\r\n\r\n this.open(toolType);\r\n\r\n } else {\r\n\r\n this.close();\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Here we will draw buttons and add listeners to components\r\n */\r\n settings.makeRemoveBlockButton = function () {\r\n\r\n var removeBlockWrapper = editor.draw.node('SPAN', 'ce-toolbar__remove-btn', {}),\r\n settingButton = editor.draw.node('SPAN', 'ce-toolbar__remove-setting', { innerHTML : '' }),\r\n actionWrapper = editor.draw.node('DIV', 'ce-toolbar__remove-confirmation', {}),\r\n confirmAction = editor.draw.node('DIV', 'ce-toolbar__remove-confirm', { textContent : 'Удалить блок' }),\r\n cancelAction = editor.draw.node('DIV', 'ce-toolbar__remove-cancel', { textContent : 'Отмена' });\r\n\r\n editor.listeners.add(settingButton, 'click', editor.toolbar.settings.removeButtonClicked, false);\r\n\r\n editor.listeners.add(confirmAction, 'click', editor.toolbar.settings.confirmRemovingRequest, false);\r\n\r\n editor.listeners.add(cancelAction, 'click', editor.toolbar.settings.cancelRemovingRequest, false);\r\n\r\n actionWrapper.appendChild(confirmAction);\r\n actionWrapper.appendChild(cancelAction);\r\n\r\n removeBlockWrapper.appendChild(settingButton);\r\n removeBlockWrapper.appendChild(actionWrapper);\r\n\r\n /** Save setting */\r\n editor.toolbar.settings.setting = settingButton;\r\n editor.toolbar.settings.actions = actionWrapper;\r\n\r\n return removeBlockWrapper;\r\n\r\n };\r\n\r\n settings.removeButtonClicked = function () {\r\n\r\n var action = editor.toolbar.settings.actions;\r\n\r\n if (action.classList.contains('opened')) {\r\n\r\n editor.toolbar.settings.hideRemoveActions();\r\n\r\n } else {\r\n\r\n editor.toolbar.settings.showRemoveActions();\r\n\r\n }\r\n\r\n editor.toolbar.toolbox.close();\r\n editor.toolbar.settings.close();\r\n\r\n };\r\n\r\n settings.cancelRemovingRequest = function () {\r\n\r\n editor.toolbar.settings.actions.classList.remove('opened');\r\n\r\n };\r\n\r\n settings.confirmRemovingRequest = function () {\r\n\r\n var currentBlock = editor.content.currentNode,\r\n firstLevelBlocksCount;\r\n\r\n currentBlock.remove();\r\n\r\n firstLevelBlocksCount = editor.nodes.redactor.childNodes.length;\r\n\r\n /**\r\n * If all blocks are removed\r\n */\r\n if (firstLevelBlocksCount === 0) {\r\n\r\n /** update currentNode variable */\r\n editor.content.currentNode = null;\r\n\r\n /** Inserting new empty initial block */\r\n editor.ui.addInitialBlock();\r\n\r\n }\r\n\r\n editor.ui.saveInputs();\r\n\r\n editor.toolbar.close();\r\n\r\n };\r\n\r\n settings.showRemoveActions = function () {\r\n\r\n editor.toolbar.settings.actions.classList.add('opened');\r\n\r\n };\r\n\r\n settings.hideRemoveActions = function () {\r\n\r\n editor.toolbar.settings.actions.classList.remove('opened');\r\n\r\n };\r\n\r\n return settings;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/toolbar/settings.js","/**\r\n * Inline toolbar\r\n *\r\n * Contains from tools:\r\n * Bold, Italic, Underline and Anchor\r\n *\r\n * @author Codex Team\r\n * @version 1.0\r\n */\r\n\r\nmodule.exports = (function (inline) {\r\n\r\n let editor = codex.editor;\r\n\r\n inline.buttonsOpened = null;\r\n inline.actionsOpened = null;\r\n inline.wrappersOffset = null;\r\n\r\n /**\r\n * saving selection that need for execCommand for styling\r\n *\r\n */\r\n inline.storedSelection = null;\r\n\r\n /**\r\n * @protected\r\n *\r\n * Open inline toobar\r\n */\r\n inline.show = function () {\r\n\r\n var currentNode = editor.content.currentNode,\r\n tool = currentNode.dataset.tool,\r\n plugin;\r\n\r\n /**\r\n * tool allowed to open inline toolbar\r\n */\r\n plugin = editor.tools[tool];\r\n\r\n if (!plugin.showInlineToolbar)\r\n return;\r\n\r\n var selectedText = inline.getSelectionText(),\r\n toolbar = editor.nodes.inlineToolbar.wrapper;\r\n\r\n if (selectedText.length > 0) {\r\n\r\n /** Move toolbar and open */\r\n editor.toolbar.inline.move();\r\n\r\n /** Open inline toolbar */\r\n toolbar.classList.add('opened');\r\n\r\n /** show buttons of inline toolbar */\r\n editor.toolbar.inline.showButtons();\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n *\r\n * Closes inline toolbar\r\n */\r\n inline.close = function () {\r\n\r\n var toolbar = editor.nodes.inlineToolbar.wrapper;\r\n\r\n toolbar.classList.remove('opened');\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Moving toolbar\r\n */\r\n inline.move = function () {\r\n\r\n if (!this.wrappersOffset) {\r\n\r\n this.wrappersOffset = this.getWrappersOffset();\r\n\r\n }\r\n\r\n var coords = this.getSelectionCoords(),\r\n defaultOffset = 0,\r\n toolbar = editor.nodes.inlineToolbar.wrapper,\r\n newCoordinateX,\r\n newCoordinateY;\r\n\r\n if (toolbar.offsetHeight === 0) {\r\n\r\n defaultOffset = 40;\r\n\r\n }\r\n\r\n newCoordinateX = coords.x - this.wrappersOffset.left;\r\n newCoordinateY = coords.y + window.scrollY - this.wrappersOffset.top - defaultOffset - toolbar.offsetHeight;\r\n\r\n toolbar.style.transform = `translate3D(${Math.floor(newCoordinateX)}px, ${Math.floor(newCoordinateY)}px, 0)`;\r\n\r\n /** Close everything */\r\n editor.toolbar.inline.closeButtons();\r\n editor.toolbar.inline.closeAction();\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Tool Clicked\r\n */\r\n\r\n inline.toolClicked = function (event, type) {\r\n\r\n /**\r\n * For simple tools we use default browser function\r\n * For more complicated tools, we should write our own behavior\r\n */\r\n switch (type) {\r\n case 'createLink' : editor.toolbar.inline.createLinkAction(event, type); break;\r\n default : editor.toolbar.inline.defaultToolAction(type); break;\r\n }\r\n\r\n /**\r\n * highlight buttons\r\n * after making some action\r\n */\r\n editor.nodes.inlineToolbar.buttons.childNodes.forEach(editor.toolbar.inline.hightlight);\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Saving wrappers offset in DOM\r\n */\r\n inline.getWrappersOffset = function () {\r\n\r\n var wrapper = editor.nodes.wrapper,\r\n offset = this.getOffset(wrapper);\r\n\r\n this.wrappersOffset = offset;\r\n return offset;\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Calculates offset of DOM element\r\n *\r\n * @param el\r\n * @returns {{top: number, left: number}}\r\n */\r\n inline.getOffset = function ( el ) {\r\n\r\n var _x = 0;\r\n var _y = 0;\r\n\r\n while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {\r\n\r\n _x += (el.offsetLeft + el.clientLeft);\r\n _y += (el.offsetTop + el.clientTop);\r\n el = el.offsetParent;\r\n\r\n }\r\n return { top: _y, left: _x };\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Calculates position of selected text\r\n * @returns {{x: number, y: number}}\r\n */\r\n inline.getSelectionCoords = function () {\r\n\r\n var sel = document.selection, range;\r\n var x = 0, y = 0;\r\n\r\n if (sel) {\r\n\r\n if (sel.type != 'Control') {\r\n\r\n range = sel.createRange();\r\n range.collapse(true);\r\n x = range.boundingLeft;\r\n y = range.boundingTop;\r\n\r\n }\r\n\r\n } else if (window.getSelection) {\r\n\r\n sel = window.getSelection();\r\n\r\n if (sel.rangeCount) {\r\n\r\n range = sel.getRangeAt(0).cloneRange();\r\n if (range.getClientRects) {\r\n\r\n range.collapse(true);\r\n var rect = range.getClientRects()[0];\r\n\r\n if (!rect) {\r\n\r\n return;\r\n\r\n }\r\n\r\n x = rect.left;\r\n y = rect.top;\r\n\r\n }\r\n\r\n }\r\n\r\n }\r\n return { x: x, y: y };\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Returns selected text as String\r\n * @returns {string}\r\n */\r\n inline.getSelectionText = function () {\r\n\r\n var selectedText = '';\r\n\r\n // all modern browsers and IE9+\r\n if (window.getSelection) {\r\n\r\n selectedText = window.getSelection().toString();\r\n\r\n }\r\n\r\n return selectedText;\r\n\r\n };\r\n\r\n /** Opens buttons block */\r\n inline.showButtons = function () {\r\n\r\n var buttons = editor.nodes.inlineToolbar.buttons;\r\n\r\n buttons.classList.add('opened');\r\n\r\n editor.toolbar.inline.buttonsOpened = true;\r\n\r\n /** highlight buttons */\r\n editor.nodes.inlineToolbar.buttons.childNodes.forEach(editor.toolbar.inline.hightlight);\r\n\r\n };\r\n\r\n /** Makes buttons disappear */\r\n inline.closeButtons = function () {\r\n\r\n var buttons = editor.nodes.inlineToolbar.buttons;\r\n\r\n buttons.classList.remove('opened');\r\n\r\n editor.toolbar.inline.buttonsOpened = false;\r\n\r\n };\r\n\r\n /** Open buttons defined action if exist */\r\n inline.showActions = function () {\r\n\r\n var action = editor.nodes.inlineToolbar.actions;\r\n\r\n action.classList.add('opened');\r\n\r\n editor.toolbar.inline.actionsOpened = true;\r\n\r\n };\r\n\r\n /** Close actions block */\r\n inline.closeAction = function () {\r\n\r\n var action = editor.nodes.inlineToolbar.actions;\r\n\r\n action.innerHTML = '';\r\n action.classList.remove('opened');\r\n editor.toolbar.inline.actionsOpened = false;\r\n\r\n };\r\n\r\n\r\n /**\r\n * Callback for keydowns in inline toolbar \"Insert link...\" input\r\n */\r\n let inlineToolbarAnchorInputKeydown_ = function (event) {\r\n\r\n if (event.keyCode != editor.core.keys.ENTER) {\r\n\r\n return;\r\n\r\n }\r\n\r\n let editable = editor.content.currentNode,\r\n storedSelection = editor.toolbar.inline.storedSelection;\r\n\r\n editor.toolbar.inline.restoreSelection(editable, storedSelection);\r\n editor.toolbar.inline.setAnchor(this.value);\r\n\r\n /**\r\n * Preventing events that will be able to happen\r\n */\r\n event.preventDefault();\r\n event.stopImmediatePropagation();\r\n\r\n editor.toolbar.inline.clearRange();\r\n\r\n };\r\n\r\n /** Action for link creation or for setting anchor */\r\n inline.createLinkAction = function (event) {\r\n\r\n var isActive = this.isLinkActive();\r\n\r\n var editable = editor.content.currentNode,\r\n storedSelection = editor.toolbar.inline.saveSelection(editable);\r\n\r\n /** Save globally selection */\r\n editor.toolbar.inline.storedSelection = storedSelection;\r\n\r\n if (isActive) {\r\n\r\n\r\n /**\r\n * Changing stored selection. if we want to remove anchor from word\r\n * we should remove anchor from whole word, not only selected part.\r\n * The solution is than we get the length of current link\r\n * Change start position to - end of selection minus length of anchor\r\n */\r\n editor.toolbar.inline.restoreSelection(editable, storedSelection);\r\n\r\n editor.toolbar.inline.defaultToolAction('unlink');\r\n\r\n } else {\r\n\r\n /** Create input and close buttons */\r\n var action = editor.draw.inputForLink();\r\n\r\n editor.nodes.inlineToolbar.actions.appendChild(action);\r\n\r\n editor.toolbar.inline.closeButtons();\r\n editor.toolbar.inline.showActions();\r\n\r\n /**\r\n * focus to input\r\n * Solution: https://developer.mozilla.org/ru/docs/Web/API/HTMLElement/focus\r\n * Prevents event after showing input and when we need to focus an input which is in unexisted form\r\n */\r\n action.focus();\r\n event.preventDefault();\r\n\r\n /** Callback to link action */\r\n editor.listeners.add(action, 'keydown', inlineToolbarAnchorInputKeydown_, false);\r\n\r\n }\r\n\r\n };\r\n\r\n inline.isLinkActive = function () {\r\n\r\n var isActive = false;\r\n\r\n editor.nodes.inlineToolbar.buttons.childNodes.forEach(function (tool) {\r\n\r\n var dataType = tool.dataset.type;\r\n\r\n if (dataType == 'link' && tool.classList.contains('hightlighted')) {\r\n\r\n isActive = true;\r\n\r\n }\r\n\r\n });\r\n\r\n return isActive;\r\n\r\n };\r\n\r\n /** default action behavior of tool */\r\n inline.defaultToolAction = function (type) {\r\n\r\n document.execCommand(type, false, null);\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Sets URL\r\n *\r\n * @param {String} url - URL\r\n */\r\n inline.setAnchor = function (url) {\r\n\r\n document.execCommand('createLink', false, url);\r\n\r\n /** Close after URL inserting */\r\n editor.toolbar.inline.closeAction();\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Saves selection\r\n */\r\n inline.saveSelection = function (containerEl) {\r\n\r\n var range = window.getSelection().getRangeAt(0),\r\n preSelectionRange = range.cloneRange(),\r\n start;\r\n\r\n preSelectionRange.selectNodeContents(containerEl);\r\n preSelectionRange.setEnd(range.startContainer, range.startOffset);\r\n\r\n start = preSelectionRange.toString().length;\r\n\r\n return {\r\n start: start,\r\n end: start + range.toString().length\r\n };\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Sets to previous selection (Range)\r\n *\r\n * @param {Element} containerEl - editable element where we restore range\r\n * @param {Object} savedSel - range basic information to restore\r\n */\r\n inline.restoreSelection = function (containerEl, savedSel) {\r\n\r\n var range = document.createRange(),\r\n charIndex = 0;\r\n\r\n range.setStart(containerEl, 0);\r\n range.collapse(true);\r\n\r\n var nodeStack = [ containerEl ],\r\n node,\r\n foundStart = false,\r\n stop = false,\r\n nextCharIndex;\r\n\r\n while (!stop && (node = nodeStack.pop())) {\r\n\r\n if (node.nodeType == 3) {\r\n\r\n nextCharIndex = charIndex + node.length;\r\n\r\n if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {\r\n\r\n range.setStart(node, savedSel.start - charIndex);\r\n foundStart = true;\r\n\r\n }\r\n if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {\r\n\r\n range.setEnd(node, savedSel.end - charIndex);\r\n stop = true;\r\n\r\n }\r\n charIndex = nextCharIndex;\r\n\r\n } else {\r\n\r\n var i = node.childNodes.length;\r\n\r\n while (i--) {\r\n\r\n nodeStack.push(node.childNodes[i]);\r\n\r\n }\r\n\r\n }\r\n\r\n }\r\n\r\n var sel = window.getSelection();\r\n\r\n sel.removeAllRanges();\r\n sel.addRange(range);\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Removes all ranges from window selection\r\n */\r\n inline.clearRange = function () {\r\n\r\n var selection = window.getSelection();\r\n\r\n selection.removeAllRanges();\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * sets or removes hightlight\r\n */\r\n inline.hightlight = function (tool) {\r\n\r\n var dataType = tool.dataset.type;\r\n\r\n if (document.queryCommandState(dataType)) {\r\n\r\n editor.toolbar.inline.setButtonHighlighted(tool);\r\n\r\n } else {\r\n\r\n editor.toolbar.inline.removeButtonsHighLight(tool);\r\n\r\n }\r\n\r\n /**\r\n *\r\n * hightlight for anchors\r\n */\r\n var selection = window.getSelection(),\r\n tag = selection.anchorNode.parentNode;\r\n\r\n if (tag.tagName == 'A' && dataType == 'link') {\r\n\r\n editor.toolbar.inline.setButtonHighlighted(tool);\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Mark button if text is already executed\r\n */\r\n inline.setButtonHighlighted = function (button) {\r\n\r\n button.classList.add('hightlighted');\r\n\r\n /** At link tool we also change icon */\r\n if (button.dataset.type == 'link') {\r\n\r\n var icon = button.childNodes[0];\r\n\r\n icon.classList.remove('ce-icon-link');\r\n icon.classList.add('ce-icon-unlink');\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Removes hightlight\r\n */\r\n inline.removeButtonsHighLight = function (button) {\r\n\r\n button.classList.remove('hightlighted');\r\n\r\n /** At link tool we also change icon */\r\n if (button.dataset.type == 'link') {\r\n\r\n var icon = button.childNodes[0];\r\n\r\n icon.classList.remove('ce-icon-unlink');\r\n icon.classList.add('ce-icon-link');\r\n\r\n }\r\n\r\n };\r\n\r\n\r\n return inline;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/toolbar/inline.js","/**\r\n * Codex Editor toolbox\r\n *\r\n * All tools be able to appended here\r\n *\r\n * @author Codex Team\r\n * @version 1.0\r\n */\r\n\r\nmodule.exports = (function (toolbox) {\r\n\r\n let editor = codex.editor;\r\n\r\n toolbox.opened = false;\r\n\r\n /** Shows toolbox */\r\n toolbox.open = function () {\r\n\r\n /** Close setting if toolbox is opened */\r\n if (editor.toolbar.settings.opened) {\r\n\r\n editor.toolbar.settings.close();\r\n\r\n }\r\n\r\n /** display toolbox */\r\n editor.nodes.toolbox.classList.add('opened');\r\n\r\n /** Animate plus button */\r\n editor.nodes.plusButton.classList.add('clicked');\r\n\r\n /** toolbox state */\r\n editor.toolbar.toolbox.opened = true;\r\n\r\n };\r\n\r\n /** Closes toolbox */\r\n toolbox.close = function () {\r\n\r\n /** Makes toolbox disapear */\r\n editor.nodes.toolbox.classList.remove('opened');\r\n\r\n /** Rotate plus button */\r\n editor.nodes.plusButton.classList.remove('clicked');\r\n\r\n /** toolbox state */\r\n editor.toolbar.toolbox.opened = false;\r\n\r\n editor.toolbar.current = null;\r\n\r\n };\r\n\r\n toolbox.leaf = function () {\r\n\r\n let currentTool = editor.toolbar.current,\r\n tools = Object.keys(editor.tools),\r\n barButtons = editor.nodes.toolbarButtons,\r\n nextToolIndex = 0,\r\n toolToSelect,\r\n visibleTool,\r\n tool;\r\n\r\n if ( !currentTool ) {\r\n\r\n /** Get first tool from object*/\r\n for(tool in editor.tools) {\r\n\r\n if (editor.tools[tool].displayInToolbox) {\r\n\r\n break;\r\n\r\n }\r\n\r\n nextToolIndex ++;\r\n\r\n }\r\n\r\n } else {\r\n\r\n nextToolIndex = (tools.indexOf(currentTool) + 1) % tools.length;\r\n visibleTool = tools[nextToolIndex];\r\n\r\n while (!editor.tools[visibleTool].displayInToolbox) {\r\n\r\n nextToolIndex = (nextToolIndex + 1) % tools.length;\r\n visibleTool = tools[nextToolIndex];\r\n\r\n }\r\n\r\n }\r\n\r\n toolToSelect = tools[nextToolIndex];\r\n\r\n for ( var button in barButtons ) {\r\n\r\n barButtons[button].classList.remove('selected');\r\n\r\n }\r\n\r\n barButtons[toolToSelect].classList.add('selected');\r\n editor.toolbar.current = toolToSelect;\r\n\r\n };\r\n\r\n /**\r\n * Transforming selected node type into selected toolbar element type\r\n * @param {event} event\r\n */\r\n toolbox.toolClicked = function (event) {\r\n\r\n /**\r\n * UNREPLACEBLE_TOOLS this types of tools are forbidden to replace even they are empty\r\n */\r\n var UNREPLACEBLE_TOOLS = ['image', 'link', 'list', 'instagram', 'twitter', 'embed'],\r\n tool = editor.tools[editor.toolbar.current],\r\n workingNode = editor.content.currentNode,\r\n currentInputIndex = editor.caret.inputIndex,\r\n newBlockContent,\r\n appendCallback,\r\n blockData;\r\n\r\n /** Make block from plugin */\r\n newBlockContent = tool.render();\r\n\r\n /** information about block */\r\n blockData = {\r\n block : newBlockContent,\r\n type : tool.type,\r\n stretched : false\r\n };\r\n\r\n if (\r\n workingNode &&\r\n UNREPLACEBLE_TOOLS.indexOf(workingNode.dataset.tool) === -1 &&\r\n workingNode.textContent.trim() === ''\r\n ) {\r\n\r\n /** Replace current block */\r\n editor.content.switchBlock(workingNode, newBlockContent, tool.type);\r\n\r\n } else {\r\n\r\n /** Insert new Block from plugin */\r\n editor.content.insertBlock(blockData);\r\n\r\n /** increase input index */\r\n currentInputIndex++;\r\n\r\n }\r\n\r\n /** Fire tool append callback */\r\n appendCallback = tool.appendCallback;\r\n\r\n if (appendCallback && typeof appendCallback == 'function') {\r\n\r\n appendCallback.call(event);\r\n\r\n }\r\n\r\n window.setTimeout(function () {\r\n\r\n /** Set caret to current block */\r\n editor.caret.setToBlock(currentInputIndex);\r\n\r\n }, 10);\r\n\r\n\r\n /**\r\n * Changing current Node\r\n */\r\n editor.content.workingNodeChanged();\r\n\r\n /**\r\n * Move toolbar when node is changed\r\n */\r\n editor.toolbar.move();\r\n\r\n };\r\n\r\n return toolbox;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/toolbar/toolbox.js","/**\r\n * @module Codex Editor Callbacks module\r\n * @description Module works with editor added Elements\r\n *\r\n * @author Codex Team\r\n * @version 1.4.0\r\n */\r\n\r\nmodule.exports = (function (callbacks) {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * used by UI module\r\n * @description Routes all keydowns on document\r\n * @param {Object} event\r\n */\r\n callbacks.globalKeydown = function (event) {\r\n\r\n switch (event.keyCode) {\r\n case editor.core.keys.ENTER : enterKeyPressed_(event); break;\r\n }\r\n\r\n };\r\n\r\n /**\r\n * used by UI module\r\n * @description Routes all keydowns on redactors area\r\n * @param {Object} event\r\n */\r\n callbacks.redactorKeyDown = function (event) {\r\n\r\n switch (event.keyCode) {\r\n case editor.core.keys.TAB : tabKeyPressedOnRedactorsZone_(event); break;\r\n case editor.core.keys.ENTER : enterKeyPressedOnRedactorsZone_(event); break;\r\n case editor.core.keys.ESC : escapeKeyPressedOnRedactorsZone_(event); break;\r\n default : defaultKeyPressedOnRedactorsZone_(event); break;\r\n }\r\n\r\n };\r\n\r\n /**\r\n * used by UI module\r\n * @description Routes all keyup events\r\n * @param {Object} event\r\n */\r\n callbacks.globalKeyup = function (event) {\r\n\r\n switch (event.keyCode) {\r\n case editor.core.keys.UP :\r\n case editor.core.keys.LEFT :\r\n case editor.core.keys.RIGHT :\r\n case editor.core.keys.DOWN : arrowKeyPressed_(event); break;\r\n }\r\n\r\n };\r\n\r\n /**\r\n * @param {Object} event\r\n * @private\r\n *\r\n * Handles behaviour when tab pressed\r\n * @description if Content is empty show toolbox (if it is closed) or leaf tools\r\n * uses Toolbars toolbox module to handle the situation\r\n */\r\n var tabKeyPressedOnRedactorsZone_ = function (event) {\r\n\r\n /**\r\n * Wait for solution. Would like to know the behaviour\r\n * @todo Add spaces\r\n */\r\n event.preventDefault();\r\n\r\n var nativeInputs = editor.content.currentNode.querySelectorAll('textarea, input'),\r\n nativeInputsAreEmpty = true,\r\n textContentIsEmpty = !editor.content.currentNode.textContent.trim();\r\n\r\n Array.prototype.map.call(nativeInputs, function (input) {\r\n\r\n if (input.type == 'textarea' || input.type == 'text') {\r\n\r\n nativeInputsAreEmpty = nativeInputsAreEmpty && !input.value.trim();\r\n\r\n }\r\n\r\n });\r\n\r\n var blockIsEmpty = textContentIsEmpty && nativeInputsAreEmpty;\r\n\r\n if (!blockIsEmpty) {\r\n\r\n return;\r\n\r\n }\r\n\r\n if ( !editor.toolbar.opened ) {\r\n\r\n editor.toolbar.open();\r\n\r\n }\r\n\r\n if (editor.toolbar.opened && !editor.toolbar.toolbox.opened) {\r\n\r\n editor.toolbar.toolbox.open();\r\n\r\n } else {\r\n\r\n editor.toolbar.toolbox.leaf();\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Handles global EnterKey Press\r\n * @see enterPressedOnBlock_\r\n * @param {Object} event\r\n */\r\n var enterKeyPressed_ = function () {\r\n\r\n if (editor.content.editorAreaHightlighted) {\r\n\r\n /**\r\n * it means that we lose input index, saved index before is not correct\r\n * therefore we need to set caret when we insert new block\r\n */\r\n editor.caret.inputIndex = -1;\r\n\r\n enterPressedOnBlock_();\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Callback for enter key pressing in first-level block area\r\n *\r\n * @param {Event} event\r\n * @private\r\n *\r\n * @description Inserts new block with initial type from settings\r\n */\r\n var enterPressedOnBlock_ = function () {\r\n\r\n var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\r\n\r\n editor.content.insertBlock({\r\n type : NEW_BLOCK_TYPE,\r\n block : editor.tools[NEW_BLOCK_TYPE].render()\r\n }, true );\r\n\r\n editor.toolbar.move();\r\n editor.toolbar.open();\r\n\r\n };\r\n\r\n\r\n /**\r\n * ENTER key handler\r\n *\r\n * @param {Object} event\r\n * @private\r\n *\r\n * @description Makes new block with initial type from settings\r\n */\r\n var enterKeyPressedOnRedactorsZone_ = function (event) {\r\n\r\n if (event.target.contentEditable == 'true') {\r\n\r\n /** Update input index */\r\n editor.caret.saveCurrentInputIndex();\r\n\r\n }\r\n\r\n var currentInputIndex = editor.caret.getCurrentInputIndex() || 0,\r\n workingNode = editor.content.currentNode,\r\n tool = workingNode.dataset.tool,\r\n isEnterPressedOnToolbar = editor.toolbar.opened &&\r\n editor.toolbar.current &&\r\n event.target == editor.state.inputs[currentInputIndex];\r\n\r\n /** The list of tools which needs the default browser behaviour */\r\n var enableLineBreaks = editor.tools[tool].enableLineBreaks;\r\n\r\n /** This type of block creates when enter is pressed */\r\n var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\r\n\r\n /**\r\n * When toolbar is opened, select tool instead of making new paragraph\r\n */\r\n if ( isEnterPressedOnToolbar ) {\r\n\r\n event.preventDefault();\r\n\r\n editor.toolbar.toolbox.toolClicked(event);\r\n\r\n editor.toolbar.close();\r\n\r\n /**\r\n * Stop other listeners callback executions\r\n */\r\n event.stopPropagation();\r\n event.stopImmediatePropagation();\r\n\r\n return;\r\n\r\n }\r\n\r\n /**\r\n * Allow paragraph lineBreaks with shift enter\r\n * Or if shiftkey pressed and enter and enabledLineBreaks, the let new block creation\r\n */\r\n if ( event.shiftKey || enableLineBreaks ) {\r\n\r\n event.stopPropagation();\r\n event.stopImmediatePropagation();\r\n return;\r\n\r\n }\r\n\r\n var currentSelection = window.getSelection(),\r\n currentSelectedNode = currentSelection.anchorNode,\r\n caretAtTheEndOfText = editor.caret.position.atTheEnd(),\r\n isTextNodeHasParentBetweenContenteditable = false;\r\n\r\n /**\r\n * Allow making new

    in same block by SHIFT+ENTER and forbids to prevent default browser behaviour\r\n */\r\n if ( event.shiftKey && !enableLineBreaks ) {\r\n\r\n editor.callback.enterPressedOnBlock(editor.content.currentBlock, event);\r\n event.preventDefault();\r\n return;\r\n\r\n }\r\n\r\n /**\r\n * Workaround situation when caret at the Text node that has some wrapper Elements\r\n * Split block cant handle this.\r\n * We need to save default behavior\r\n */\r\n isTextNodeHasParentBetweenContenteditable = currentSelectedNode && currentSelectedNode.parentNode.contentEditable != 'true';\r\n\r\n /**\r\n * Split blocks when input has several nodes and caret placed in textNode\r\n */\r\n if (\r\n currentSelectedNode.nodeType == editor.core.nodeTypes.TEXT &&\r\n !isTextNodeHasParentBetweenContenteditable &&\r\n !caretAtTheEndOfText\r\n ) {\r\n\r\n event.preventDefault();\r\n\r\n editor.core.log('Splitting Text node...');\r\n\r\n editor.content.splitBlock(currentInputIndex);\r\n\r\n /** Show plus button when next input after split is empty*/\r\n if (!editor.state.inputs[currentInputIndex + 1].textContent.trim()) {\r\n\r\n editor.toolbar.showPlusButton();\r\n\r\n }\r\n\r\n } else {\r\n\r\n var islastNode = editor.content.isLastNode(currentSelectedNode);\r\n\r\n if ( islastNode && caretAtTheEndOfText ) {\r\n\r\n event.preventDefault();\r\n event.stopPropagation();\r\n event.stopImmediatePropagation();\r\n\r\n editor.core.log('ENTER clicked in last textNode. Create new BLOCK');\r\n\r\n editor.content.insertBlock({\r\n type: NEW_BLOCK_TYPE,\r\n block: editor.tools[NEW_BLOCK_TYPE].render()\r\n }, true);\r\n\r\n editor.toolbar.move();\r\n editor.toolbar.open();\r\n\r\n /** Show plus button with empty block */\r\n editor.toolbar.showPlusButton();\r\n\r\n }\r\n\r\n }\r\n\r\n /** get all inputs after new appending block */\r\n editor.ui.saveInputs();\r\n\r\n };\r\n\r\n /**\r\n * Escape behaviour\r\n * @param event\r\n * @private\r\n *\r\n * @description Closes toolbox and toolbar. Prevents default behaviour\r\n */\r\n var escapeKeyPressedOnRedactorsZone_ = function (event) {\r\n\r\n /** Close all toolbar */\r\n editor.toolbar.close();\r\n\r\n /** Close toolbox */\r\n editor.toolbar.toolbox.close();\r\n\r\n event.preventDefault();\r\n\r\n };\r\n\r\n /**\r\n * @param {Event} event\r\n * @private\r\n *\r\n * closes and moves toolbar\r\n */\r\n var arrowKeyPressed_ = function (event) {\r\n\r\n editor.content.workingNodeChanged();\r\n\r\n /* Closing toolbar */\r\n editor.toolbar.close();\r\n editor.toolbar.move();\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n * @param {Event} event\r\n *\r\n * @description Closes all opened bars from toolbar.\r\n * If block is mark, clears highlightning\r\n */\r\n var defaultKeyPressedOnRedactorsZone_ = function () {\r\n\r\n editor.toolbar.close();\r\n\r\n if (!editor.toolbar.inline.actionsOpened) {\r\n\r\n editor.toolbar.inline.close();\r\n editor.content.clearMark();\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Handler when clicked on redactors area\r\n *\r\n * @protected\r\n * @param event\r\n *\r\n * @description Detects clicked area. If it is first-level block area, marks as detected and\r\n * on next enter press will be inserted new block\r\n * Otherwise, save carets position (input index) and put caret to the editable zone.\r\n *\r\n * @see detectWhenClickedOnFirstLevelBlockArea_\r\n *\r\n */\r\n callbacks.redactorClicked = function (event) {\r\n\r\n detectWhenClickedOnFirstLevelBlockArea_();\r\n\r\n editor.content.workingNodeChanged(event.target);\r\n editor.ui.saveInputs();\r\n\r\n var selectedText = editor.toolbar.inline.getSelectionText(),\r\n firstLevelBlock;\r\n\r\n /** If selection range took off, then we hide inline toolbar */\r\n if (selectedText.length === 0) {\r\n\r\n editor.toolbar.inline.close();\r\n\r\n }\r\n\r\n /** Update current input index in memory when caret focused into existed input */\r\n if (event.target.contentEditable == 'true') {\r\n\r\n editor.caret.saveCurrentInputIndex();\r\n\r\n }\r\n\r\n if (editor.content.currentNode === null) {\r\n\r\n /**\r\n * If inputs in redactor does not exits, then we put input index 0 not -1\r\n */\r\n var indexOfLastInput = editor.state.inputs.length > 0 ? editor.state.inputs.length - 1 : 0;\r\n\r\n /** If we have any inputs */\r\n if (editor.state.inputs.length) {\r\n\r\n /** getting firstlevel parent of input */\r\n firstLevelBlock = editor.content.getFirstLevelBlock(editor.state.inputs[indexOfLastInput]);\r\n\r\n }\r\n\r\n /** If input is empty, then we set caret to the last input */\r\n if (editor.state.inputs.length && editor.state.inputs[indexOfLastInput].textContent === '' && firstLevelBlock.dataset.tool == editor.settings.initialBlockPlugin) {\r\n\r\n editor.caret.setToBlock(indexOfLastInput);\r\n\r\n } else {\r\n\r\n /** Create new input when caret clicked in redactors area */\r\n var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\r\n\r\n editor.content.insertBlock({\r\n type : NEW_BLOCK_TYPE,\r\n block : editor.tools[NEW_BLOCK_TYPE].render()\r\n });\r\n\r\n /** If there is no inputs except inserted */\r\n if (editor.state.inputs.length === 1) {\r\n\r\n editor.caret.setToBlock(indexOfLastInput);\r\n\r\n } else {\r\n\r\n /** Set caret to this appended input */\r\n editor.caret.setToNextBlock(indexOfLastInput);\r\n\r\n }\r\n\r\n }\r\n\r\n } else {\r\n\r\n /** Close all panels */\r\n editor.toolbar.settings.close();\r\n editor.toolbar.toolbox.close();\r\n\r\n }\r\n\r\n /**\r\n * Move toolbar and open\r\n */\r\n editor.toolbar.move();\r\n editor.toolbar.open();\r\n\r\n var inputIsEmpty = !editor.content.currentNode.textContent.trim(),\r\n currentNodeType = editor.content.currentNode.dataset.tool,\r\n isInitialType = currentNodeType == editor.settings.initialBlockPlugin;\r\n\r\n\r\n /** Hide plus buttons */\r\n editor.toolbar.hidePlusButton();\r\n\r\n if (!inputIsEmpty) {\r\n\r\n /** Mark current block */\r\n editor.content.markBlock();\r\n\r\n }\r\n\r\n if ( isInitialType && inputIsEmpty ) {\r\n\r\n /** Show plus button */\r\n editor.toolbar.showPlusButton();\r\n\r\n }\r\n\r\n\r\n };\r\n\r\n /**\r\n * This method allows to define, is caret in contenteditable element or not.\r\n *\r\n * @private\r\n *\r\n * @description Otherwise, if we get TEXT node from range container, that will means we have input index.\r\n * In this case we use default browsers behaviour (if plugin allows that) or overwritten action.\r\n * Therefore, to be sure that we've clicked first-level block area, we should have currentNode, which always\r\n * specifies to the first-level block. Other cases we just ignore.\r\n */\r\n var detectWhenClickedOnFirstLevelBlockArea_ = function () {\r\n\r\n var selection = window.getSelection(),\r\n anchorNode = selection.anchorNode,\r\n flag = false;\r\n\r\n if (selection.rangeCount === 0) {\r\n\r\n editor.content.editorAreaHightlighted = true;\r\n\r\n } else {\r\n\r\n if (!editor.core.isDomNode(anchorNode)) {\r\n\r\n anchorNode = anchorNode.parentNode;\r\n\r\n }\r\n\r\n /** Already founded, without loop */\r\n if (anchorNode.contentEditable == 'true') {\r\n\r\n flag = true;\r\n\r\n }\r\n\r\n while (anchorNode.contentEditable != 'true') {\r\n\r\n anchorNode = anchorNode.parentNode;\r\n\r\n if (anchorNode.contentEditable == 'true') {\r\n\r\n flag = true;\r\n\r\n }\r\n\r\n if (anchorNode == document.body) {\r\n\r\n break;\r\n\r\n }\r\n\r\n }\r\n\r\n /** If editable element founded, flag is \"TRUE\", Therefore we return \"FALSE\" */\r\n editor.content.editorAreaHightlighted = !flag;\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Toolbar button click handler\r\n *\r\n * @param {Object} event - cursor to the button\r\n * @protected\r\n *\r\n * @description gets current tool and calls render method\r\n */\r\n callbacks.toolbarButtonClicked = function (event) {\r\n\r\n var button = this;\r\n\r\n editor.toolbar.current = button.dataset.type;\r\n\r\n editor.toolbar.toolbox.toolClicked(event);\r\n editor.toolbar.close();\r\n\r\n };\r\n\r\n /**\r\n * Show or Hide toolbox when plus button is clicked\r\n */\r\n callbacks.plusButtonClicked = function () {\r\n\r\n if (!editor.nodes.toolbox.classList.contains('opened')) {\r\n\r\n editor.toolbar.toolbox.open();\r\n\r\n } else {\r\n\r\n editor.toolbar.toolbox.close();\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Block handlers for KeyDown events\r\n *\r\n * @protected\r\n * @param {Object} event\r\n *\r\n * Handles keydowns on block\r\n * @see blockRightOrDownArrowPressed_\r\n * @see backspacePressed_\r\n * @see blockLeftOrUpArrowPressed_\r\n */\r\n callbacks.blockKeydown = function (event) {\r\n\r\n let block = event.target; // event.target is input\r\n\r\n switch (event.keyCode) {\r\n\r\n case editor.core.keys.DOWN:\r\n case editor.core.keys.RIGHT:\r\n blockRightOrDownArrowPressed_(event);\r\n break;\r\n\r\n case editor.core.keys.BACKSPACE:\r\n backspacePressed_(block, event);\r\n break;\r\n\r\n case editor.core.keys.UP:\r\n case editor.core.keys.LEFT:\r\n blockLeftOrUpArrowPressed_(event);\r\n break;\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * RIGHT or DOWN keydowns on block\r\n *\r\n * @param {Object} event\r\n * @private\r\n *\r\n * @description watches the selection and gets closest editable element.\r\n * Uses method getDeepestTextNodeFromPosition to get the last node of next block\r\n * Sets caret if it is contenteditable\r\n */\r\n var blockRightOrDownArrowPressed_ = function (event) {\r\n\r\n var selection = window.getSelection(),\r\n inputs = editor.state.inputs,\r\n focusedNode = selection.anchorNode,\r\n focusedNodeHolder;\r\n\r\n /** Check for caret existance */\r\n if (!focusedNode) {\r\n\r\n return false;\r\n\r\n }\r\n\r\n /** Looking for closest (parent) contentEditable element of focused node */\r\n while (focusedNode.contentEditable != 'true') {\r\n\r\n focusedNodeHolder = focusedNode.parentNode;\r\n focusedNode = focusedNodeHolder;\r\n\r\n }\r\n\r\n /** Input index in DOM level */\r\n var editableElementIndex = 0;\r\n\r\n while (focusedNode != inputs[editableElementIndex]) {\r\n\r\n editableElementIndex ++;\r\n\r\n }\r\n\r\n /**\r\n * Founded contentEditable element doesn't have childs\r\n * Or maybe New created block\r\n */\r\n if (!focusedNode.textContent) {\r\n\r\n editor.caret.setToNextBlock(editableElementIndex);\r\n return;\r\n\r\n }\r\n\r\n /**\r\n * Do nothing when caret doesn not reaches the end of last child\r\n */\r\n var caretInLastChild = false,\r\n caretAtTheEndOfText = false;\r\n\r\n var lastChild,\r\n deepestTextnode;\r\n\r\n lastChild = focusedNode.childNodes[focusedNode.childNodes.length - 1 ];\r\n\r\n if (editor.core.isDomNode(lastChild)) {\r\n\r\n deepestTextnode = editor.content.getDeepestTextNodeFromPosition(lastChild, lastChild.childNodes.length);\r\n\r\n } else {\r\n\r\n deepestTextnode = lastChild;\r\n\r\n }\r\n\r\n caretInLastChild = selection.anchorNode == deepestTextnode;\r\n caretAtTheEndOfText = deepestTextnode.length == selection.anchorOffset;\r\n\r\n if ( !caretInLastChild || !caretAtTheEndOfText ) {\r\n\r\n editor.core.log('arrow [down|right] : caret does not reached the end');\r\n return false;\r\n\r\n }\r\n\r\n editor.caret.setToNextBlock(editableElementIndex);\r\n\r\n };\r\n\r\n /**\r\n * LEFT or UP keydowns on block\r\n *\r\n * @param {Object} event\r\n * @private\r\n *\r\n * watches the selection and gets closest editable element.\r\n * Uses method getDeepestTextNodeFromPosition to get the last node of previous block\r\n * Sets caret if it is contenteditable\r\n *\r\n */\r\n var blockLeftOrUpArrowPressed_ = function (event) {\r\n\r\n var selection = window.getSelection(),\r\n inputs = editor.state.inputs,\r\n focusedNode = selection.anchorNode,\r\n focusedNodeHolder;\r\n\r\n /** Check for caret existance */\r\n if (!focusedNode) {\r\n\r\n return false;\r\n\r\n }\r\n\r\n /**\r\n * LEFT or UP not at the beginning\r\n */\r\n if ( selection.anchorOffset !== 0) {\r\n\r\n return false;\r\n\r\n }\r\n\r\n /** Looking for parent contentEditable block */\r\n while (focusedNode.contentEditable != 'true') {\r\n\r\n focusedNodeHolder = focusedNode.parentNode;\r\n focusedNode = focusedNodeHolder;\r\n\r\n }\r\n\r\n /** Input index in DOM level */\r\n var editableElementIndex = 0;\r\n\r\n while (focusedNode != inputs[editableElementIndex]) {\r\n\r\n editableElementIndex ++;\r\n\r\n }\r\n\r\n /**\r\n * Do nothing if caret is not at the beginning of first child\r\n */\r\n var caretInFirstChild = false,\r\n caretAtTheBeginning = false;\r\n\r\n var firstChild,\r\n deepestTextnode;\r\n\r\n /**\r\n * Founded contentEditable element doesn't have childs\r\n * Or maybe New created block\r\n */\r\n if (!focusedNode.textContent) {\r\n\r\n editor.caret.setToPreviousBlock(editableElementIndex);\r\n return;\r\n\r\n }\r\n\r\n firstChild = focusedNode.childNodes[0];\r\n\r\n if (editor.core.isDomNode(firstChild)) {\r\n\r\n deepestTextnode = editor.content.getDeepestTextNodeFromPosition(firstChild, 0);\r\n\r\n } else {\r\n\r\n deepestTextnode = firstChild;\r\n\r\n }\r\n\r\n caretInFirstChild = selection.anchorNode == deepestTextnode;\r\n caretAtTheBeginning = selection.anchorOffset === 0;\r\n\r\n if ( caretInFirstChild && caretAtTheBeginning ) {\r\n\r\n editor.caret.setToPreviousBlock(editableElementIndex);\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Handles backspace keydown\r\n *\r\n * @param {Element} block\r\n * @param {Object} event\r\n * @private\r\n *\r\n * @description if block is empty, delete the block and set caret to the previous block\r\n * If block is not empty, try to merge two blocks - current and previous\r\n * But it we try'n to remove first block, then we should set caret to the next block, not previous.\r\n * If we removed the last block, create new one\r\n */\r\n var backspacePressed_ = function (block, event) {\r\n\r\n var currentInputIndex = editor.caret.getCurrentInputIndex(),\r\n range,\r\n selectionLength,\r\n firstLevelBlocksCount;\r\n\r\n if (editor.core.isNativeInput(event.target)) {\r\n\r\n /** If input value is empty - remove block */\r\n if (event.target.value.trim() == '') {\r\n\r\n block.remove();\r\n\r\n } else {\r\n\r\n return;\r\n\r\n }\r\n\r\n }\r\n\r\n if (block.textContent.trim()) {\r\n\r\n range = editor.content.getRange();\r\n selectionLength = range.endOffset - range.startOffset;\r\n\r\n if (editor.caret.position.atStart() && !selectionLength && editor.state.inputs[currentInputIndex - 1]) {\r\n\r\n editor.content.mergeBlocks(currentInputIndex);\r\n\r\n } else {\r\n\r\n return;\r\n\r\n }\r\n\r\n }\r\n\r\n if (!selectionLength) {\r\n\r\n block.remove();\r\n\r\n }\r\n\r\n\r\n firstLevelBlocksCount = editor.nodes.redactor.childNodes.length;\r\n\r\n /**\r\n * If all blocks are removed\r\n */\r\n if (firstLevelBlocksCount === 0) {\r\n\r\n /** update currentNode variable */\r\n editor.content.currentNode = null;\r\n\r\n /** Inserting new empty initial block */\r\n editor.ui.addInitialBlock();\r\n\r\n /** Updating inputs state after deleting last block */\r\n editor.ui.saveInputs();\r\n\r\n /** Set to current appended block */\r\n window.setTimeout(function () {\r\n\r\n editor.caret.setToPreviousBlock(1);\r\n\r\n }, 10);\r\n\r\n } else {\r\n\r\n if (editor.caret.inputIndex !== 0) {\r\n\r\n /** Target block is not first */\r\n editor.caret.setToPreviousBlock(editor.caret.inputIndex);\r\n\r\n } else {\r\n\r\n /** If we try to delete first block */\r\n editor.caret.setToNextBlock(editor.caret.inputIndex);\r\n\r\n }\r\n\r\n }\r\n\r\n editor.toolbar.move();\r\n\r\n if (!editor.toolbar.opened) {\r\n\r\n editor.toolbar.open();\r\n\r\n }\r\n\r\n /** Updating inputs state */\r\n editor.ui.saveInputs();\r\n\r\n /** Prevent default browser behaviour */\r\n event.preventDefault();\r\n\r\n };\r\n\r\n /**\r\n * used by UI module\r\n * Clicks on block settings button\r\n *\r\n * @param {Object} event\r\n * @protected\r\n * @description Opens toolbar settings\r\n */\r\n callbacks.showSettingsButtonClicked = function (event) {\r\n\r\n /**\r\n * Get type of current block\r\n * It uses to append settings from tool.settings property.\r\n * ...\r\n * Type is stored in data-type attribute on block\r\n */\r\n var currentToolType = editor.content.currentNode.dataset.tool;\r\n\r\n editor.toolbar.settings.toggle(currentToolType);\r\n\r\n /** Close toolbox when settings button is active */\r\n editor.toolbar.toolbox.close();\r\n editor.toolbar.settings.hideRemoveActions();\r\n\r\n };\r\n\r\n return callbacks;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/callbacks.js","/**\r\n * Codex Editor Draw module\r\n *\r\n * @author Codex Team\r\n * @version 1.0.\r\n */\r\n\r\nmodule.exports = (function (draw) {\r\n\r\n /**\r\n * Base editor wrapper\r\n */\r\n draw.wrapper = function () {\r\n\r\n var wrapper = document.createElement('div');\r\n\r\n wrapper.className += 'codex-editor';\r\n\r\n return wrapper;\r\n\r\n };\r\n\r\n /**\r\n * Content-editable holder\r\n */\r\n draw.redactor = function () {\r\n\r\n var redactor = document.createElement('div');\r\n\r\n redactor.className += 'ce-redactor';\r\n\r\n return redactor;\r\n\r\n };\r\n\r\n draw.ceBlock = function () {\r\n\r\n var block = document.createElement('DIV');\r\n\r\n block.className += 'ce_block';\r\n\r\n return block;\r\n\r\n };\r\n\r\n /**\r\n * Empty toolbar with toggler\r\n */\r\n draw.toolbar = function () {\r\n\r\n var bar = document.createElement('div');\r\n\r\n bar.className += 'ce-toolbar';\r\n\r\n return bar;\r\n\r\n };\r\n\r\n draw.toolbarContent = function () {\r\n\r\n var wrapper = document.createElement('DIV');\r\n\r\n wrapper.classList.add('ce-toolbar__content');\r\n\r\n return wrapper;\r\n\r\n };\r\n\r\n /**\r\n * Inline toolbar\r\n */\r\n draw.inlineToolbar = function () {\r\n\r\n var bar = document.createElement('DIV');\r\n\r\n bar.className += 'ce-toolbar-inline';\r\n\r\n return bar;\r\n\r\n };\r\n\r\n /**\r\n * Wrapper for inline toobar buttons\r\n */\r\n draw.inlineToolbarButtons = function () {\r\n\r\n var wrapper = document.createElement('DIV');\r\n\r\n wrapper.className += 'ce-toolbar-inline__buttons';\r\n\r\n return wrapper;\r\n\r\n };\r\n\r\n /**\r\n * For some actions\r\n */\r\n draw.inlineToolbarActions = function () {\r\n\r\n var wrapper = document.createElement('DIV');\r\n\r\n wrapper.className += 'ce-toolbar-inline__actions';\r\n\r\n return wrapper;\r\n\r\n };\r\n\r\n draw.inputForLink = function () {\r\n\r\n var input = document.createElement('INPUT');\r\n\r\n input.type = 'input';\r\n input.className += 'inputForLink';\r\n input.placeholder = 'Вставьте ссылку ...';\r\n input.setAttribute('form', 'defaultForm');\r\n\r\n input.setAttribute('autofocus', 'autofocus');\r\n\r\n return input;\r\n\r\n };\r\n\r\n /**\r\n * @todo Desc\r\n */\r\n draw.blockButtons = function () {\r\n\r\n var block = document.createElement('div');\r\n\r\n block.className += 'ce-toolbar__actions';\r\n\r\n return block;\r\n\r\n };\r\n\r\n /**\r\n * Block settings panel\r\n */\r\n draw.blockSettings = function () {\r\n\r\n var settings = document.createElement('div');\r\n\r\n settings.className += 'ce-settings';\r\n\r\n return settings;\r\n\r\n };\r\n\r\n draw.defaultSettings = function () {\r\n\r\n var div = document.createElement('div');\r\n\r\n div.classList.add('ce-settings_default');\r\n\r\n return div;\r\n\r\n };\r\n\r\n draw.pluginsSettings = function () {\r\n\r\n var div = document.createElement('div');\r\n\r\n div.classList.add('ce-settings_plugin');\r\n\r\n return div;\r\n\r\n };\r\n\r\n draw.plusButton = function () {\r\n\r\n var button = document.createElement('span');\r\n\r\n button.className = 'ce-toolbar__plus';\r\n // button.innerHTML = '';\r\n\r\n return button;\r\n\r\n };\r\n\r\n /**\r\n * Settings button in toolbar\r\n */\r\n draw.settingsButton = function () {\r\n\r\n var toggler = document.createElement('span');\r\n\r\n toggler.className = 'ce-toolbar__settings-btn';\r\n\r\n /** Toggler button*/\r\n toggler.innerHTML = '';\r\n\r\n return toggler;\r\n\r\n };\r\n\r\n /**\r\n * Redactor tools wrapper\r\n */\r\n\r\n draw.toolbox = function () {\r\n\r\n var wrapper = document.createElement('div');\r\n\r\n wrapper.className = 'ce-toolbar__tools';\r\n\r\n return wrapper;\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n *\r\n * Draws tool buttons for toolbox\r\n *\r\n * @param {String} type\r\n * @param {String} classname\r\n * @returns {Element}\r\n */\r\n draw.toolbarButton = function (type, classname) {\r\n\r\n var button = document.createElement('li'),\r\n toolIcon = document.createElement('i'),\r\n toolTitle = document.createElement('span');\r\n\r\n button.dataset.type = type;\r\n button.setAttribute('title', type);\r\n\r\n toolIcon.classList.add(classname);\r\n toolTitle.classList.add('ce_toolbar_tools--title');\r\n\r\n\r\n button.appendChild(toolIcon);\r\n button.appendChild(toolTitle);\r\n\r\n return button;\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n *\r\n * Draws tools for inline toolbar\r\n *\r\n * @param {String} type\r\n * @param {String} classname\r\n */\r\n draw.toolbarButtonInline = function (type, classname) {\r\n\r\n var button = document.createElement('BUTTON'),\r\n toolIcon = document.createElement('I');\r\n\r\n button.type = 'button';\r\n button.dataset.type = type;\r\n toolIcon.classList.add(classname);\r\n\r\n button.appendChild(toolIcon);\r\n\r\n return button;\r\n\r\n };\r\n\r\n /**\r\n * Redactor block\r\n */\r\n draw.block = function (tagName, content) {\r\n\r\n var node = document.createElement(tagName);\r\n\r\n node.innerHTML = content || '';\r\n\r\n return node;\r\n\r\n };\r\n\r\n /**\r\n * Creates Node with passed tagName and className\r\n * @param {string} tagName\r\n * @param {string} className\r\n * @param {object} properties - allow to assign properties\r\n */\r\n draw.node = function ( tagName, className, properties ) {\r\n\r\n var el = document.createElement( tagName );\r\n\r\n if ( className ) el.className = className;\r\n\r\n if ( properties ) {\r\n\r\n for (var name in properties) {\r\n\r\n el[name] = properties[name];\r\n\r\n }\r\n\r\n }\r\n\r\n return el;\r\n\r\n };\r\n\r\n /**\r\n * Unavailable plugin block\r\n */\r\n draw.unavailableBlock = function () {\r\n\r\n var wrapper = document.createElement('DIV');\r\n\r\n wrapper.classList.add('cdx-unavailable-block');\r\n\r\n return wrapper;\r\n\r\n };\r\n\r\n return draw;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/draw.js","/**\r\n * Codex Editor Caret Module\r\n *\r\n * @author Codex Team\r\n * @version 1.0\r\n */\r\n\r\nmodule.exports = (function (caret) {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * @var {int} InputIndex - editable element in DOM\r\n */\r\n caret.inputIndex = null;\r\n\r\n /**\r\n * @var {int} offset - caret position in a text node.\r\n */\r\n caret.offset = null;\r\n\r\n /**\r\n * @var {int} focusedNodeIndex - we get index of child node from first-level block\r\n */\r\n caret.focusedNodeIndex = null;\r\n\r\n /**\r\n * Creates Document Range and sets caret to the element.\r\n * @protected\r\n * @uses caret.save — if you need to save caret position\r\n * @param {Element} el - Changed Node.\r\n */\r\n caret.set = function ( el, index, offset) {\r\n\r\n offset = offset || caret.offset || 0;\r\n index = index || caret.focusedNodeIndex || 0;\r\n\r\n var childs = el.childNodes,\r\n nodeToSet;\r\n\r\n if ( childs.length === 0 ) {\r\n\r\n nodeToSet = el;\r\n\r\n } else {\r\n\r\n nodeToSet = childs[index];\r\n\r\n }\r\n\r\n /** If Element is INPUT */\r\n if (el.contentEditable != 'true') {\r\n\r\n el.focus();\r\n return;\r\n\r\n }\r\n\r\n if (editor.core.isDomNode(nodeToSet)) {\r\n\r\n nodeToSet = editor.content.getDeepestTextNodeFromPosition(nodeToSet, nodeToSet.childNodes.length);\r\n\r\n }\r\n\r\n var range = document.createRange(),\r\n selection = window.getSelection();\r\n\r\n window.setTimeout(function () {\r\n\r\n range.setStart(nodeToSet, offset);\r\n range.setEnd(nodeToSet, offset);\r\n\r\n selection.removeAllRanges();\r\n selection.addRange(range);\r\n\r\n editor.caret.saveCurrentInputIndex();\r\n\r\n }, 20);\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n * Updates index of input and saves it in caret object\r\n */\r\n caret.saveCurrentInputIndex = function () {\r\n\r\n /** Index of Input that we paste sanitized content */\r\n var selection = window.getSelection(),\r\n inputs = editor.state.inputs,\r\n focusedNode = selection.anchorNode,\r\n focusedNodeHolder;\r\n\r\n if (!focusedNode) {\r\n\r\n return;\r\n\r\n }\r\n\r\n /** Looking for parent contentEditable block */\r\n while (focusedNode.contentEditable != 'true') {\r\n\r\n focusedNodeHolder = focusedNode.parentNode;\r\n focusedNode = focusedNodeHolder;\r\n\r\n }\r\n\r\n /** Input index in DOM level */\r\n var editableElementIndex = 0;\r\n\r\n while (focusedNode != inputs[editableElementIndex]) {\r\n\r\n editableElementIndex ++;\r\n\r\n }\r\n\r\n caret.inputIndex = editableElementIndex;\r\n\r\n };\r\n\r\n /**\r\n * Returns current input index (caret object)\r\n */\r\n caret.getCurrentInputIndex = function () {\r\n\r\n return caret.inputIndex;\r\n\r\n };\r\n\r\n /**\r\n * @param {int} index - index of first-level block after that we set caret into next input\r\n */\r\n caret.setToNextBlock = function (index) {\r\n\r\n var inputs = editor.state.inputs,\r\n nextInput = inputs[index + 1];\r\n\r\n if (!nextInput) {\r\n\r\n editor.core.log('We are reached the end');\r\n return;\r\n\r\n }\r\n\r\n /**\r\n * When new Block created or deleted content of input\r\n * We should add some text node to set caret\r\n */\r\n if (!nextInput.childNodes.length) {\r\n\r\n var emptyTextElement = document.createTextNode('');\r\n\r\n nextInput.appendChild(emptyTextElement);\r\n\r\n }\r\n\r\n editor.caret.inputIndex = index + 1;\r\n editor.caret.set(nextInput, 0, 0);\r\n editor.content.workingNodeChanged(nextInput);\r\n\r\n };\r\n\r\n /**\r\n * @param {int} index - index of target input.\r\n * Sets caret to input with this index\r\n */\r\n caret.setToBlock = function (index) {\r\n\r\n var inputs = editor.state.inputs,\r\n targetInput = inputs[index];\r\n\r\n if ( !targetInput ) {\r\n\r\n return;\r\n\r\n }\r\n\r\n /**\r\n * When new Block created or deleted content of input\r\n * We should add some text node to set caret\r\n */\r\n if (!targetInput.childNodes.length) {\r\n\r\n var emptyTextElement = document.createTextNode('');\r\n\r\n targetInput.appendChild(emptyTextElement);\r\n\r\n }\r\n\r\n editor.caret.inputIndex = index;\r\n editor.caret.set(targetInput, 0, 0);\r\n editor.content.workingNodeChanged(targetInput);\r\n\r\n };\r\n\r\n /**\r\n * @param {int} index - index of input\r\n */\r\n caret.setToPreviousBlock = function (index) {\r\n\r\n index = index || 0;\r\n\r\n var inputs = editor.state.inputs,\r\n previousInput = inputs[index - 1],\r\n lastChildNode,\r\n lengthOfLastChildNode,\r\n emptyTextElement;\r\n\r\n\r\n if (!previousInput) {\r\n\r\n editor.core.log('We are reached first node');\r\n return;\r\n\r\n }\r\n\r\n lastChildNode = editor.content.getDeepestTextNodeFromPosition(previousInput, previousInput.childNodes.length);\r\n lengthOfLastChildNode = lastChildNode.length;\r\n\r\n /**\r\n * When new Block created or deleted content of input\r\n * We should add some text node to set caret\r\n */\r\n if (!previousInput.childNodes.length) {\r\n\r\n emptyTextElement = document.createTextNode('');\r\n previousInput.appendChild(emptyTextElement);\r\n\r\n }\r\n editor.caret.inputIndex = index - 1;\r\n editor.caret.set(previousInput, previousInput.childNodes.length - 1, lengthOfLastChildNode);\r\n editor.content.workingNodeChanged(inputs[index - 1]);\r\n\r\n };\r\n\r\n caret.position = {\r\n\r\n atStart : function () {\r\n\r\n var selection = window.getSelection(),\r\n anchorOffset = selection.anchorOffset,\r\n anchorNode = selection.anchorNode,\r\n firstLevelBlock = editor.content.getFirstLevelBlock(anchorNode),\r\n pluginsRender = firstLevelBlock.childNodes[0];\r\n\r\n if (!editor.core.isDomNode(anchorNode)) {\r\n\r\n anchorNode = anchorNode.parentNode;\r\n\r\n }\r\n\r\n var isFirstNode = anchorNode === pluginsRender.childNodes[0],\r\n isOffsetZero = anchorOffset === 0;\r\n\r\n return isFirstNode && isOffsetZero;\r\n\r\n },\r\n\r\n atTheEnd : function () {\r\n\r\n var selection = window.getSelection(),\r\n anchorOffset = selection.anchorOffset,\r\n anchorNode = selection.anchorNode;\r\n\r\n /** Caret is at the end of input */\r\n return !anchorNode || !anchorNode.length || anchorOffset === anchorNode.length;\r\n\r\n }\r\n };\r\n\r\n\r\n /**\r\n * Inserts node at the caret location\r\n * @param node\r\n */\r\n caret.insertNode = function (node) {\r\n\r\n var selection, range,\r\n lastNode = node;\r\n\r\n if (node instanceof window.DocumentFragment) {\r\n\r\n lastNode = node.lastChild;\r\n\r\n }\r\n\r\n selection = window.getSelection();\r\n\r\n range = selection.getRangeAt(0);\r\n range.deleteContents();\r\n\r\n range.insertNode(node);\r\n\r\n range.setStartAfter(lastNode);\r\n range.collapse(true);\r\n\r\n selection.removeAllRanges();\r\n selection.addRange(range);\r\n\r\n\r\n };\r\n\r\n return caret;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/caret.js","/**\r\n * Codex Editor Notification Module\r\n *\r\n * @author Codex Team\r\n * @version 1.0\r\n */\r\n\r\nmodule.exports = (function (notifications) {\r\n\r\n let editor = codex.editor;\r\n\r\n var queue = [];\r\n\r\n var addToQueue = function (settings) {\r\n\r\n queue.push(settings);\r\n\r\n var index = 0;\r\n\r\n while ( index < queue.length && queue.length > 5) {\r\n\r\n if (queue[index].type == 'confirm' || queue[index].type == 'prompt') {\r\n\r\n index++;\r\n continue;\r\n\r\n }\r\n\r\n queue[index].close();\r\n queue.splice(index, 1);\r\n\r\n }\r\n\r\n };\r\n\r\n notifications.createHolder = function () {\r\n\r\n var holder = editor.draw.node('DIV', 'cdx-notifications-block');\r\n\r\n editor.nodes.notifications = document.body.appendChild(holder);\r\n\r\n return holder;\r\n\r\n };\r\n\r\n\r\n /**\r\n * Error notificator. Shows block with message\r\n * @protected\r\n */\r\n notifications.errorThrown = function (errorMsg, event) {\r\n\r\n editor.notifications.notification({message: 'This action is not available currently', type: event.type});\r\n\r\n };\r\n\r\n /**\r\n *\r\n * Appends notification\r\n *\r\n * settings = {\r\n * type - notification type (reserved types: alert, confirm, prompt). Just add class 'cdx-notification-'+type\r\n * message - notification message\r\n * okMsg - confirm button text (default - 'Ok')\r\n * cancelBtn - cancel button text (default - 'Cancel'). Only for confirm and prompt types\r\n * confirm - function-handler for ok button click\r\n * cancel - function-handler for cancel button click. Only for confirm and prompt types\r\n * time - time (in seconds) after which notification will close (default - 10s)\r\n * }\r\n *\r\n * @param settings\r\n */\r\n notifications.notification = function (constructorSettings) {\r\n\r\n /** Private vars and methods */\r\n var notification = null,\r\n cancel = null,\r\n type = null,\r\n confirm = null,\r\n inputField = null;\r\n\r\n var confirmHandler = function () {\r\n\r\n close();\r\n\r\n if (typeof confirm !== 'function' ) {\r\n\r\n return;\r\n\r\n }\r\n\r\n if (type == 'prompt') {\r\n\r\n confirm(inputField.value);\r\n return;\r\n\r\n }\r\n\r\n confirm();\r\n\r\n };\r\n\r\n var cancelHandler = function () {\r\n\r\n close();\r\n\r\n if (typeof cancel !== 'function' ) {\r\n\r\n return;\r\n\r\n }\r\n\r\n cancel();\r\n\r\n };\r\n\r\n\r\n /** Public methods */\r\n function create(settings) {\r\n\r\n if (!(settings && settings.message)) {\r\n\r\n editor.core.log('Can\\'t create notification. Message is missed');\r\n return;\r\n\r\n }\r\n\r\n settings.type = settings.type || 'alert';\r\n settings.time = settings.time*1000 || 10000;\r\n\r\n var wrapper = editor.draw.node('DIV', 'cdx-notification'),\r\n message = editor.draw.node('DIV', 'cdx-notification__message'),\r\n input = editor.draw.node('INPUT', 'cdx-notification__input'),\r\n okBtn = editor.draw.node('SPAN', 'cdx-notification__ok-btn'),\r\n cancelBtn = editor.draw.node('SPAN', 'cdx-notification__cancel-btn');\r\n\r\n message.textContent = settings.message;\r\n okBtn.textContent = settings.okMsg || 'ОК';\r\n cancelBtn.textContent = settings.cancelMsg || 'Отмена';\r\n\r\n editor.listeners.add(okBtn, 'click', confirmHandler);\r\n editor.listeners.add(cancelBtn, 'click', cancelHandler);\r\n\r\n wrapper.appendChild(message);\r\n\r\n if (settings.type == 'prompt') {\r\n\r\n wrapper.appendChild(input);\r\n\r\n }\r\n\r\n wrapper.appendChild(okBtn);\r\n\r\n if (settings.type == 'prompt' || settings.type == 'confirm') {\r\n\r\n wrapper.appendChild(cancelBtn);\r\n\r\n }\r\n\r\n wrapper.classList.add('cdx-notification-' + settings.type);\r\n wrapper.dataset.type = settings.type;\r\n\r\n notification = wrapper;\r\n type = settings.type;\r\n confirm = settings.confirm;\r\n cancel = settings.cancel;\r\n inputField = input;\r\n\r\n if (settings.type != 'prompt' && settings.type != 'confirm') {\r\n\r\n window.setTimeout(close, settings.time);\r\n\r\n }\r\n\r\n };\r\n\r\n function send() {\r\n\r\n editor.nodes.notifications.appendChild(notification);\r\n inputField.focus();\r\n\r\n editor.nodes.notifications.classList.add('cdx-notification__notification-appending');\r\n\r\n window.setTimeout(function () {\r\n\r\n editor.nodes.notifications.classList.remove('cdx-notification__notification-appending');\r\n\r\n }, 100);\r\n\r\n addToQueue({type: type, close: close});\r\n\r\n };\r\n\r\n function close() {\r\n\r\n notification.remove();\r\n\r\n };\r\n\r\n\r\n if (constructorSettings) {\r\n\r\n create(constructorSettings);\r\n send();\r\n\r\n }\r\n\r\n return {\r\n create: create,\r\n send: send,\r\n close: close\r\n };\r\n\r\n };\r\n\r\n notifications.clear = function () {\r\n\r\n editor.nodes.notifications.innerHTML = '';\r\n queue = [];\r\n\r\n };\r\n\r\n return notifications;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/notifications.js","/**\r\n * Codex Editor Parser Module\r\n *\r\n * @author Codex Team\r\n * @version 1.1\r\n */\r\n\r\nmodule.exports = (function (parser) {\r\n\r\n let editor = codex.editor;\r\n\r\n /** inserting text */\r\n parser.insertPastedContent = function (blockType, tag) {\r\n\r\n editor.content.insertBlock({\r\n type : blockType.type,\r\n block : blockType.render({\r\n text : tag.innerHTML\r\n })\r\n });\r\n\r\n };\r\n\r\n /**\r\n * Check DOM node for display style: separated block or child-view\r\n */\r\n parser.isFirstLevelBlock = function (node) {\r\n\r\n return node.nodeType == editor.core.nodeTypes.TAG &&\r\n node.classList.contains(editor.ui.className.BLOCK_CLASSNAME);\r\n\r\n };\r\n\r\n return parser;\r\n\r\n})({});\r\n\n\n\n// WEBPACK FOOTER //\n// ./modules/parser.js","/**\r\n * Codex Sanitizer\r\n */\r\n\r\nmodule.exports = (function (sanitizer) {\r\n\r\n /** HTML Janitor library */\r\n let janitor = require('html-janitor');\r\n\r\n /** Codex Editor */\r\n let editor = codex.editor;\r\n\r\n sanitizer.prepare = function () {\r\n\r\n if (editor.settings.sanitizer && !editor.core.isEmpty(editor.settings.sanitizer)) {\r\n\r\n Config.CUSTOM = editor.settings.sanitizer;\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Basic config\r\n */\r\n var Config = {\r\n\r\n /** User configuration */\r\n CUSTOM : null,\r\n\r\n BASIC : {\r\n\r\n tags: {\r\n p: {},\r\n a: {\r\n href: true,\r\n target: '_blank',\r\n rel: 'nofollow'\r\n },\r\n i: {},\r\n b: {},\r\n strong: {},\r\n em: {},\r\n span: {}\r\n }\r\n }\r\n };\r\n\r\n sanitizer.Config = Config;\r\n\r\n /**\r\n *\r\n * @param userCustomConfig\r\n * @returns {*}\r\n * @private\r\n *\r\n * @description If developer uses editor's API, then he can customize sane restrictions.\r\n * Or, sane config can be defined globally in editors initialization. That config will be used everywhere\r\n * At least, if there is no config overrides, that API uses BASIC Default configation\r\n */\r\n let init_ = function (userCustomConfig) {\r\n\r\n let configuration = userCustomConfig || Config.CUSTOM || Config.BASIC;\r\n\r\n return new janitor(configuration);\r\n\r\n };\r\n\r\n /**\r\n * Cleans string from unwanted tags\r\n * @protected\r\n * @param {String} dirtyString - taint string\r\n * @param {Object} customConfig - allowed tags\r\n */\r\n sanitizer.clean = function (dirtyString, customConfig) {\r\n\r\n let janitorInstance = init_(customConfig);\r\n\r\n return janitorInstance.clean(dirtyString);\r\n\r\n };\r\n\r\n return sanitizer;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/sanitizer.js","(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n define('html-janitor', factory);\n } else if (typeof exports === 'object') {\n module.exports = factory();\n } else {\n root.HTMLJanitor = factory();\n }\n}(this, function () {\n\n /**\n * @param {Object} config.tags Dictionary of allowed tags.\n * @param {boolean} config.keepNestedBlockElements Default false.\n */\n function HTMLJanitor(config) {\n\n var tagDefinitions = config['tags'];\n var tags = Object.keys(tagDefinitions);\n\n var validConfigValues = tags\n .map(function(k) { return typeof tagDefinitions[k]; })\n .every(function(type) { return type === 'object' || type === 'boolean' || type === 'function'; });\n\n if(!validConfigValues) {\n throw new Error(\"The configuration was invalid\");\n }\n\n this.config = config;\n }\n\n // TODO: not exhaustive?\n var blockElementNames = ['P', 'LI', 'TD', 'TH', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'PRE'];\n function isBlockElement(node) {\n return blockElementNames.indexOf(node.nodeName) !== -1;\n }\n\n var inlineElementNames = ['A', 'B', 'STRONG', 'I', 'EM', 'SUB', 'SUP', 'U', 'STRIKE'];\n function isInlineElement(node) {\n return inlineElementNames.indexOf(node.nodeName) !== -1;\n }\n\n HTMLJanitor.prototype.clean = function (html) {\n var sandbox = document.createElement('div');\n sandbox.innerHTML = html;\n\n this._sanitize(sandbox);\n\n return sandbox.innerHTML;\n };\n\n HTMLJanitor.prototype._sanitize = function (parentNode) {\n var treeWalker = createTreeWalker(parentNode);\n var node = treeWalker.firstChild();\n if (!node) { return; }\n\n do {\n // Ignore nodes that have already been sanitized\n if (node._sanitized) {\n continue;\n }\n\n if (node.nodeType === Node.TEXT_NODE) {\n // If this text node is just whitespace and the previous or next element\n // sibling is a block element, remove it\n // N.B.: This heuristic could change. Very specific to a bug with\n // `contenteditable` in Firefox: http://jsbin.com/EyuKase/1/edit?js,output\n // FIXME: make this an option?\n if (node.data.trim() === ''\n && ((node.previousElementSibling && isBlockElement(node.previousElementSibling))\n || (node.nextElementSibling && isBlockElement(node.nextElementSibling)))) {\n parentNode.removeChild(node);\n this._sanitize(parentNode);\n break;\n } else {\n continue;\n }\n }\n\n // Remove all comments\n if (node.nodeType === Node.COMMENT_NODE) {\n parentNode.removeChild(node);\n this._sanitize(parentNode);\n break;\n }\n\n var isInline = isInlineElement(node);\n var containsBlockElement;\n if (isInline) {\n containsBlockElement = Array.prototype.some.call(node.childNodes, isBlockElement);\n }\n\n // Block elements should not be nested (e.g.

  • ...); if\n // they are, we want to unwrap the inner block element.\n var isNotTopContainer = !! parentNode.parentNode;\n var isNestedBlockElement =\n isBlockElement(parentNode) &&\n isBlockElement(node) &&\n isNotTopContainer;\n\n var nodeName = node.nodeName.toLowerCase();\n\n var allowedAttrs = getAllowedAttrs(this.config, nodeName, node);\n\n var isInvalid = isInline && containsBlockElement;\n\n // Drop tag entirely according to the whitelist *and* if the markup\n // is invalid.\n if (isInvalid || shouldRejectNode(node, allowedAttrs)\n || (!this.config.keepNestedBlockElements && isNestedBlockElement)) {\n // Do not keep the inner text of SCRIPT/STYLE elements.\n if (! (node.nodeName === 'SCRIPT' || node.nodeName === 'STYLE')) {\n while (node.childNodes.length > 0) {\n parentNode.insertBefore(node.childNodes[0], node);\n }\n }\n parentNode.removeChild(node);\n\n this._sanitize(parentNode);\n break;\n }\n\n // Sanitize attributes\n for (var a = 0; a < node.attributes.length; a += 1) {\n var attr = node.attributes[a];\n\n if (shouldRejectAttr(attr, allowedAttrs, node)) {\n node.removeAttribute(attr.name);\n // Shift the array to continue looping.\n a = a - 1;\n }\n }\n\n // Sanitize children\n this._sanitize(node);\n\n // Mark node as sanitized so it's ignored in future runs\n node._sanitized = true;\n } while ((node = treeWalker.nextSibling()));\n };\n\n function createTreeWalker(node) {\n return document.createTreeWalker(node,\n NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT,\n null, false);\n }\n\n function getAllowedAttrs(config, nodeName, node){\n if (typeof config.tags[nodeName] === 'function') {\n return config.tags[nodeName](node);\n } else {\n return config.tags[nodeName];\n }\n }\n\n function shouldRejectNode(node, allowedAttrs){\n if (typeof allowedAttrs === 'undefined') {\n return true;\n } else if (typeof allowedAttrs === 'boolean') {\n return !allowedAttrs;\n }\n\n return false;\n }\n\n function shouldRejectAttr(attr, allowedAttrs, node){\n var attrName = attr.name.toLowerCase();\n\n if (allowedAttrs === true){\n return false;\n } else if (typeof allowedAttrs[attrName] === 'function'){\n return !allowedAttrs[attrName](attr.value, node);\n } else if (typeof allowedAttrs[attrName] === 'undefined'){\n return true;\n } else if (allowedAttrs[attrName] === false) {\n return true;\n } else if (typeof allowedAttrs[attrName] === 'string') {\n return (allowedAttrs[attrName] !== attr.value);\n }\n\n return false;\n }\n\n return HTMLJanitor;\n\n}));\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./~/html-janitor/src/html-janitor.js\n// module id = 18\n// module chunks = 0","/**\r\n * Codex Editor Listeners module\r\n *\r\n * @author Codex Team\r\n * @version 1.0\r\n */\r\n\r\n/**\r\n * Module-decorator for event listeners assignment\r\n */\r\nmodule.exports = function (listeners) {\r\n\r\n var allListeners = [];\r\n\r\n /**\r\n * Search methods\r\n *\r\n * byElement, byType and byHandler returns array of suitable listeners\r\n * one and all takes element, eventType, and handler and returns first (all) suitable listener\r\n *\r\n */\r\n listeners.search = function () {\r\n\r\n var byElement = function (element, context) {\r\n\r\n var listenersOnElement = [];\r\n\r\n context = context || allListeners;\r\n\r\n for (var i = 0; i < context.length; i++) {\r\n\r\n var listener = context[i];\r\n\r\n if (listener.element === element) {\r\n\r\n listenersOnElement.push(listener);\r\n\r\n }\r\n\r\n }\r\n\r\n return listenersOnElement;\r\n\r\n };\r\n\r\n var byType = function (eventType, context) {\r\n\r\n var listenersWithType = [];\r\n\r\n context = context || allListeners;\r\n\r\n for (var i = 0; i < context.length; i++) {\r\n\r\n var listener = context[i];\r\n\r\n if (listener.type === eventType) {\r\n\r\n listenersWithType.push(listener);\r\n\r\n }\r\n\r\n }\r\n\r\n return listenersWithType;\r\n\r\n };\r\n\r\n var byHandler = function (handler, context) {\r\n\r\n var listenersWithHandler = [];\r\n\r\n context = context || allListeners;\r\n\r\n for (var i = 0; i < context.length; i++) {\r\n\r\n var listener = context[i];\r\n\r\n if (listener.handler === handler) {\r\n\r\n listenersWithHandler.push(listener);\r\n\r\n }\r\n\r\n }\r\n\r\n return listenersWithHandler;\r\n\r\n };\r\n\r\n var one = function (element, eventType, handler) {\r\n\r\n var result = allListeners;\r\n\r\n if (element)\r\n result = byElement(element, result);\r\n\r\n if (eventType)\r\n result = byType(eventType, result);\r\n\r\n if (handler)\r\n result = byHandler(handler, result);\r\n\r\n return result[0];\r\n\r\n };\r\n\r\n var all = function (element, eventType, handler) {\r\n\r\n var result = allListeners;\r\n\r\n if (element)\r\n result = byElement(element, result);\r\n\r\n if (eventType)\r\n result = byType(eventType, result);\r\n\r\n if (handler)\r\n result = byHandler(handler, result);\r\n\r\n return result;\r\n\r\n };\r\n\r\n return {\r\n byElement : byElement,\r\n byType : byType,\r\n byHandler : byHandler,\r\n one : one,\r\n all : all\r\n };\r\n\r\n }();\r\n\r\n listeners.add = function (element, eventType, handler, isCapture) {\r\n\r\n element.addEventListener(eventType, handler, isCapture);\r\n\r\n var data = {\r\n element: element,\r\n type: eventType,\r\n handler: handler\r\n };\r\n\r\n var alreadyAddedListener = listeners.search.one(element, eventType, handler);\r\n\r\n if (!alreadyAddedListener) {\r\n\r\n allListeners.push(data);\r\n\r\n }\r\n\r\n };\r\n\r\n listeners.remove = function (element, eventType, handler) {\r\n\r\n element.removeEventListener(eventType, handler);\r\n\r\n var existingListeners = listeners.search.all(element, eventType, handler);\r\n\r\n for (var i = 0; i < existingListeners.length; i++) {\r\n\r\n var index = allListeners.indexOf(existingListeners[i]);\r\n\r\n if (index > 0) {\r\n\r\n allListeners.splice(index, 1);\r\n\r\n }\r\n\r\n }\r\n\r\n };\r\n\r\n listeners.removeAll = function () {\r\n\r\n allListeners.map(function (current) {\r\n\r\n listeners.remove(current.element, current.type, current.handler);\r\n\r\n });\r\n\r\n };\r\n\r\n listeners.get = function (element, eventType, handler) {\r\n\r\n return listeners.search.all(element, eventType, handler);\r\n\r\n };\r\n\r\n return listeners;\r\n\r\n}({});\n\n\n// WEBPACK FOOTER //\n// ./modules/listeners.js","/**\r\n * Codex Editor Destroyer module\r\n *\r\n * @auhor Codex Team\r\n * @version 1.0\r\n */\r\n\r\nmodule.exports = function (destroyer) {\r\n\r\n let editor = codex.editor;\r\n\r\n destroyer.removeNodes = function () {\r\n\r\n editor.nodes.wrapper.remove();\r\n editor.nodes.notifications.remove();\r\n\r\n };\r\n\r\n destroyer.destroyPlugins = function () {\r\n\r\n for (var tool in editor.tools) {\r\n\r\n if (typeof editor.tools[tool].destroy === 'function') {\r\n\r\n editor.tools[tool].destroy();\r\n\r\n }\r\n\r\n }\r\n\r\n };\r\n\r\n destroyer.destroyScripts = function () {\r\n\r\n var scripts = document.getElementsByTagName('SCRIPT');\r\n\r\n for (var i = 0; i < scripts.length; i++) {\r\n\r\n if (scripts[i].id.indexOf(editor.scriptPrefix) + 1) {\r\n\r\n scripts[i].remove();\r\n i--;\r\n\r\n }\r\n\r\n }\r\n\r\n };\r\n\r\n\r\n /**\r\n * Delete editor data from webpage.\r\n * You should send settings argument with boolean flags:\r\n * @param settings.ui- remove redactor event listeners and DOM nodes\r\n * @param settings.scripts - remove redactor scripts from DOM\r\n * @param settings.plugins - remove plugin's objects\r\n * @param settings.core - remove editor core. You can remove core only if UI and scripts flags is true\r\n * }\r\n *\r\n */\r\n destroyer.destroy = function (settings) {\r\n\r\n if (!settings || typeof settings !== 'object') {\r\n\r\n return;\r\n\r\n }\r\n\r\n if (settings.ui) {\r\n\r\n destroyer.removeNodes();\r\n editor.listeners.removeAll();\r\n\r\n }\r\n\r\n if (settings.scripts) {\r\n\r\n destroyer.destroyScripts();\r\n\r\n }\r\n\r\n if (settings.plugins) {\r\n\r\n destroyer.destroyPlugins();\r\n\r\n }\r\n\r\n if (settings.ui && settings.scripts && settings.core) {\r\n\r\n delete codex.editor;\r\n\r\n }\r\n\r\n };\r\n\r\n return destroyer;\r\n\r\n}({});\n\n\n// WEBPACK FOOTER //\n// ./modules/destroyer.js","/**\r\n * Codex Editor Paste module\r\n *\r\n * @author Codex Team\r\n * @version 1.1.1\r\n */\r\n\r\nmodule.exports = function (paste) {\r\n\r\n let editor = codex.editor;\r\n\r\n var patterns = [];\r\n\r\n paste.prepare = function () {\r\n\r\n var tools = editor.tools;\r\n\r\n for (var tool in tools) {\r\n\r\n if (!tools[tool].renderOnPastePatterns || !Array.isArray(tools[tool].renderOnPastePatterns)) {\r\n\r\n continue;\r\n\r\n }\r\n\r\n tools[tool].renderOnPastePatterns.map(function (pattern) {\r\n\r\n\r\n patterns.push(pattern);\r\n\r\n });\r\n\r\n }\r\n\r\n return Promise.resolve();\r\n\r\n };\r\n\r\n /**\r\n * Saves data\r\n * @param event\r\n */\r\n paste.pasted = function (event) {\r\n\r\n var clipBoardData = event.clipboardData || window.clipboardData,\r\n content = clipBoardData.getData('Text');\r\n\r\n var result = analize(content);\r\n\r\n if (result) {\r\n\r\n event.preventDefault();\r\n event.stopImmediatePropagation();\r\n\r\n }\r\n\r\n return result;\r\n\r\n };\r\n\r\n /**\r\n * Analizes pated string and calls necessary method\r\n */\r\n\r\n var analize = function (string) {\r\n\r\n var result = false,\r\n content = editor.content.currentNode,\r\n plugin = content.dataset.tool;\r\n\r\n patterns.map( function (pattern) {\r\n\r\n var execArray = pattern.regex.exec(string),\r\n match = execArray && execArray[0];\r\n\r\n if ( match && match === string.trim()) {\r\n\r\n /** current block is not empty */\r\n if ( content.textContent.trim() && plugin == editor.settings.initialBlockPlugin ) {\r\n\r\n pasteToNewBlock_();\r\n\r\n }\r\n\r\n pattern.callback(string, pattern);\r\n result = true;\r\n\r\n }\r\n\r\n });\r\n\r\n return result;\r\n\r\n };\r\n\r\n var pasteToNewBlock_ = function () {\r\n\r\n /** Create new initial block */\r\n editor.content.insertBlock({\r\n\r\n type : editor.settings.initialBlockPlugin,\r\n block : editor.tools[editor.settings.initialBlockPlugin].render({\r\n text : ''\r\n })\r\n\r\n }, false);\r\n\r\n };\r\n\r\n /**\r\n * This method prevents default behaviour.\r\n *\r\n * @param {Object} event\r\n * @protected\r\n *\r\n * @description We get from clipboard pasted data, sanitize, make a fragment that contains of this sanitized nodes.\r\n * Firstly, we need to memorize the caret position. We can do that by getting the range of selection.\r\n * After all, we insert clear fragment into caret placed position. Then, we should move the caret to the last node\r\n */\r\n paste.blockPasteCallback = function (event) {\r\n\r\n\r\n if (!needsToHandlePasteEvent(event.target)) {\r\n\r\n return;\r\n\r\n }\r\n\r\n /** Prevent default behaviour */\r\n event.preventDefault();\r\n\r\n /** get html pasted data - dirty data */\r\n var htmlData = event.clipboardData.getData('text/html'),\r\n plainData = event.clipboardData.getData('text/plain');\r\n\r\n /** Temporary DIV that is used to work with text's paragraphs as DOM-elements*/\r\n var paragraphs = editor.draw.node('DIV', '', {}),\r\n cleanData,\r\n wrappedData;\r\n\r\n /** Create fragment, that we paste to range after proccesing */\r\n cleanData = editor.sanitizer.clean(htmlData);\r\n\r\n /**\r\n * We wrap pasted text with

    tags to split it logically\r\n * @type {string}\r\n */\r\n wrappedData = editor.content.wrapTextWithParagraphs(cleanData, plainData);\r\n paragraphs.innerHTML = wrappedData;\r\n\r\n /**\r\n * If there only one paragraph, just insert in at the caret location\r\n */\r\n if (paragraphs.childNodes.length == 1) {\r\n\r\n emulateUserAgentBehaviour(paragraphs.firstChild);\r\n return;\r\n\r\n }\r\n\r\n insertPastedParagraphs(paragraphs.childNodes);\r\n\r\n };\r\n\r\n /**\r\n * Checks if we should handle paste event on block\r\n * @param block\r\n *\r\n * @return {boolean}\r\n */\r\n var needsToHandlePasteEvent = function (block) {\r\n\r\n /** If area is input or textarea then allow default behaviour */\r\n if ( editor.core.isNativeInput(block) ) {\r\n\r\n return false;\r\n\r\n }\r\n\r\n var editableParent = editor.content.getEditableParent(block);\r\n\r\n /** Allow paste when event target placed in Editable element */\r\n if (!editableParent) {\r\n\r\n return false;\r\n\r\n }\r\n\r\n return true;\r\n\r\n };\r\n\r\n /**\r\n * Inserts new initial plugin blocks with data in paragraphs\r\n *\r\n * @param {Array} paragraphs - array of paragraphs (

    ) whit content, that should be inserted\r\n */\r\n var insertPastedParagraphs = function (paragraphs) {\r\n\r\n var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin,\r\n currentBlockContent = editor.content.currentNode.firstChild.firstChild;\r\n\r\n paragraphs.forEach(function (paragraph, index) {\r\n\r\n /** Don't allow empty paragraphs */\r\n if (paragraph.innerHTML.trim() === '') {\r\n\r\n return;\r\n\r\n }\r\n\r\n /**\r\n * If there was no data in working node, replace it with first paragraph of pasted text\r\n */\r\n if (index == 0 && currentBlockContent.innerHTML.trim() === '') {\r\n\r\n editor.content.switchBlock(editor.content.currentNode, editor.tools[NEW_BLOCK_TYPE].render({\r\n text : paragraph.innerHTML\r\n }), NEW_BLOCK_TYPE);\r\n\r\n return;\r\n\r\n }\r\n\r\n editor.content.insertBlock({\r\n type : NEW_BLOCK_TYPE,\r\n block : editor.tools[NEW_BLOCK_TYPE].render({\r\n text : paragraph.innerHTML\r\n })\r\n });\r\n\r\n editor.caret.inputIndex++;\r\n\r\n });\r\n\r\n editor.caret.setToPreviousBlock(editor.caret.getCurrentInputIndex() + 1);\r\n\r\n\r\n };\r\n\r\n /**\r\n * Inserts node content at the caret position\r\n *\r\n * @param {Node} node - DOM node (could be DocumentFragment), that should be inserted at the caret location\r\n */\r\n var emulateUserAgentBehaviour = function (node) {\r\n\r\n var newNode;\r\n\r\n if (node.childElementCount) {\r\n\r\n newNode = document.createDocumentFragment();\r\n\r\n node.childNodes.forEach(function (current) {\r\n\r\n if (!editor.core.isDomNode(current) && current.data.trim() === '') {\r\n\r\n return;\r\n\r\n }\r\n\r\n newNode.appendChild(current.cloneNode(true));\r\n\r\n });\r\n\r\n } else {\r\n\r\n newNode = document.createTextNode(node.textContent);\r\n\r\n }\r\n\r\n editor.caret.insertNode(newNode);\r\n\r\n };\r\n\r\n\r\n return paste;\r\n\r\n}({});\n\n\n// WEBPACK FOOTER //\n// ./modules/paste.js"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack:///codex-editor.js","webpack:///webpack/bootstrap 0918e0998f907537cc62","webpack:///./codex.js","webpack:///./modules/core.js","webpack:///./modules/tools.js","webpack:///./modules/ui.js","webpack:///./modules/transport.js","webpack:///./modules/renderer.js","webpack:///./modules/saver.js","webpack:///./modules/content.js","webpack:///./modules/toolbar/toolbar.js","webpack:///./modules/toolbar/settings.js","webpack:///./modules/toolbar/inline.js","webpack:///./modules/toolbar/toolbox.js","webpack:///./modules/callbacks.js","webpack:///./modules/draw.js","webpack:///./modules/caret.js","webpack:///./modules/notifications.js","webpack:///./modules/parser.js","webpack:///./modules/sanitizer.js","webpack:///./~/html-janitor/src/html-janitor.js","webpack:///./modules/listeners.js","webpack:///./modules/destroyer.js","webpack:///./modules/paste.js"],"names":["codex","modules","__webpack_require__","moduleId","installedModules","exports","module","id","loaded","call","m","c","p","editor","version","scriptPrefix","init","core","tools","ui","transport","renderer","saver","content","toolbar","callback","draw","caret","notifications","parser","sanitizer","listeners","destroyer","paste","settings","holderId","initialBlockPlugin","nodes","holder","wrapper","inlineToolbar","buttons","actions","toolbox","plusButton","showSettingsButton","showTrashButton","blockSettings","pluginSettings","defaultSettings","toolbarButtons","redactor","state","jsonOutput","blocks","inputs","start","userSettings","prepare","then","makeBlocksFromData","saveInputs","catch","error","log","_typeof","Symbol","iterator","obj","constructor","prototype","Promise","resolve","reject","data","hideToolbar","document","getElementById","undefined","Error","msg","type","arg","window","console","e","insertAfter","target","element","parentNode","insertBefore","nextSibling","nodeTypes","TAG","TEXT","COMMENT","DOCUMENT_FRAGMENT","keys","BACKSPACE","TAB","ENTER","SHIFT","CTRL","ALT","ESC","SPACE","LEFT","UP","DOWN","RIGHT","DELETE","META","isDomNode","el","nodeType","this","isEmpty","Object","length","ajax","url","encodedString","isFormData","prop","XMLHTTP","XMLHttpRequest","ActiveXObject","async","test","encodeURIComponent","withCredentials","responseContext","beforeSendResult","beforeSend","open","isFormData_","setRequestHeader","progress","upload","onprogress","bind","onreadystatechange","readyState","status","success","responseText","send","importScript","scriptPath","instanceName","script","createElement","defer","onload","onerror","src","head","appendChild","object","FormData","isNativeInput","nativeInputAreas","indexOf","tagName","isBlockEmpty","block","EXCEPTION_TAGS","nativeInputs","querySelectorAll","nativeInputsAreEmpty","textContentIsEmpty","textContent","trim","Array","forEach","input","value","resolve_","reject_","pluginsRequiresPreparation","allPlugins","pluginName","plugin","push","waitAllPluginsPreparation_","plugins","allPluginsProcessed__","reduce","previousValue","iteration","pluginIsReady__","callPluginsPrepareMethod_","available","loadingMessage","config","className","BLOCK_CLASSNAME","BLOCK_CONTENT","BLOCK_STRETCHED","BLOCK_HIGHLIGHTED","SETTINGS_ITEM","makeToolBar_","addTools_","makeInlineToolbar_","addInlineToolbarTools_","makeNotificationHolder_","bindEvents_","container","inlineToolbarButtons","inlineToolbarActions","blockButtons","makeToolbarSettings_","toolbarContent","makeToolbarContent_","settingsButton","makeRemoveBlockButton","pluginsSettings","createHolder","tool","toolName","toolButton","iconClassname","displayInToolbox","render","toolbarButton","bold","icon","command","italic","underline","link","name","toolbarButtonInline","setInlineToolbarButtonBehaviour","add","globalKeydown","redactorKeyDown","globalKeyup","redactorClicked","plusButtonClicked","showSettingsButtonClicked","button","toolbarButtonClicked","addBlockHandlers","blockKeydown","blockPasteCallback","inline","show","map","current","addInitialBlock","initialBlock","initialBlockType","setAttribute","insertBlock","workingNodeChanged","event","toolClicked","currentRequest","arguments","node","fileSelected","clearInput","i","files","formData","multiple","append","selectAndUpload","args","accept","click","abort","items","appendBlocks","nodeSequence","index","appendNodeAtIndex","getNodeAsync","createBlockFromData","blockData","blocksList","position","toolData","unavailableBlock","innerHTML","dataset","inputPosition","stretched","isStretched","saveBlocks","html","childNodes","makeQueue","queue","getBlockData","makeFormDataFromBlocks","save","savedData","output","blockContent","pluginsContent","validate","result","currentNode","editorAreaHightlighted","sync","markBlock","classList","clearMark","remove","getFirstLevelBlock","body","contains","targetNode","replaceBlock","targetBlock","newBlock","replaceChild","needPlaceCaret","workingBlock","newBlockContent","blockType","composeNewBlock_","currentInputIndex","getCurrentInputIndex","editableElement","querySelector","emptyText","createTextNode","set","move","showPlusButton","setTimeout","setToNextBlock","switchBlock","blockToReplace","newBlockComposed","getDeepestTextNodeFromPosition","text","blockChilds","removeChild","lookingFromStart","getRange","selection","getSelection","getRangeAt","splitBlock","inputIndex","textBeforeCaret","textNodeBeforeCaret","textAfterCaret","textNodeAfterCaret","anchorNode","anchorNodeText","caretOffset","anchorOffset","currentBlock","substring","previousChilds","nextChilds","reachedCurrent","child","previousChildsLength","nextChildsLength","newNode","NEW_BLOCK_TYPE","mergeBlocks","targetInputIndex","targetInput","currentInputContent","isLastNode","allChecked","allSiblingsEmpty_","sibling","wrapTextWithParagraphs","htmlData","plainData","wrapPlainTextWithParagraphs","paragraph","blockTyped","newWrapper","firstLevelBlocks","cloneNode","plainText","split","join","getEditableParent","contentEditable","defaultToolbarHeight","defaultOffset","opened","close","toggle","hidePlusButton","newYCoordinate","offsetTop","style","transform","Math","floor","hideRemoveActions","setting","toolType","makeSettings","settingsBlock","removeBlockWrapper","settingButton","actionWrapper","confirmAction","cancelAction","removeButtonClicked","confirmRemovingRequest","cancelRemovingRequest","action","showRemoveActions","firstLevelBlocksCount","buttonsOpened","actionsOpened","wrappersOffset","storedSelection","showInlineToolbar","selectedText","getSelectionText","showButtons","getWrappersOffset","newCoordinateX","newCoordinateY","coords","getSelectionCoords","offsetHeight","x","left","y","scrollY","top","closeButtons","closeAction","createLinkAction","defaultToolAction","hightlight","offset","getOffset","_x","_y","isNaN","offsetLeft","clientLeft","clientTop","offsetParent","range","sel","createRange","collapse","boundingLeft","boundingTop","rangeCount","cloneRange","getClientRects","rect","toString","showActions","inlineToolbarAnchorInputKeydown_","keyCode","editable","restoreSelection","setAnchor","preventDefault","stopImmediatePropagation","clearRange","isActive","isLinkActive","saveSelection","inputForLink","focus","dataType","execCommand","containerEl","preSelectionRange","selectNodeContents","setEnd","startContainer","startOffset","end","savedSel","charIndex","setStart","nextCharIndex","nodeStack","foundStart","stop","pop","removeAllRanges","addRange","queryCommandState","setButtonHighlighted","removeButtonsHighLight","tag","leaf","currentTool","barButtons","nextToolIndex","toolToSelect","visibleTool","appendCallback","UNREPLACEBLE_TOOLS","workingNode","setToBlock","callbacks","enterKeyPressed_","tabKeyPressedOnRedactorsZone_","enterKeyPressedOnRedactorsZone_","escapeKeyPressedOnRedactorsZone_","defaultKeyPressedOnRedactorsZone_","arrowKeyPressed_","enterPressedOnBlock_","saveCurrentInputIndex","isEnterPressedOnToolbar","enableLineBreaks","stopPropagation","shiftKey","currentSelection","currentSelectedNode","caretAtTheEndOfText","atTheEnd","isTextNodeHasParentBetweenContenteditable","enterPressedOnBlock","islastNode","detectWhenClickedOnFirstLevelBlockArea_","firstLevelBlock","indexOfLastInput","inputIsEmpty","currentNodeType","isInitialType","flag","blockRightOrDownArrowPressed_","backspacePressed_","blockLeftOrUpArrowPressed_","focusedNodeHolder","focusedNode","editableElementIndex","lastChild","deepestTextnode","caretInLastChild","firstChild","caretInFirstChild","caretAtTheBeginning","setToPreviousBlock","selectionLength","endOffset","atStart","currentToolType","ceBlock","bar","placeholder","div","toggler","classname","toolIcon","toolTitle","properties","focusedNodeIndex","nodeToSet","childs","nextInput","emptyTextElement","lastChildNode","lengthOfLastChildNode","previousInput","pluginsRender","isFirstNode","isOffsetZero","insertNode","lastNode","deleteContents","setStartAfter","addToQueue","splice","errorThrown","errorMsg","notification","message","constructorSettings","create","time","okBtn","cancelBtn","okMsg","cancelMsg","confirmHandler","cancelHandler","confirm","cancel","inputField","clear","insertPastedContent","isFirstLevelBlock","janitor","Config","CUSTOM","BASIC","tags","a","href","rel","b","strong","em","span","init_","userCustomConfig","configuration","clean","dirtyString","customConfig","janitorInstance","__WEBPACK_AMD_DEFINE_FACTORY__","__WEBPACK_AMD_DEFINE_RESULT__","root","factory","HTMLJanitor","tagDefinitions","validConfigValues","k","every","isBlockElement","blockElementNames","nodeName","isInlineElement","inlineElementNames","createTreeWalker","NodeFilter","SHOW_TEXT","SHOW_ELEMENT","SHOW_COMMENT","getAllowedAttrs","shouldRejectNode","allowedAttrs","shouldRejectAttr","attr","attrName","toLowerCase","sandbox","_sanitize","treeWalker","_sanitized","Node","TEXT_NODE","COMMENT_NODE","containsBlockElement","isInline","some","isNotTopContainer","isNestedBlockElement","isInvalid","keepNestedBlockElements","attributes","removeAttribute","previousElementSibling","nextElementSibling","allListeners","search","byElement","context","listenersOnElement","listener","byType","eventType","listenersWithType","byHandler","handler","listenersWithHandler","one","all","isCapture","addEventListener","alreadyAddedListener","removeEventListener","existingListeners","removeAll","get","removeNodes","destroyPlugins","destroy","destroyScripts","scripts","getElementsByTagName","patterns","renderOnPastePatterns","isArray","pattern","pasted","clipBoardData","clipboardData","getData","analize","string","execArray","regex","exec","match","pasteToNewBlock_","needsToHandlePasteEvent","cleanData","wrappedData","paragraphs","emulateUserAgentBehaviour","insertPastedParagraphs","editableParent","childElementCount","createDocumentFragment"],"mappings":"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","file":"codex-editor.js","sourcesContent":["var codex = codex || {}; codex[\"editor\"] =\n/******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId])\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\texports: {},\n/******/ \t\t\tid: moduleId,\n/******/ \t\t\tloaded: false\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.loaded = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(0);\n/******/ })\n/************************************************************************/\n/******/ ([\n/* 0 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\t\n\t/**\r\n\t *\r\n\t * Codex Editor\r\n\t *\r\n\t * @author Codex Team\r\n\t */\n\t\n\tmodule.exports = function (editor) {\n\t\n\t 'use strict';\n\t\n\t editor.version = (\"1.6.4\");\n\t editor.scriptPrefix = 'cdx-script-';\n\t\n\t var init = function init() {\n\t\n\t editor.core = __webpack_require__(1);\n\t editor.tools = __webpack_require__(2);\n\t editor.ui = __webpack_require__(3);\n\t editor.transport = __webpack_require__(4);\n\t editor.renderer = __webpack_require__(5);\n\t editor.saver = __webpack_require__(6);\n\t editor.content = __webpack_require__(7);\n\t editor.toolbar = __webpack_require__(8);\n\t editor.callback = __webpack_require__(12);\n\t editor.draw = __webpack_require__(13);\n\t editor.caret = __webpack_require__(14);\n\t editor.notifications = __webpack_require__(15);\n\t editor.parser = __webpack_require__(16);\n\t editor.sanitizer = __webpack_require__(17);\n\t editor.listeners = __webpack_require__(19);\n\t editor.destroyer = __webpack_require__(20);\n\t editor.paste = __webpack_require__(21);\n\t };\n\t\n\t /**\r\n\t * @public\r\n\t * holds initial settings\r\n\t */\n\t editor.settings = {\n\t tools: ['paragraph', 'header', 'picture', 'list', 'quote', 'code', 'twitter', 'instagram', 'smile'],\n\t holderId: 'codex-editor',\n\t\n\t // Type of block showing on empty editor\n\t initialBlockPlugin: 'paragraph'\n\t };\n\t\n\t /**\r\n\t * public\r\n\t *\r\n\t * Static nodes\r\n\t */\n\t editor.nodes = {\n\t holder: null,\n\t wrapper: null,\n\t toolbar: null,\n\t inlineToolbar: {\n\t wrapper: null,\n\t buttons: null,\n\t actions: null\n\t },\n\t toolbox: null,\n\t notifications: null,\n\t plusButton: null,\n\t showSettingsButton: null,\n\t showTrashButton: null,\n\t blockSettings: null,\n\t pluginSettings: null,\n\t defaultSettings: null,\n\t toolbarButtons: {}, // { type : DomEl, ... }\n\t redactor: null\n\t };\n\t\n\t /**\r\n\t * @public\r\n\t *\r\n\t * Output state\r\n\t */\n\t editor.state = {\n\t jsonOutput: [],\n\t blocks: [],\n\t inputs: []\n\t };\n\t\n\t /**\r\n\t * @public\r\n\t * Editor plugins\r\n\t */\n\t editor.tools = {};\n\t\n\t /**\r\n\t * Initialization\r\n\t * @uses Promise cEditor.core.prepare\r\n\t * @param {Object} userSettings\r\n\t * @param {Array} userSettings.tools list of plugins\r\n\t * @param {String} userSettings.holderId Element's id to append editor\r\n\t *\r\n\t * Load user defined tools\r\n\t * Tools must contain this important objects :\r\n\t * @param {String} type - this is a type of plugin. It can be used as plugin name\r\n\t * @param {String} iconClassname - this a icon in toolbar\r\n\t * @param {Object} make - what should plugin do, when it is clicked\r\n\t * @param {Object} appendCallback - callback after clicking\r\n\t * @param {Element} settings - what settings does it have\r\n\t * @param {Object} render - plugin get JSON, and should return HTML\r\n\t * @param {Object} save - plugin gets HTML content, returns JSON\r\n\t * @param {Boolean} displayInToolbox - will be displayed in toolbox. Default value is TRUE\r\n\t * @param {Boolean} enableLineBreaks - inserts new block or break lines. Default value is FALSE\r\n\t *\r\n\t * @example\r\n\t * - type : 'header',\r\n\t * - iconClassname : 'ce-icon-header',\r\n\t * - make : headerTool.make,\r\n\t * - appendCallback : headerTool.appendCallback,\r\n\t * - settings : headerTool.makeSettings(),\r\n\t * - render : headerTool.render,\r\n\t * - save : headerTool.save,\r\n\t * - displayInToolbox : true,\r\n\t * - enableLineBreaks : false\r\n\t */\n\t editor.start = function (userSettings) {\n\t\n\t init();\n\t\n\t editor.core.prepare(userSettings)\n\t\n\t // If all ok, make UI, bind events and parse initial-content\n\t .then(editor.ui.prepare).then(editor.tools.prepare).then(editor.sanitizer.prepare).then(editor.paste.prepare).then(editor.transport.prepare).then(editor.renderer.makeBlocksFromData).then(editor.ui.saveInputs).catch(function (error) {\n\t\n\t editor.core.log('Initialization failed with error: %o', 'warn', error);\n\t });\n\t };\n\t\n\t return editor;\n\t}({});\n\n/***/ },\n/* 1 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\tvar _typeof = typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; };\n\t\n\t/**\r\n\t * Codex Editor Core\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.1.3\r\n\t */\n\t\n\tmodule.exports = function (core) {\n\t\n\t var editor = codex.editor;\n\t\n\t /**\r\n\t * @public\r\n\t *\r\n\t * Editor preparing method\r\n\t * @return Promise\r\n\t */\n\t core.prepare = function (userSettings) {\n\t\n\t return new Promise(function (resolve, reject) {\n\t\n\t if (userSettings) {\n\t\n\t editor.settings.tools = userSettings.tools || editor.settings.tools;\n\t }\n\t\n\t if (userSettings.data) {\n\t\n\t editor.state.blocks = userSettings.data;\n\t }\n\t\n\t if (userSettings.initialBlockPlugin) {\n\t\n\t editor.settings.initialBlockPlugin = userSettings.initialBlockPlugin;\n\t }\n\t\n\t if (userSettings.sanitizer) {\n\t\n\t editor.settings.sanitizer = userSettings.sanitizer;\n\t }\n\t\n\t editor.hideToolbar = userSettings.hideToolbar;\n\t\n\t editor.nodes.holder = document.getElementById(userSettings.holderId || editor.settings.holderId);\n\t\n\t if (_typeof(editor.nodes.holder) === undefined || editor.nodes.holder === null) {\n\t\n\t reject(Error(\"Holder wasn't found by ID: #\" + userSettings.holderId));\n\t } else {\n\t\n\t resolve();\n\t }\n\t });\n\t };\n\t\n\t /**\r\n\t * Logging method\r\n\t * @param type = ['log', 'info', 'warn']\r\n\t */\n\t core.log = function (msg, type, arg) {\n\t\n\t type = type || 'log';\n\t\n\t if (!arg) {\n\t\n\t arg = msg || 'undefined';\n\t msg = '[codex-editor]: %o';\n\t } else {\n\t\n\t msg = '[codex-editor]: ' + msg;\n\t }\n\t\n\t try {\n\t\n\t if ('console' in window && window.console[type]) {\n\t\n\t if (arg) window.console[type](msg, arg);else window.console[type](msg);\n\t }\n\t } catch (e) {}\n\t };\n\t\n\t /**\r\n\t * @protected\r\n\t *\r\n\t * Helper for insert one element after another\r\n\t */\n\t core.insertAfter = function (target, element) {\n\t\n\t target.parentNode.insertBefore(element, target.nextSibling);\n\t };\n\t\n\t /**\r\n\t * @const\r\n\t *\r\n\t * Readable DOM-node types map\r\n\t */\n\t core.nodeTypes = {\n\t TAG: 1,\n\t TEXT: 3,\n\t COMMENT: 8,\n\t DOCUMENT_FRAGMENT: 11\n\t };\n\t\n\t /**\r\n\t * @const\r\n\t * Readable keys map\r\n\t */\n\t core.keys = { BACKSPACE: 8, TAB: 9, ENTER: 13, SHIFT: 16, CTRL: 17, ALT: 18, ESC: 27, SPACE: 32, LEFT: 37, UP: 38, DOWN: 40, RIGHT: 39, DELETE: 46, META: 91 };\n\t\n\t /**\r\n\t * @protected\r\n\t *\r\n\t * Check object for DOM node\r\n\t */\n\t core.isDomNode = function (el) {\n\t\n\t return el && (typeof el === 'undefined' ? 'undefined' : _typeof(el)) === 'object' && el.nodeType && el.nodeType == this.nodeTypes.TAG;\n\t };\n\t\n\t /**\r\n\t * Checks passed object for emptiness\r\n\t * @require ES5 - Object.keys\r\n\t * @param {object}\r\n\t */\n\t core.isEmpty = function (obj) {\n\t\n\t return Object.keys(obj).length === 0;\n\t };\n\t\n\t /**\r\n\t * Native Ajax\r\n\t * @param {String} settings.url - request URL\r\n\t * @param {function} settings.beforeSend - returned value will be passed as context to the Success, Error and Progress callbacks\r\n\t * @param {function} settings.success\r\n\t * @param {function} settings.progress\r\n\t */\n\t core.ajax = function (settings) {\n\t\n\t if (!settings || !settings.url) {\n\t\n\t return;\n\t }\n\t\n\t var XMLHTTP = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'),\n\t encodedString,\n\t isFormData,\n\t prop;\n\t\n\t settings.async = true;\n\t settings.type = settings.type || 'GET';\n\t settings.data = settings.data || '';\n\t settings['content-type'] = settings['content-type'] || 'application/json; charset=utf-8';\n\t\n\t if (settings.type == 'GET' && settings.data) {\n\t\n\t settings.url = /\\?/.test(settings.url) ? settings.url + '&' + settings.data : settings.url + '?' + settings.data;\n\t } else {\n\t\n\t encodedString = '';\n\t for (prop in settings.data) {\n\t\n\t encodedString += prop + '=' + encodeURIComponent(settings.data[prop]) + '&';\n\t }\n\t }\n\t\n\t if (settings.withCredentials) {\n\t\n\t XMLHTTP.withCredentials = true;\n\t }\n\t\n\t /**\r\n\t * Value returned in beforeSend funtion will be passed as context to the other response callbacks\r\n\t * If beforeSend returns false, AJAX will be blocked\r\n\t */\n\t var responseContext = void 0,\n\t beforeSendResult = void 0;\n\t\n\t if (typeof settings.beforeSend === 'function') {\n\t\n\t beforeSendResult = settings.beforeSend.call();\n\t\n\t if (beforeSendResult === false) {\n\t\n\t return;\n\t }\n\t }\n\t\n\t XMLHTTP.open(settings.type, settings.url, settings.async);\n\t\n\t /**\r\n\t * If we send FormData, we need no content-type header\r\n\t */\n\t isFormData = isFormData_(settings.data);\n\t\n\t if (!isFormData) {\n\t\n\t if (settings.type !== 'POST') {\n\t\n\t XMLHTTP.setRequestHeader('Content-type', settings['content-type']);\n\t } else {\n\t\n\t XMLHTTP.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');\n\t }\n\t }\n\t\n\t XMLHTTP.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n\t\n\t responseContext = beforeSendResult || XMLHTTP;\n\t\n\t if (typeof settings.progress === 'function') {\n\t\n\t XMLHTTP.upload.onprogress = settings.progress.bind(responseContext);\n\t }\n\t\n\t XMLHTTP.onreadystatechange = function () {\n\t\n\t if (XMLHTTP.readyState === 4) {\n\t\n\t if (XMLHTTP.status === 200) {\n\t\n\t if (typeof settings.success === 'function') {\n\t\n\t settings.success.call(responseContext, XMLHTTP.responseText);\n\t }\n\t } else {\n\t\n\t if (typeof settings.error === 'function') {\n\t\n\t settings.error.call(responseContext, XMLHTTP.responseText, XMLHTTP.status);\n\t }\n\t }\n\t }\n\t };\n\t\n\t if (isFormData) {\n\t\n\t // Sending FormData\n\t XMLHTTP.send(settings.data);\n\t } else {\n\t\n\t // POST requests\n\t XMLHTTP.send(encodedString);\n\t }\n\t\n\t return XMLHTTP;\n\t };\n\t\n\t /**\r\n\t * Appends script to head of document\r\n\t * @return Promise\r\n\t */\n\t core.importScript = function (scriptPath, instanceName) {\n\t\n\t return new Promise(function (resolve, reject) {\n\t\n\t var script = void 0;\n\t\n\t /** Script is already loaded */\n\t if (!instanceName) {\n\t\n\t reject('Instance name is missed');\n\t } else if (document.getElementById(editor.scriptPrefix + instanceName)) {\n\t\n\t resolve(scriptPath);\n\t }\n\t\n\t script = document.createElement('SCRIPT');\n\t script.async = true;\n\t script.defer = true;\n\t script.id = editor.scriptPrefix + instanceName;\n\t\n\t script.onload = function () {\n\t\n\t resolve(scriptPath);\n\t };\n\t\n\t script.onerror = function () {\n\t\n\t reject(scriptPath);\n\t };\n\t\n\t script.src = scriptPath;\n\t document.head.appendChild(script);\n\t });\n\t };\n\t\n\t /**\r\n\t * Function for checking is it FormData object to send.\r\n\t * @param {Object} object to check\r\n\t * @return boolean\r\n\t */\n\t var isFormData_ = function isFormData_(object) {\n\t\n\t return object instanceof FormData;\n\t };\n\t\n\t /**\r\n\t * Check block\r\n\t * @param target\r\n\t * @description Checks target is it native input\r\n\t */\n\t core.isNativeInput = function (target) {\n\t\n\t var nativeInputAreas = ['INPUT', 'TEXTAREA'];\n\t\n\t return nativeInputAreas.indexOf(target.tagName) != -1;\n\t };\n\t\n\t /**\r\n\t * Check if block is empty\r\n\t * We should check block textContent, child native inputs and some exceptions like IMG and IFRAME\r\n\t *\r\n\t * @param block\r\n\t * @returns {boolean}\r\n\t */\n\t core.isBlockEmpty = function (block) {\n\t\n\t var EXCEPTION_TAGS = ['IMG', 'IFRAME'];\n\t\n\t var nativeInputs = block.querySelectorAll('textarea, input'),\n\t nativeInputsAreEmpty = true,\n\t textContentIsEmpty = !block.textContent.trim();\n\t\n\t Array.prototype.forEach.call(nativeInputs, function (input) {\n\t\n\t if (input.type == 'textarea' || input.type == 'text') {\n\t\n\t nativeInputsAreEmpty = nativeInputsAreEmpty && !input.value.trim();\n\t }\n\t });\n\t\n\t return textContentIsEmpty && nativeInputsAreEmpty && !(EXCEPTION_TAGS.indexOf(block.tagName) + 1);\n\t };\n\t\n\t return core;\n\t}({});\n\n/***/ },\n/* 2 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t* Module working with plugins\r\n\t*/\n\tmodule.exports = function () {\n\t\n\t var editor = codex.editor;\n\t\n\t /**\r\n\t * Initialize plugins before using\r\n\t * Ex. Load scripts or call some internal methods\r\n\t * @return Promise\r\n\t */\n\t function prepare() {\n\t\n\t return new Promise(function (resolve_, reject_) {\n\t\n\t Promise.resolve()\n\t\n\t /**\r\n\t * Compose a sequence of plugins that requires preparation\r\n\t */\n\t .then(function () {\n\t\n\t var pluginsRequiresPreparation = [],\n\t allPlugins = editor.tools;\n\t\n\t for (var pluginName in allPlugins) {\n\t\n\t var plugin = allPlugins[pluginName];\n\t\n\t if (plugin.prepare && typeof plugin.prepare != 'function' || !plugin.prepare) {\n\t\n\t continue;\n\t }\n\t\n\t pluginsRequiresPreparation.push(plugin);\n\t }\n\t\n\t /**\r\n\t * If no one passed plugins requires preparation, finish prepare() and go ahead\r\n\t */\n\t if (!pluginsRequiresPreparation.length) {\n\t\n\t resolve_();\n\t }\n\t\n\t return pluginsRequiresPreparation;\n\t })\n\t\n\t /** Wait plugins while they prepares */\n\t .then(waitAllPluginsPreparation_).then(function () {\n\t\n\t editor.core.log('Plugins loaded', 'info');\n\t resolve_();\n\t }).catch(function (error) {\n\t\n\t reject_(error);\n\t });\n\t });\n\t }\n\t\n\t /**\r\n\t * @param {array} plugins - list of tools that requires preparation\r\n\t * @return {Promise} resolved while all plugins will be ready or failed\r\n\t */\n\t function waitAllPluginsPreparation_(plugins) {\n\t\n\t /**\r\n\t * @calls allPluginsProcessed__ when all plugins prepared or failed\r\n\t */\n\t return new Promise(function (allPluginsProcessed__) {\n\t\n\t /**\r\n\t * pluck each element from queue\r\n\t * First, send resolved Promise as previous value\r\n\t * Each plugins \"prepare\" method returns a Promise, that's why\r\n\t * reduce current element will not be able to continue while can't get\r\n\t * a resolved Promise\r\n\t *\r\n\t * If last plugin is \"prepared\" then go to the next stage of initialization\r\n\t */\n\t plugins.reduce(function (previousValue, plugin, iteration) {\n\t\n\t return previousValue.then(function () {\n\t\n\t /**\r\n\t * Wait till plugins prepared\r\n\t * @calls pluginIsReady__ when plugin is ready or failed\r\n\t */\n\t return new Promise(function (pluginIsReady__) {\n\t\n\t callPluginsPrepareMethod_(plugin).then(pluginIsReady__).then(function () {\n\t\n\t plugin.available = true;\n\t }).catch(function (error) {\n\t\n\t editor.core.log('Plugin \\xAB' + plugin.type + '\\xBB was not loaded. Preparation failed because %o', 'warn', error);\n\t plugin.available = false;\n\t plugin.loadingMessage = error;\n\t\n\t /** Go ahead even some plugin has problems */\n\t pluginIsReady__();\n\t }).then(function () {\n\t\n\t /** If last plugin has problems then just ignore and continue */\n\t if (iteration == plugins.length - 1) {\n\t\n\t allPluginsProcessed__();\n\t }\n\t });\n\t });\n\t });\n\t }, Promise.resolve());\n\t });\n\t }\n\t\n\t var callPluginsPrepareMethod_ = function callPluginsPrepareMethod_(plugin) {\n\t\n\t return plugin.prepare(plugin.config || {});\n\t };\n\t\n\t return {\n\t prepare: prepare\n\t };\n\t}();\n\n/***/ },\n/* 3 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor UI module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.2.0\r\n\t */\n\t\n\tmodule.exports = function (ui) {\n\t\n\t var editor = codex.editor;\n\t\n\t /**\r\n\t * Basic editor classnames\r\n\t */\n\t ui.className = {\n\t\n\t /**\r\n\t * @const {string} BLOCK_CLASSNAME - redactor blocks name\r\n\t */\n\t BLOCK_CLASSNAME: 'ce-block',\n\t\n\t /**\r\n\t * @const {String} wrapper for plugins content\r\n\t */\n\t BLOCK_CONTENT: 'ce-block__content',\n\t\n\t /**\r\n\t * @const {String} BLOCK_STRETCHED - makes block stretched\r\n\t */\n\t BLOCK_STRETCHED: 'ce-block--stretched',\n\t\n\t /**\r\n\t * @const {String} BLOCK_HIGHLIGHTED - adds background\r\n\t */\n\t BLOCK_HIGHLIGHTED: 'ce-block--focused',\n\t\n\t /**\r\n\t * @const {String} - for all default settings\r\n\t */\n\t SETTINGS_ITEM: 'ce-settings__item'\n\t\n\t };\n\t\n\t /**\r\n\t * @protected\r\n\t *\r\n\t * Making main interface\r\n\t */\n\t ui.prepare = function () {\n\t\n\t return new Promise(function (resolve) {\n\t\n\t var wrapper = editor.draw.wrapper(),\n\t redactor = editor.draw.redactor(),\n\t toolbar = makeToolBar_();\n\t\n\t wrapper.appendChild(toolbar);\n\t wrapper.appendChild(redactor);\n\t\n\t /** Save created ui-elements to static nodes state */\n\t editor.nodes.wrapper = wrapper;\n\t editor.nodes.redactor = redactor;\n\t\n\t /** Append editor wrapper with redactor zone into holder */\n\t editor.nodes.holder.appendChild(wrapper);\n\t\n\t resolve();\n\t })\n\t\n\t /** Add toolbox tools */\n\t .then(addTools_)\n\t\n\t /** Make container for inline toolbar */\n\t .then(makeInlineToolbar_)\n\t\n\t /** Add inline toolbar tools */\n\t .then(addInlineToolbarTools_)\n\t\n\t /** Draw wrapper for notifications */\n\t .then(makeNotificationHolder_)\n\t\n\t /** Add eventlisteners to redactor elements */\n\t .then(bindEvents_).catch(function () {\n\t\n\t editor.core.log(\"Can't draw editor interface\");\n\t });\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t * Draws inline toolbar zone\r\n\t */\n\t var makeInlineToolbar_ = function makeInlineToolbar_() {\n\t\n\t var container = editor.draw.inlineToolbar();\n\t\n\t /** Append to redactor new inline block */\n\t editor.nodes.inlineToolbar.wrapper = container;\n\t\n\t /** Draw toolbar buttons */\n\t editor.nodes.inlineToolbar.buttons = editor.draw.inlineToolbarButtons();\n\t\n\t /** Buttons action or settings */\n\t editor.nodes.inlineToolbar.actions = editor.draw.inlineToolbarActions();\n\t\n\t /** Append to inline toolbar buttons as part of it */\n\t editor.nodes.inlineToolbar.wrapper.appendChild(editor.nodes.inlineToolbar.buttons);\n\t editor.nodes.inlineToolbar.wrapper.appendChild(editor.nodes.inlineToolbar.actions);\n\t\n\t editor.nodes.wrapper.appendChild(editor.nodes.inlineToolbar.wrapper);\n\t };\n\t\n\t var makeToolBar_ = function makeToolBar_() {\n\t\n\t var toolbar = editor.draw.toolbar(),\n\t blockButtons = makeToolbarSettings_(),\n\t toolbarContent = makeToolbarContent_();\n\t\n\t /** Appending first-level block buttons */\n\t toolbar.appendChild(blockButtons);\n\t\n\t /** Append toolbarContent to toolbar */\n\t toolbar.appendChild(toolbarContent);\n\t\n\t /** Make toolbar global */\n\t editor.nodes.toolbar = toolbar;\n\t\n\t return toolbar;\n\t };\n\t\n\t var makeToolbarContent_ = function makeToolbarContent_() {\n\t\n\t var toolbarContent = editor.draw.toolbarContent(),\n\t toolbox = editor.draw.toolbox(),\n\t plusButton = editor.draw.plusButton();\n\t\n\t /** Append plus button */\n\t toolbarContent.appendChild(plusButton);\n\t\n\t /** Appending toolbar tools */\n\t toolbarContent.appendChild(toolbox);\n\t\n\t /** Make Toolbox and plusButton global */\n\t editor.nodes.toolbox = toolbox;\n\t editor.nodes.plusButton = plusButton;\n\t\n\t return toolbarContent;\n\t };\n\t\n\t var makeToolbarSettings_ = function makeToolbarSettings_() {\n\t\n\t var blockSettings = editor.draw.blockSettings(),\n\t blockButtons = editor.draw.blockButtons(),\n\t defaultSettings = editor.draw.defaultSettings(),\n\t showSettingsButton = editor.draw.settingsButton(),\n\t showTrashButton = editor.toolbar.settings.makeRemoveBlockButton(),\n\t pluginSettings = editor.draw.pluginsSettings();\n\t\n\t /** Add default and plugins settings */\n\t blockSettings.appendChild(pluginSettings);\n\t blockSettings.appendChild(defaultSettings);\n\t\n\t /**\r\n\t * Make blocks buttons\r\n\t * This block contains settings button and remove block button\r\n\t */\n\t blockButtons.appendChild(showSettingsButton);\n\t blockButtons.appendChild(showTrashButton);\n\t blockButtons.appendChild(blockSettings);\n\t\n\t /** Make BlockSettings, PluginSettings, DefaultSettings global */\n\t editor.nodes.blockSettings = blockSettings;\n\t editor.nodes.pluginSettings = pluginSettings;\n\t editor.nodes.defaultSettings = defaultSettings;\n\t editor.nodes.showSettingsButton = showSettingsButton;\n\t editor.nodes.showTrashButton = showTrashButton;\n\t\n\t return blockButtons;\n\t };\n\t\n\t /** Draw notifications holder */\n\t var makeNotificationHolder_ = function makeNotificationHolder_() {\n\t\n\t /** Append block with notifications to the document */\n\t editor.nodes.notifications = editor.notifications.createHolder();\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t * Append tools passed in editor.tools\r\n\t */\n\t var addTools_ = function addTools_() {\n\t\n\t var tool, toolName, toolButton;\n\t\n\t for (toolName in editor.settings.tools) {\n\t\n\t tool = editor.settings.tools[toolName];\n\t\n\t editor.tools[toolName] = tool;\n\t\n\t if (!tool.iconClassname && tool.displayInToolbox) {\n\t\n\t editor.core.log('Toolbar icon classname missed. Tool %o skipped', 'warn', toolName);\n\t continue;\n\t }\n\t\n\t if (typeof tool.render != 'function') {\n\t\n\t editor.core.log('render method missed. Tool %o skipped', 'warn', toolName);\n\t continue;\n\t }\n\t\n\t if (!tool.displayInToolbox) {\n\t\n\t continue;\n\t } else {\n\t\n\t /** if tools is for toolbox */\n\t toolButton = editor.draw.toolbarButton(toolName, tool.iconClassname);\n\t\n\t editor.nodes.toolbox.appendChild(toolButton);\n\t\n\t editor.nodes.toolbarButtons[toolName] = toolButton;\n\t }\n\t }\n\t };\n\t\n\t var addInlineToolbarTools_ = function addInlineToolbarTools_() {\n\t\n\t var tools = {\n\t\n\t bold: {\n\t icon: 'ce-icon-bold',\n\t command: 'bold'\n\t },\n\t\n\t italic: {\n\t icon: 'ce-icon-italic',\n\t command: 'italic'\n\t },\n\t\n\t underline: {\n\t icon: 'ce-icon-underline',\n\t command: 'underline'\n\t },\n\t\n\t link: {\n\t icon: 'ce-icon-link',\n\t command: 'createLink'\n\t }\n\t };\n\t\n\t var toolButton, tool;\n\t\n\t for (var name in tools) {\n\t\n\t tool = tools[name];\n\t\n\t toolButton = editor.draw.toolbarButtonInline(name, tool.icon);\n\t\n\t editor.nodes.inlineToolbar.buttons.appendChild(toolButton);\n\t /**\r\n\t * Add callbacks to this buttons\r\n\t */\n\t editor.ui.setInlineToolbarButtonBehaviour(toolButton, tool.command);\n\t }\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t * Bind editor UI events\r\n\t */\n\t var bindEvents_ = function bindEvents_() {\n\t\n\t editor.core.log('ui.bindEvents fired', 'info');\n\t\n\t // window.addEventListener('error', function (errorMsg, url, lineNumber) {\n\t // editor.notifications.errorThrown(errorMsg, event);\n\t // }, false );\n\t\n\t /** All keydowns on Document */\n\t editor.listeners.add(document, 'keydown', editor.callback.globalKeydown, false);\n\t\n\t /** All keydowns on Redactor zone */\n\t editor.listeners.add(editor.nodes.redactor, 'keydown', editor.callback.redactorKeyDown, false);\n\t\n\t /** All keydowns on Document */\n\t editor.listeners.add(document, 'keyup', editor.callback.globalKeyup, false);\n\t\n\t /**\r\n\t * Mouse click to radactor\r\n\t */\n\t editor.listeners.add(editor.nodes.redactor, 'click', editor.callback.redactorClicked, false);\n\t\n\t /**\r\n\t * Clicks to the Plus button\r\n\t */\n\t editor.listeners.add(editor.nodes.plusButton, 'click', editor.callback.plusButtonClicked, false);\n\t\n\t /**\r\n\t * Clicks to SETTINGS button in toolbar\r\n\t */\n\t editor.listeners.add(editor.nodes.showSettingsButton, 'click', editor.callback.showSettingsButtonClicked, false);\n\t\n\t /** Bind click listeners on toolbar buttons */\n\t for (var button in editor.nodes.toolbarButtons) {\n\t\n\t editor.listeners.add(editor.nodes.toolbarButtons[button], 'click', editor.callback.toolbarButtonClicked, false);\n\t }\n\t };\n\t\n\t ui.addBlockHandlers = function (block) {\n\t\n\t if (!block) return;\n\t\n\t /**\r\n\t * Block keydowns\r\n\t */\n\t editor.listeners.add(block, 'keydown', editor.callback.blockKeydown, false);\n\t\n\t /**\r\n\t * Pasting content from another source\r\n\t * We have two type of sanitization\r\n\t * First - uses deep-first search algorithm to get sub nodes,\r\n\t * sanitizes whole Block_content and replaces cleared nodes\r\n\t * This method is deprecated\r\n\t * Method is used in editor.callback.blockPaste(event)\r\n\t *\r\n\t * Secont - uses Mutation observer.\r\n\t * Observer \"observe\" DOM changes and send changings to callback.\r\n\t * Callback gets changed node, not whole Block_content.\r\n\t * Inserted or changed node, which we've gotten have been cleared and replaced with diry node\r\n\t *\r\n\t * Method is used in editor.callback.blockPasteViaSanitize(event)\r\n\t *\r\n\t * @uses html-janitor\r\n\t * @example editor.callback.blockPasteViaSanitize(event), the second method.\r\n\t *\r\n\t */\n\t editor.listeners.add(block, 'paste', editor.paste.blockPasteCallback, false);\n\t\n\t editor.listeners.add(block, 'mouseup', editor.toolbar.inline.show, false);\n\t };\n\t\n\t /** getting all contenteditable elements */\n\t ui.saveInputs = function () {\n\t\n\t var redactor = editor.nodes.redactor;\n\t\n\t editor.state.inputs = [];\n\t\n\t /** Save all inputs in global variable state */\n\t var inputs = redactor.querySelectorAll('[contenteditable], input, textarea');\n\t\n\t Array.prototype.map.call(inputs, function (current) {\n\t\n\t if (!current.type || current.type == 'text' || current.type == 'textarea') {\n\t\n\t editor.state.inputs.push(current);\n\t }\n\t });\n\t };\n\t\n\t /**\r\n\t * Adds first initial block on empty redactor\r\n\t */\n\t ui.addInitialBlock = function () {\n\t\n\t var initialBlockType = editor.settings.initialBlockPlugin,\n\t initialBlock;\n\t\n\t if (!editor.tools[initialBlockType]) {\n\t\n\t editor.core.log('Plugin %o was not implemented and can\\'t be used as initial block', 'warn', initialBlockType);\n\t return;\n\t }\n\t\n\t initialBlock = editor.tools[initialBlockType].render();\n\t\n\t initialBlock.setAttribute('data-placeholder', 'Расскажите свою историю...');\n\t\n\t editor.content.insertBlock({\n\t type: initialBlockType,\n\t block: initialBlock\n\t });\n\t\n\t editor.content.workingNodeChanged(initialBlock);\n\t };\n\t\n\t ui.setInlineToolbarButtonBehaviour = function (button, type) {\n\t\n\t editor.listeners.add(button, 'mousedown', function (event) {\n\t\n\t editor.toolbar.inline.toolClicked(event, type);\n\t }, false);\n\t };\n\t\n\t return ui;\n\t}({});\n\n/***/ },\n/* 4 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t *\r\n\t * Codex.Editor Transport Module\r\n\t *\r\n\t * @copyright 2017 Codex-Team\r\n\t * @version 1.2.0\r\n\t */\n\t\n\tmodule.exports = function (transport) {\n\t\n\t var editor = codex.editor;\n\t\n\t /**\r\n\t * @private {Object} current XmlHttpRequest instance\r\n\t */\n\t var currentRequest = null;\n\t\n\t /**\r\n\t * @type {null} | {DOMElement} input - keeps input element in memory\r\n\t */\n\t transport.input = null;\n\t\n\t /**\r\n\t * @property {Object} arguments - keep plugin settings and defined callbacks\r\n\t */\n\t transport.arguments = null;\n\t\n\t /**\r\n\t * Prepares input element where will be files\r\n\t */\n\t transport.prepare = function () {\n\t\n\t var input = editor.draw.node('INPUT', '', { type: 'file' });\n\t\n\t editor.listeners.add(input, 'change', editor.transport.fileSelected);\n\t editor.transport.input = input;\n\t };\n\t\n\t /** Clear input when files is uploaded */\n\t transport.clearInput = function () {\n\t\n\t /** Remove old input */\n\t transport.input = null;\n\t\n\t /** Prepare new one */\n\t transport.prepare();\n\t };\n\t\n\t /**\r\n\t * Callback for file selection\r\n\t * @param {Event} event\r\n\t */\n\t transport.fileSelected = function () {\n\t\n\t var input = this,\n\t i,\n\t files = input.files,\n\t formData = new FormData();\n\t\n\t if (editor.transport.arguments.multiple === true) {\n\t\n\t for (i = 0; i < files.length; i++) {\n\t\n\t formData.append('files[]', files[i], files[i].name);\n\t }\n\t } else {\n\t\n\t formData.append('files', files[0], files[0].name);\n\t }\n\t\n\t currentRequest = editor.core.ajax({\n\t type: 'POST',\n\t data: formData,\n\t url: editor.transport.arguments.url,\n\t beforeSend: editor.transport.arguments.beforeSend,\n\t success: editor.transport.arguments.success,\n\t error: editor.transport.arguments.error,\n\t progress: editor.transport.arguments.progress\n\t });\n\t\n\t /** Clear input */\n\t transport.clearInput();\n\t };\n\t\n\t /**\r\n\t * Use plugin callbacks\r\n\t * @protected\r\n\t *\r\n\t * @param {Object} args - can have :\r\n\t * @param {String} args.url - fetch URL\r\n\t * @param {Function} args.beforeSend - function calls before sending ajax\r\n\t * @param {Function} args.success - success callback\r\n\t * @param {Function} args.error - on error handler\r\n\t * @param {Function} args.progress - xhr onprogress handler\r\n\t * @param {Boolean} args.multiple - allow select several files\r\n\t * @param {String} args.accept - adds accept attribute\r\n\t */\n\t transport.selectAndUpload = function (args) {\n\t\n\t transport.arguments = args;\n\t\n\t if (args.multiple === true) {\n\t\n\t transport.input.setAttribute('multiple', 'multiple');\n\t }\n\t\n\t if (args.accept) {\n\t\n\t transport.input.setAttribute('accept', args.accept);\n\t }\n\t\n\t transport.input.click();\n\t };\n\t\n\t transport.abort = function () {\n\t\n\t currentRequest.abort();\n\t\n\t currentRequest = null;\n\t };\n\t\n\t return transport;\n\t}({});\n\n/***/ },\n/* 5 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor Renderer Module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0\r\n\t */\n\t\n\tmodule.exports = function (renderer) {\n\t\n\t var editor = codex.editor;\n\t\n\t /**\r\n\t * Asyncronously parses input JSON to redactor blocks\r\n\t */\n\t renderer.makeBlocksFromData = function () {\n\t\n\t /**\r\n\t * If redactor is empty, add first paragraph to start writing\r\n\t */\n\t if (editor.core.isEmpty(editor.state.blocks) || !editor.state.blocks.items.length) {\n\t\n\t editor.ui.addInitialBlock();\n\t return;\n\t }\n\t\n\t Promise.resolve()\n\t\n\t /** First, get JSON from state */\n\t .then(function () {\n\t\n\t return editor.state.blocks;\n\t })\n\t\n\t /** Then, start to iterate they */\n\t .then(editor.renderer.appendBlocks)\n\t\n\t /** Write log if something goes wrong */\n\t .catch(function (error) {\n\t\n\t editor.core.log('Error while parsing JSON: %o', 'error', error);\n\t });\n\t };\n\t\n\t /**\r\n\t * Parses JSON to blocks\r\n\t * @param {object} data\r\n\t * @return Primise -> nodeList\r\n\t */\n\t renderer.appendBlocks = function (data) {\n\t\n\t var blocks = data.items;\n\t\n\t /**\r\n\t * Sequence of one-by-one blocks appending\r\n\t * Uses to save blocks order after async-handler\r\n\t */\n\t var nodeSequence = Promise.resolve();\n\t\n\t for (var index = 0; index < blocks.length; index++) {\n\t\n\t /** Add node to sequence at specified index */\n\t editor.renderer.appendNodeAtIndex(nodeSequence, blocks, index);\n\t }\n\t };\n\t\n\t /**\r\n\t * Append node at specified index\r\n\t */\n\t renderer.appendNodeAtIndex = function (nodeSequence, blocks, index) {\n\t\n\t /** We need to append node to sequence */\n\t nodeSequence\n\t\n\t /** first, get node async-aware */\n\t .then(function () {\n\t\n\t return editor.renderer.getNodeAsync(blocks, index);\n\t })\n\t\n\t /**\r\n\t * second, compose editor-block from JSON object\r\n\t */\n\t .then(editor.renderer.createBlockFromData)\n\t\n\t /**\r\n\t * now insert block to redactor\r\n\t */\n\t .then(function (blockData) {\n\t\n\t /**\r\n\t * blockData has 'block', 'type' and 'stretched' information\r\n\t */\n\t editor.content.insertBlock(blockData);\n\t\n\t /** Pass created block to next step */\n\t return blockData.block;\n\t })\n\t\n\t /** Log if something wrong with node */\n\t .catch(function (error) {\n\t\n\t editor.core.log('Node skipped while parsing because %o', 'error', error);\n\t });\n\t };\n\t\n\t /**\r\n\t * Asynchronously returns block data from blocksList by index\r\n\t * @return Promise to node\r\n\t */\n\t renderer.getNodeAsync = function (blocksList, index) {\n\t\n\t return Promise.resolve().then(function () {\n\t\n\t return {\n\t tool: blocksList[index],\n\t position: index\n\t };\n\t });\n\t };\n\t\n\t /**\r\n\t * Creates editor block by JSON-data\r\n\t *\r\n\t * @uses render method of each plugin\r\n\t *\r\n\t * @param {Object} toolData.tool\r\n\t * { header : {\r\n\t * text: '',\r\n\t * type: 'H3', ...\r\n\t * }\r\n\t * }\r\n\t * @param {Number} toolData.position - index in input-blocks array\r\n\t * @return {Object} with type and Element\r\n\t */\n\t renderer.createBlockFromData = function (toolData) {\n\t\n\t /** New parser */\n\t var block,\n\t tool = toolData.tool,\n\t pluginName = tool.type;\n\t\n\t /** Get first key of object that stores plugin name */\n\t // for (var pluginName in blockData) break;\n\t\n\t /** Check for plugin existance */\n\t if (!editor.tools[pluginName]) {\n\t\n\t throw Error('Plugin \\xAB' + pluginName + '\\xBB not found');\n\t }\n\t\n\t /** Check for plugin having render method */\n\t if (typeof editor.tools[pluginName].render != 'function') {\n\t\n\t throw Error('Plugin \\xAB' + pluginName + '\\xBB must have \\xABrender\\xBB method');\n\t }\n\t\n\t if (editor.tools[pluginName].available === false) {\n\t\n\t block = editor.draw.unavailableBlock();\n\t\n\t block.innerHTML = editor.tools[pluginName].loadingMessage;\n\t\n\t /**\r\n\t * Saver will extract data from initial block data by position in array\r\n\t */\n\t block.dataset.inputPosition = toolData.position;\n\t } else {\n\t\n\t /** New Parser */\n\t block = editor.tools[pluginName].render(tool.data);\n\t }\n\t\n\t /** is first-level block stretched */\n\t var stretched = editor.tools[pluginName].isStretched || false;\n\t\n\t /** Retrun type and block */\n\t return {\n\t type: pluginName,\n\t block: block,\n\t stretched: stretched\n\t };\n\t };\n\t\n\t return renderer;\n\t}({});\n\n/***/ },\n/* 6 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor Saver\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0.2\r\n\t */\n\t\n\tmodule.exports = function (saver) {\n\t\n\t var editor = codex.editor;\n\t\n\t /**\r\n\t * Saves blocks\r\n\t * @private\r\n\t */\n\t saver.saveBlocks = function () {\n\t\n\t /** Save html content of redactor to memory */\n\t editor.state.html = editor.nodes.redactor.innerHTML;\n\t\n\t /** Empty jsonOutput state */\n\t editor.state.jsonOutput = [];\n\t\n\t Promise.resolve().then(function () {\n\t\n\t return editor.nodes.redactor.childNodes;\n\t })\n\t /** Making a sequence from separate blocks */\n\t .then(editor.saver.makeQueue).then(function () {\n\t // editor.nodes.textarea.innerHTML = editor.state.html;\n\t }).catch(function (error) {\n\t\n\t editor.core.log(error);\n\t });\n\t };\n\t\n\t saver.makeQueue = function (blocks) {\n\t\n\t var queue = Promise.resolve();\n\t\n\t for (var index = 0; index < blocks.length; index++) {\n\t\n\t /** Add node to sequence at specified index */\n\t editor.saver.getBlockData(queue, blocks, index);\n\t }\n\t };\n\t\n\t /** Gets every block and makes From Data */\n\t saver.getBlockData = function (queue, blocks, index) {\n\t\n\t queue.then(function () {\n\t\n\t return editor.saver.getNodeAsync(blocks, index);\n\t }).then(editor.saver.makeFormDataFromBlocks);\n\t };\n\t\n\t /**\r\n\t * Asynchronously returns block data from blocksList by index\r\n\t * @return Promise to node\r\n\t */\n\t saver.getNodeAsync = function (blocksList, index) {\n\t\n\t return Promise.resolve().then(function () {\n\t\n\t return blocksList[index];\n\t });\n\t };\n\t\n\t saver.makeFormDataFromBlocks = function (block) {\n\t\n\t var pluginName = block.dataset.tool;\n\t\n\t /** Check for plugin existance */\n\t if (!editor.tools[pluginName]) {\n\t\n\t throw Error('Plugin \\xAB' + pluginName + '\\xBB not found');\n\t }\n\t\n\t /** Check for plugin having render method */\n\t if (typeof editor.tools[pluginName].save != 'function') {\n\t\n\t throw Error('Plugin \\xAB' + pluginName + '\\xBB must have save method');\n\t }\n\t\n\t /** Result saver */\n\t var blockContent = block.childNodes[0],\n\t pluginsContent = blockContent.childNodes[0],\n\t savedData,\n\t position,\n\t output;\n\t\n\t /** If plugin wasn't available then return data from cache */\n\t if (editor.tools[pluginName].available === false) {\n\t\n\t position = pluginsContent.dataset.inputPosition;\n\t savedData = codex.editor.state.blocks.items[position].data;\n\t } else {\n\t\n\t savedData = editor.tools[pluginName].save(pluginsContent);\n\t\n\t if (editor.tools[pluginName].validate) {\n\t\n\t var result = editor.tools[pluginName].validate(savedData);\n\t\n\t /**\r\n\t * Do not allow invalid data\r\n\t */\n\t if (!result) return;\n\t }\n\t }\n\t\n\t output = {\n\t type: pluginName,\n\t data: savedData\n\t };\n\t\n\t editor.state.jsonOutput.push(output);\n\t };\n\t\n\t return saver;\n\t}({});\n\n/***/ },\n/* 7 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor Content Module\r\n\t * Works with DOM\r\n\t *\r\n\t * @module Codex Editor content module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.3.13\r\n\t *\r\n\t * @description Module works with Elements that have been appended to the main DOM\r\n\t */\n\t\n\tmodule.exports = function (content) {\n\t\n\t var editor = codex.editor;\n\t\n\t /**\r\n\t * Links to current active block\r\n\t * @type {null | Element}\r\n\t */\n\t content.currentNode = null;\n\t\n\t /**\r\n\t * clicked in redactor area\r\n\t * @type {null | Boolean}\r\n\t */\n\t content.editorAreaHightlighted = null;\n\t\n\t /**\r\n\t * @deprecated\r\n\t * Synchronizes redactor with original textarea\r\n\t */\n\t content.sync = function () {\n\t\n\t editor.core.log('syncing...');\n\t\n\t /**\r\n\t * Save redactor content to editor.state\r\n\t */\n\t editor.state.html = editor.nodes.redactor.innerHTML;\n\t };\n\t\n\t /**\r\n\t * Appends background to the block\r\n\t *\r\n\t * @description add CSS class to highlight visually first-level block area\r\n\t */\n\t content.markBlock = function () {\n\t\n\t editor.content.currentNode.classList.add(editor.ui.className.BLOCK_HIGHLIGHTED);\n\t };\n\t\n\t /**\r\n\t * Clear background\r\n\t *\r\n\t * @description clears styles that highlights block\r\n\t */\n\t content.clearMark = function () {\n\t\n\t if (editor.content.currentNode) {\n\t\n\t editor.content.currentNode.classList.remove(editor.ui.className.BLOCK_HIGHLIGHTED);\n\t }\n\t };\n\t\n\t /**\r\n\t * Finds first-level block\r\n\t *\r\n\t * @param {Element} node - selected or clicked in redactors area node\r\n\t * @protected\r\n\t *\r\n\t * @description looks for first-level block.\r\n\t * gets parent while node is not first-level\r\n\t */\n\t content.getFirstLevelBlock = function (node) {\n\t\n\t if (!editor.core.isDomNode(node)) {\n\t\n\t node = node.parentNode;\n\t }\n\t\n\t if (node === editor.nodes.redactor || node === document.body) {\n\t\n\t return null;\n\t } else {\n\t\n\t while (!node.classList.contains(editor.ui.className.BLOCK_CLASSNAME)) {\n\t\n\t node = node.parentNode;\n\t }\n\t\n\t return node;\n\t }\n\t };\n\t\n\t /**\r\n\t * Trigger this event when working node changed\r\n\t * @param {Element} targetNode - first-level of this node will be current\r\n\t * @protected\r\n\t *\r\n\t * @description If targetNode is first-level then we set it as current else we look for parents to find first-level\r\n\t */\n\t content.workingNodeChanged = function (targetNode) {\n\t\n\t /** Clear background from previous marked block before we change */\n\t editor.content.clearMark();\n\t\n\t if (!targetNode) {\n\t\n\t return;\n\t }\n\t\n\t content.currentNode = content.getFirstLevelBlock(targetNode);\n\t };\n\t\n\t /**\r\n\t * Replaces one redactor block with another\r\n\t * @protected\r\n\t * @param {Element} targetBlock - block to replace. Mostly currentNode.\r\n\t * @param {Element} newBlock\r\n\t * @param {string} newBlockType - type of new block; we need to store it to data-attribute\r\n\t *\r\n\t * [!] Function does not saves old block content.\r\n\t * You can get it manually and pass with newBlock.innerHTML\r\n\t */\n\t content.replaceBlock = function (targetBlock, newBlock) {\n\t\n\t if (!targetBlock || !newBlock) {\n\t\n\t editor.core.log('replaceBlock: missed params');\n\t return;\n\t }\n\t\n\t /** If target-block is not a frist-level block, then we iterate parents to find it */\n\t while (!targetBlock.classList.contains(editor.ui.className.BLOCK_CLASSNAME)) {\n\t\n\t targetBlock = targetBlock.parentNode;\n\t }\n\t\n\t /** Replacing */\n\t editor.nodes.redactor.replaceChild(newBlock, targetBlock);\n\t\n\t /**\r\n\t * Set new node as current\r\n\t */\n\t editor.content.workingNodeChanged(newBlock);\n\t\n\t /**\r\n\t * Add block handlers\r\n\t */\n\t editor.ui.addBlockHandlers(newBlock);\n\t\n\t /**\r\n\t * Save changes\r\n\t */\n\t editor.ui.saveInputs();\n\t };\n\t\n\t /**\r\n\t * @protected\r\n\t *\r\n\t * Inserts new block to redactor\r\n\t * Wrapps block into a DIV with BLOCK_CLASSNAME class\r\n\t *\r\n\t * @param blockData {object}\r\n\t * @param blockData.block {Element} element with block content\r\n\t * @param blockData.type {string} block plugin\r\n\t * @param needPlaceCaret {bool} pass true to set caret in new block\r\n\t *\r\n\t */\n\t content.insertBlock = function (blockData, needPlaceCaret) {\n\t\n\t var workingBlock = editor.content.currentNode,\n\t newBlockContent = blockData.block,\n\t blockType = blockData.type,\n\t isStretched = blockData.stretched;\n\t\n\t var newBlock = composeNewBlock_(newBlockContent, blockType, isStretched);\n\t\n\t if (workingBlock) {\n\t\n\t editor.core.insertAfter(workingBlock, newBlock);\n\t } else {\n\t\n\t /**\r\n\t * If redactor is empty, append as first child\r\n\t */\n\t editor.nodes.redactor.appendChild(newBlock);\n\t }\n\t\n\t /**\r\n\t * Block handler\r\n\t */\n\t editor.ui.addBlockHandlers(newBlock);\n\t\n\t /**\r\n\t * Set new node as current\r\n\t */\n\t editor.content.workingNodeChanged(newBlock);\n\t\n\t /**\r\n\t * Save changes\r\n\t */\n\t editor.ui.saveInputs();\n\t\n\t if (needPlaceCaret) {\n\t\n\t /**\r\n\t * If we don't know input index then we set default value -1\r\n\t */\n\t var currentInputIndex = editor.caret.getCurrentInputIndex() || -1;\n\t\n\t if (currentInputIndex == -1) {\n\t\n\t var editableElement = newBlock.querySelector('[contenteditable]'),\n\t emptyText = document.createTextNode('');\n\t\n\t editableElement.appendChild(emptyText);\n\t editor.caret.set(editableElement, 0, 0);\n\t\n\t editor.toolbar.move();\n\t editor.toolbar.showPlusButton();\n\t } else {\n\t\n\t if (currentInputIndex === editor.state.inputs.length - 1) return;\n\t\n\t /** Timeout for browsers execution */\n\t window.setTimeout(function () {\n\t\n\t /** Setting to the new input */\n\t editor.caret.setToNextBlock(currentInputIndex);\n\t editor.toolbar.move();\n\t editor.toolbar.open();\n\t }, 10);\n\t }\n\t }\n\t\n\t /**\r\n\t * Block is inserted, wait for new click that defined focusing on editors area\r\n\t * @type {boolean}\r\n\t */\n\t content.editorAreaHightlighted = false;\n\t };\n\t\n\t /**\r\n\t * Replaces blocks with saving content\r\n\t * @protected\r\n\t * @param {Element} noteToReplace\r\n\t * @param {Element} newNode\r\n\t * @param {Element} blockType\r\n\t */\n\t content.switchBlock = function (blockToReplace, newBlock, tool) {\n\t\n\t tool = tool || editor.content.currentNode.dataset.tool;\n\t var newBlockComposed = composeNewBlock_(newBlock, tool);\n\t\n\t /** Replacing */\n\t editor.content.replaceBlock(blockToReplace, newBlockComposed);\n\t\n\t /** Save new Inputs when block is changed */\n\t editor.ui.saveInputs();\n\t };\n\t\n\t /**\r\n\t * Iterates between child noted and looking for #text node on deepest level\r\n\t * @protected\r\n\t *\r\n\t * @param {Element} block - node where find\r\n\t * @param {int} postiton - starting postion\r\n\t * Example: childNodex.length to find from the end\r\n\t * or 0 to find from the start\r\n\t * @return {Text} block\r\n\t * @uses DFS\r\n\t */\n\t content.getDeepestTextNodeFromPosition = function (block, position) {\n\t\n\t /**\r\n\t * Clear Block from empty and useless spaces with trim.\r\n\t * Such nodes we should remove\r\n\t */\n\t var blockChilds = block.childNodes,\n\t index,\n\t node,\n\t text;\n\t\n\t for (index = 0; index < blockChilds.length; index++) {\n\t\n\t node = blockChilds[index];\n\t\n\t if (node.nodeType == editor.core.nodeTypes.TEXT) {\n\t\n\t text = node.textContent.trim();\n\t\n\t /** Text is empty. We should remove this child from node before we start DFS\r\n\t * decrease the quantity of childs.\r\n\t */\n\t if (text === '') {\n\t\n\t block.removeChild(node);\n\t position--;\n\t }\n\t }\n\t }\n\t\n\t if (block.childNodes.length === 0) {\n\t\n\t return document.createTextNode('');\n\t }\n\t\n\t /** Setting default position when we deleted all empty nodes */\n\t if (position < 0) position = 1;\n\t\n\t var lookingFromStart = false;\n\t\n\t /** For looking from START */\n\t if (position === 0) {\n\t\n\t lookingFromStart = true;\n\t position = 1;\n\t }\n\t\n\t while (position) {\n\t\n\t /** initial verticle of node. */\n\t if (lookingFromStart) {\n\t\n\t block = block.childNodes[0];\n\t } else {\n\t\n\t block = block.childNodes[position - 1];\n\t }\n\t\n\t if (block.nodeType == editor.core.nodeTypes.TAG) {\n\t\n\t position = block.childNodes.length;\n\t } else if (block.nodeType == editor.core.nodeTypes.TEXT) {\n\t\n\t position = 0;\n\t }\n\t }\n\t\n\t return block;\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t * @param {Element} block - current plugins render\r\n\t * @param {String} tool - plugins name\r\n\t * @param {Boolean} isStretched - make stretched block or not\r\n\t *\r\n\t * @description adds necessary information to wrap new created block by first-level holder\r\n\t */\n\t var composeNewBlock_ = function composeNewBlock_(block, tool, isStretched) {\n\t\n\t var newBlock = editor.draw.node('DIV', editor.ui.className.BLOCK_CLASSNAME, {}),\n\t blockContent = editor.draw.node('DIV', editor.ui.className.BLOCK_CONTENT, {});\n\t\n\t blockContent.appendChild(block);\n\t newBlock.appendChild(blockContent);\n\t\n\t if (isStretched) {\n\t\n\t blockContent.classList.add(editor.ui.className.BLOCK_STRETCHED);\n\t }\n\t\n\t newBlock.dataset.tool = tool;\n\t return newBlock;\n\t };\n\t\n\t /**\r\n\t * Returns Range object of current selection\r\n\t * @protected\r\n\t */\n\t content.getRange = function () {\n\t\n\t var selection = window.getSelection().getRangeAt(0);\n\t\n\t return selection;\n\t };\n\t\n\t /**\r\n\t * Divides block in two blocks (after and before caret)\r\n\t *\r\n\t * @protected\r\n\t * @param {int} inputIndex - target input index\r\n\t *\r\n\t * @description splits current input content to the separate blocks\r\n\t * When enter is pressed among the words, that text will be splited.\r\n\t */\n\t content.splitBlock = function (inputIndex) {\n\t\n\t var selection = window.getSelection(),\n\t anchorNode = selection.anchorNode,\n\t anchorNodeText = anchorNode.textContent,\n\t caretOffset = selection.anchorOffset,\n\t textBeforeCaret,\n\t textNodeBeforeCaret,\n\t textAfterCaret,\n\t textNodeAfterCaret;\n\t\n\t var currentBlock = editor.content.currentNode.querySelector('[contentEditable]');\n\t\n\t textBeforeCaret = anchorNodeText.substring(0, caretOffset);\n\t textAfterCaret = anchorNodeText.substring(caretOffset);\n\t\n\t textNodeBeforeCaret = document.createTextNode(textBeforeCaret);\n\t\n\t if (textAfterCaret) {\n\t\n\t textNodeAfterCaret = document.createTextNode(textAfterCaret);\n\t }\n\t\n\t var previousChilds = [],\n\t nextChilds = [],\n\t reachedCurrent = false;\n\t\n\t if (textNodeAfterCaret) {\n\t\n\t nextChilds.push(textNodeAfterCaret);\n\t }\n\t\n\t for (var i = 0, child; !!(child = currentBlock.childNodes[i]); i++) {\n\t\n\t if (child != anchorNode) {\n\t\n\t if (!reachedCurrent) {\n\t\n\t previousChilds.push(child);\n\t } else {\n\t\n\t nextChilds.push(child);\n\t }\n\t } else {\n\t\n\t reachedCurrent = true;\n\t }\n\t }\n\t\n\t /** Clear current input */\n\t editor.state.inputs[inputIndex].innerHTML = '';\n\t\n\t /**\r\n\t * Append all childs founded before anchorNode\r\n\t */\n\t var previousChildsLength = previousChilds.length;\n\t\n\t for (i = 0; i < previousChildsLength; i++) {\n\t\n\t editor.state.inputs[inputIndex].appendChild(previousChilds[i]);\n\t }\n\t\n\t editor.state.inputs[inputIndex].appendChild(textNodeBeforeCaret);\n\t\n\t /**\r\n\t * Append text node which is after caret\r\n\t */\n\t var nextChildsLength = nextChilds.length,\n\t newNode = document.createElement('div');\n\t\n\t for (i = 0; i < nextChildsLength; i++) {\n\t\n\t newNode.appendChild(nextChilds[i]);\n\t }\n\t\n\t newNode = newNode.innerHTML;\n\t\n\t /** This type of block creates when enter is pressed */\n\t var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\n\t\n\t /**\r\n\t * Make new paragraph with text after caret\r\n\t */\n\t editor.content.insertBlock({\n\t type: NEW_BLOCK_TYPE,\n\t block: editor.tools[NEW_BLOCK_TYPE].render({\n\t text: newNode\n\t })\n\t }, true);\n\t };\n\t\n\t /**\r\n\t * Merges two blocks — current and target\r\n\t * If target index is not exist, then previous will be as target\r\n\t *\r\n\t * @protected\r\n\t * @param {int} currentInputIndex\r\n\t * @param {int} targetInputIndex\r\n\t *\r\n\t * @description gets two inputs indexes and merges into one\r\n\t */\n\t content.mergeBlocks = function (currentInputIndex, targetInputIndex) {\n\t\n\t /** If current input index is zero, then prevent method execution */\n\t if (currentInputIndex === 0) {\n\t\n\t return;\n\t }\n\t\n\t var targetInput,\n\t currentInputContent = editor.state.inputs[currentInputIndex].innerHTML;\n\t\n\t if (!targetInputIndex) {\n\t\n\t targetInput = editor.state.inputs[currentInputIndex - 1];\n\t } else {\n\t\n\t targetInput = editor.state.inputs[targetInputIndex];\n\t }\n\t\n\t targetInput.innerHTML += currentInputContent;\n\t };\n\t\n\t /**\r\n\t * Iterates all right siblings and parents, which has right siblings\r\n\t * while it does not reached the first-level block\r\n\t *\r\n\t * @param {Element} node\r\n\t * @return {boolean}\r\n\t */\n\t content.isLastNode = function (node) {\n\t\n\t // console.log('погнали перебор родителей');\n\t\n\t var allChecked = false;\n\t\n\t while (!allChecked) {\n\t\n\t // console.log('Смотрим на %o', node);\n\t // console.log('Проверим, пустые ли соседи справа');\n\t\n\t if (!allSiblingsEmpty_(node)) {\n\t\n\t // console.log('Есть непустые соседи. Узел не последний. Выходим.');\n\t return false;\n\t }\n\t\n\t node = node.parentNode;\n\t\n\t /**\r\n\t * Проверяем родителей до тех пор, пока не найдем блок первого уровня\r\n\t */\n\t if (node.classList.contains(editor.ui.className.BLOCK_CONTENT)) {\n\t\n\t allChecked = true;\n\t }\n\t }\n\t\n\t return true;\n\t };\n\t\n\t /**\r\n\t * Checks if all element right siblings is empty\r\n\t * @param node\r\n\t */\n\t var allSiblingsEmpty_ = function allSiblingsEmpty_(node) {\n\t\n\t /**\r\n\t * Нужно убедиться, что после пустого соседа ничего нет\r\n\t */\n\t var sibling = node.nextSibling;\n\t\n\t while (sibling) {\n\t\n\t if (sibling.textContent.length) {\n\t\n\t return false;\n\t }\n\t\n\t sibling = sibling.nextSibling;\n\t }\n\t\n\t return true;\n\t };\n\t\n\t /**\r\n\t * @public\r\n\t *\r\n\t * @param {string} htmlData - html content as string\r\n\t * @param {string} plainData - plain text\r\n\t * @return {string} - html content as string\r\n\t */\n\t content.wrapTextWithParagraphs = function (htmlData, plainData) {\n\t\n\t if (!htmlData.trim()) {\n\t\n\t return wrapPlainTextWithParagraphs(plainData);\n\t }\n\t\n\t var wrapper = document.createElement('DIV'),\n\t newWrapper = document.createElement('DIV'),\n\t i,\n\t paragraph,\n\t firstLevelBlocks = ['DIV', 'P'],\n\t blockTyped,\n\t node;\n\t\n\t /**\r\n\t * Make HTML Element to Wrap Text\r\n\t * It allows us to work with input data as HTML content\r\n\t */\n\t wrapper.innerHTML = htmlData;\n\t paragraph = document.createElement('P');\n\t\n\t for (i = 0; i < wrapper.childNodes.length; i++) {\n\t\n\t node = wrapper.childNodes[i];\n\t\n\t blockTyped = firstLevelBlocks.indexOf(node.tagName) != -1;\n\t\n\t /**\r\n\t * If node is first-levet\r\n\t * we add this node to our new wrapper\r\n\t */\n\t if (blockTyped) {\n\t\n\t /**\r\n\t * If we had splitted inline nodes to paragraph before\r\n\t */\n\t if (paragraph.childNodes.length) {\n\t\n\t newWrapper.appendChild(paragraph.cloneNode(true));\n\t\n\t /** empty paragraph */\n\t paragraph = null;\n\t paragraph = document.createElement('P');\n\t }\n\t\n\t newWrapper.appendChild(node.cloneNode(true));\n\t } else {\n\t\n\t /** Collect all inline nodes to one as paragraph */\n\t paragraph.appendChild(node.cloneNode(true));\n\t\n\t /** if node is last we should append this node to paragraph and paragraph to new wrapper */\n\t if (i == wrapper.childNodes.length - 1) {\n\t\n\t newWrapper.appendChild(paragraph.cloneNode(true));\n\t }\n\t }\n\t }\n\t\n\t return newWrapper.innerHTML;\n\t };\n\t\n\t /**\r\n\t * Splits strings on new line and wraps paragraphs with

    tag\r\n\t * @param plainText\r\n\t * @returns {string}\r\n\t */\n\t var wrapPlainTextWithParagraphs = function wrapPlainTextWithParagraphs(plainText) {\n\t\n\t return '

    ' + plainText.split('\\n\\n').join('

    ') + '

    ';\n\t };\n\t\n\t /**\r\n\t * Finds closest Contenteditable parent from Element\r\n\t * @param {Element} node element looking from\r\n\t * @return {Element} node contenteditable\r\n\t */\n\t content.getEditableParent = function (node) {\n\t\n\t while (node && node.contentEditable != 'true') {\n\t\n\t node = node.parentNode;\n\t }\n\t\n\t return node;\n\t };\n\t\n\t return content;\n\t}({});\n\n/***/ },\n/* 8 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor toolbar module\r\n\t *\r\n\t * Contains:\r\n\t * - Inline toolbox\r\n\t * - Toolbox within plus button\r\n\t * - Settings section\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0\r\n\t */\n\t\n\tmodule.exports = function (toolbar) {\n\t\n\t var editor = codex.editor;\n\t\n\t toolbar.settings = __webpack_require__(9);\n\t toolbar.inline = __webpack_require__(10);\n\t toolbar.toolbox = __webpack_require__(11);\n\t\n\t /**\r\n\t * Margin between focused node and toolbar\r\n\t */\n\t toolbar.defaultToolbarHeight = 49;\n\t\n\t toolbar.defaultOffset = 34;\n\t\n\t toolbar.opened = false;\n\t\n\t toolbar.current = null;\n\t\n\t /**\r\n\t * @protected\r\n\t */\n\t toolbar.open = function () {\n\t\n\t if (editor.hideToolbar) {\n\t\n\t return;\n\t }\n\t\n\t editor.nodes.toolbar.classList.add('opened');\n\t this.opened = true;\n\t };\n\t\n\t /**\r\n\t * @protected\r\n\t */\n\t toolbar.close = function () {\n\t\n\t editor.nodes.toolbar.classList.remove('opened');\n\t\n\t toolbar.opened = false;\n\t toolbar.current = null;\n\t\n\t for (var button in editor.nodes.toolbarButtons) {\n\t\n\t editor.nodes.toolbarButtons[button].classList.remove('selected');\n\t }\n\t\n\t /** Close toolbox when toolbar is not displayed */\n\t editor.toolbar.toolbox.close();\n\t editor.toolbar.settings.close();\n\t };\n\t\n\t toolbar.toggle = function () {\n\t\n\t if (!this.opened) {\n\t\n\t this.open();\n\t } else {\n\t\n\t this.close();\n\t }\n\t };\n\t\n\t toolbar.hidePlusButton = function () {\n\t\n\t editor.nodes.plusButton.classList.add('hide');\n\t };\n\t\n\t toolbar.showPlusButton = function () {\n\t\n\t editor.nodes.plusButton.classList.remove('hide');\n\t };\n\t\n\t /**\r\n\t * Moving toolbar to the specified node\r\n\t */\n\t toolbar.move = function () {\n\t\n\t /** Close Toolbox when we move toolbar */\n\t editor.toolbar.toolbox.close();\n\t\n\t if (!editor.content.currentNode) {\n\t\n\t return;\n\t }\n\t\n\t var newYCoordinate = editor.content.currentNode.offsetTop - editor.toolbar.defaultToolbarHeight / 2 + editor.toolbar.defaultOffset;\n\t\n\t editor.nodes.toolbar.style.transform = 'translate3D(0, ' + Math.floor(newYCoordinate) + 'px, 0)';\n\t\n\t /** Close trash actions */\n\t editor.toolbar.settings.hideRemoveActions();\n\t };\n\t\n\t return toolbar;\n\t}({});\n\n/***/ },\n/* 9 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Toolbar settings\r\n\t *\r\n\t * @version 1.0.4\r\n\t */\n\t\n\tmodule.exports = function (settings) {\n\t\n\t var editor = codex.editor;\n\t\n\t settings.opened = false;\n\t\n\t settings.setting = null;\n\t settings.actions = null;\n\t\n\t /**\r\n\t * Append and open settings\r\n\t */\n\t settings.open = function (toolType) {\n\t\n\t /**\r\n\t * Append settings content\r\n\t * It's stored in tool.settings\r\n\t */\n\t if (!editor.tools[toolType] || !editor.tools[toolType].makeSettings) {\n\t\n\t editor.core.log('Plugin \\xAB' + toolType + '\\xBB has no settings', 'warn');\n\t editor.nodes.pluginSettings.innerHTML = '\\u041F\\u043B\\u0430\\u0433\\u0438\\u043D \\xAB' + toolType + '\\xBB \\u043D\\u0435 \\u0438\\u043C\\u0435\\u0435\\u0442 \\u043D\\u0430\\u0441\\u0442\\u0440\\u043E\\u0435\\u043A';\n\t } else {\n\t\n\t /**\r\n\t * Draw settings block\r\n\t */\n\t var settingsBlock = editor.tools[toolType].makeSettings();\n\t\n\t editor.nodes.pluginSettings.appendChild(settingsBlock);\n\t }\n\t\n\t /** Open settings block */\n\t editor.nodes.blockSettings.classList.add('opened');\n\t this.opened = true;\n\t };\n\t\n\t /**\r\n\t * Close and clear settings\r\n\t */\n\t settings.close = function () {\n\t\n\t editor.nodes.blockSettings.classList.remove('opened');\n\t editor.nodes.pluginSettings.innerHTML = '';\n\t\n\t this.opened = false;\n\t };\n\t\n\t /**\r\n\t * @param {string} toolType - plugin type\r\n\t */\n\t settings.toggle = function (toolType) {\n\t\n\t if (!this.opened) {\n\t\n\t this.open(toolType);\n\t } else {\n\t\n\t this.close();\n\t }\n\t };\n\t\n\t /**\r\n\t * Here we will draw buttons and add listeners to components\r\n\t */\n\t settings.makeRemoveBlockButton = function () {\n\t\n\t var removeBlockWrapper = editor.draw.node('SPAN', 'ce-toolbar__remove-btn', {}),\n\t settingButton = editor.draw.node('SPAN', 'ce-toolbar__remove-setting', { innerHTML: '' }),\n\t actionWrapper = editor.draw.node('DIV', 'ce-toolbar__remove-confirmation', {}),\n\t confirmAction = editor.draw.node('DIV', 'ce-toolbar__remove-confirm', { textContent: 'Удалить блок' }),\n\t cancelAction = editor.draw.node('DIV', 'ce-toolbar__remove-cancel', { textContent: 'Отмена' });\n\t\n\t editor.listeners.add(settingButton, 'click', editor.toolbar.settings.removeButtonClicked, false);\n\t\n\t editor.listeners.add(confirmAction, 'click', editor.toolbar.settings.confirmRemovingRequest, false);\n\t\n\t editor.listeners.add(cancelAction, 'click', editor.toolbar.settings.cancelRemovingRequest, false);\n\t\n\t actionWrapper.appendChild(confirmAction);\n\t actionWrapper.appendChild(cancelAction);\n\t\n\t removeBlockWrapper.appendChild(settingButton);\n\t removeBlockWrapper.appendChild(actionWrapper);\n\t\n\t /** Save setting */\n\t editor.toolbar.settings.setting = settingButton;\n\t editor.toolbar.settings.actions = actionWrapper;\n\t\n\t return removeBlockWrapper;\n\t };\n\t\n\t settings.removeButtonClicked = function () {\n\t\n\t var action = editor.toolbar.settings.actions;\n\t\n\t if (action.classList.contains('opened')) {\n\t\n\t editor.toolbar.settings.hideRemoveActions();\n\t } else {\n\t\n\t editor.toolbar.settings.showRemoveActions();\n\t }\n\t\n\t editor.toolbar.toolbox.close();\n\t editor.toolbar.settings.close();\n\t };\n\t\n\t settings.cancelRemovingRequest = function () {\n\t\n\t editor.toolbar.settings.actions.classList.remove('opened');\n\t };\n\t\n\t settings.confirmRemovingRequest = function () {\n\t\n\t var currentBlock = editor.content.currentNode,\n\t firstLevelBlocksCount;\n\t\n\t currentBlock.remove();\n\t\n\t firstLevelBlocksCount = editor.nodes.redactor.childNodes.length;\n\t\n\t /**\r\n\t * If all blocks are removed\r\n\t */\n\t if (firstLevelBlocksCount === 0) {\n\t\n\t /** update currentNode variable */\n\t editor.content.currentNode = null;\n\t\n\t /** Inserting new empty initial block */\n\t editor.ui.addInitialBlock();\n\t }\n\t\n\t editor.ui.saveInputs();\n\t\n\t editor.toolbar.close();\n\t };\n\t\n\t settings.showRemoveActions = function () {\n\t\n\t editor.toolbar.settings.actions.classList.add('opened');\n\t };\n\t\n\t settings.hideRemoveActions = function () {\n\t\n\t editor.toolbar.settings.actions.classList.remove('opened');\n\t };\n\t\n\t return settings;\n\t}({});\n\n/***/ },\n/* 10 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Inline toolbar\r\n\t *\r\n\t * Contains from tools:\r\n\t * Bold, Italic, Underline and Anchor\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0\r\n\t */\n\t\n\tmodule.exports = function (inline) {\n\t\n\t var editor = codex.editor;\n\t\n\t inline.buttonsOpened = null;\n\t inline.actionsOpened = null;\n\t inline.wrappersOffset = null;\n\t\n\t /**\r\n\t * saving selection that need for execCommand for styling\r\n\t *\r\n\t */\n\t inline.storedSelection = null;\n\t\n\t /**\r\n\t * @protected\r\n\t *\r\n\t * Open inline toobar\r\n\t */\n\t inline.show = function () {\n\t\n\t var currentNode = editor.content.currentNode,\n\t tool = currentNode.dataset.tool,\n\t plugin;\n\t\n\t /**\r\n\t * tool allowed to open inline toolbar\r\n\t */\n\t plugin = editor.tools[tool];\n\t\n\t if (!plugin.showInlineToolbar) return;\n\t\n\t var selectedText = inline.getSelectionText(),\n\t toolbar = editor.nodes.inlineToolbar.wrapper;\n\t\n\t if (selectedText.length > 0) {\n\t\n\t /** Move toolbar and open */\n\t editor.toolbar.inline.move();\n\t\n\t /** Open inline toolbar */\n\t toolbar.classList.add('opened');\n\t\n\t /** show buttons of inline toolbar */\n\t editor.toolbar.inline.showButtons();\n\t }\n\t };\n\t\n\t /**\r\n\t * @protected\r\n\t *\r\n\t * Closes inline toolbar\r\n\t */\n\t inline.close = function () {\n\t\n\t var toolbar = editor.nodes.inlineToolbar.wrapper;\n\t\n\t toolbar.classList.remove('opened');\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Moving toolbar\r\n\t */\n\t inline.move = function () {\n\t\n\t if (!this.wrappersOffset) {\n\t\n\t this.wrappersOffset = this.getWrappersOffset();\n\t }\n\t\n\t var coords = this.getSelectionCoords(),\n\t defaultOffset = 0,\n\t toolbar = editor.nodes.inlineToolbar.wrapper,\n\t newCoordinateX,\n\t newCoordinateY;\n\t\n\t if (toolbar.offsetHeight === 0) {\n\t\n\t defaultOffset = 40;\n\t }\n\t\n\t newCoordinateX = coords.x - this.wrappersOffset.left;\n\t newCoordinateY = coords.y + window.scrollY - this.wrappersOffset.top - defaultOffset - toolbar.offsetHeight;\n\t\n\t toolbar.style.transform = 'translate3D(' + Math.floor(newCoordinateX) + 'px, ' + Math.floor(newCoordinateY) + 'px, 0)';\n\t\n\t /** Close everything */\n\t editor.toolbar.inline.closeButtons();\n\t editor.toolbar.inline.closeAction();\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Tool Clicked\r\n\t */\n\t\n\t inline.toolClicked = function (event, type) {\n\t\n\t /**\r\n\t * For simple tools we use default browser function\r\n\t * For more complicated tools, we should write our own behavior\r\n\t */\n\t switch (type) {\n\t case 'createLink':\n\t editor.toolbar.inline.createLinkAction(event, type);break;\n\t default:\n\t editor.toolbar.inline.defaultToolAction(type);break;\n\t }\n\t\n\t /**\r\n\t * highlight buttons\r\n\t * after making some action\r\n\t */\n\t editor.nodes.inlineToolbar.buttons.childNodes.forEach(editor.toolbar.inline.hightlight);\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Saving wrappers offset in DOM\r\n\t */\n\t inline.getWrappersOffset = function () {\n\t\n\t var wrapper = editor.nodes.wrapper,\n\t offset = this.getOffset(wrapper);\n\t\n\t this.wrappersOffset = offset;\n\t return offset;\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Calculates offset of DOM element\r\n\t *\r\n\t * @param el\r\n\t * @returns {{top: number, left: number}}\r\n\t */\n\t inline.getOffset = function (el) {\n\t\n\t var _x = 0;\n\t var _y = 0;\n\t\n\t while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {\n\t\n\t _x += el.offsetLeft + el.clientLeft;\n\t _y += el.offsetTop + el.clientTop;\n\t el = el.offsetParent;\n\t }\n\t return { top: _y, left: _x };\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Calculates position of selected text\r\n\t * @returns {{x: number, y: number}}\r\n\t */\n\t inline.getSelectionCoords = function () {\n\t\n\t var sel = document.selection,\n\t range;\n\t var x = 0,\n\t y = 0;\n\t\n\t if (sel) {\n\t\n\t if (sel.type != 'Control') {\n\t\n\t range = sel.createRange();\n\t range.collapse(true);\n\t x = range.boundingLeft;\n\t y = range.boundingTop;\n\t }\n\t } else if (window.getSelection) {\n\t\n\t sel = window.getSelection();\n\t\n\t if (sel.rangeCount) {\n\t\n\t range = sel.getRangeAt(0).cloneRange();\n\t if (range.getClientRects) {\n\t\n\t range.collapse(true);\n\t var rect = range.getClientRects()[0];\n\t\n\t if (!rect) {\n\t\n\t return;\n\t }\n\t\n\t x = rect.left;\n\t y = rect.top;\n\t }\n\t }\n\t }\n\t return { x: x, y: y };\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Returns selected text as String\r\n\t * @returns {string}\r\n\t */\n\t inline.getSelectionText = function () {\n\t\n\t var selectedText = '';\n\t\n\t // all modern browsers and IE9+\n\t if (window.getSelection) {\n\t\n\t selectedText = window.getSelection().toString();\n\t }\n\t\n\t return selectedText;\n\t };\n\t\n\t /** Opens buttons block */\n\t inline.showButtons = function () {\n\t\n\t var buttons = editor.nodes.inlineToolbar.buttons;\n\t\n\t buttons.classList.add('opened');\n\t\n\t editor.toolbar.inline.buttonsOpened = true;\n\t\n\t /** highlight buttons */\n\t editor.nodes.inlineToolbar.buttons.childNodes.forEach(editor.toolbar.inline.hightlight);\n\t };\n\t\n\t /** Makes buttons disappear */\n\t inline.closeButtons = function () {\n\t\n\t var buttons = editor.nodes.inlineToolbar.buttons;\n\t\n\t buttons.classList.remove('opened');\n\t\n\t editor.toolbar.inline.buttonsOpened = false;\n\t };\n\t\n\t /** Open buttons defined action if exist */\n\t inline.showActions = function () {\n\t\n\t var action = editor.nodes.inlineToolbar.actions;\n\t\n\t action.classList.add('opened');\n\t\n\t editor.toolbar.inline.actionsOpened = true;\n\t };\n\t\n\t /** Close actions block */\n\t inline.closeAction = function () {\n\t\n\t var action = editor.nodes.inlineToolbar.actions;\n\t\n\t action.innerHTML = '';\n\t action.classList.remove('opened');\n\t editor.toolbar.inline.actionsOpened = false;\n\t };\n\t\n\t /**\r\n\t * Callback for keydowns in inline toolbar \"Insert link...\" input\r\n\t */\n\t var inlineToolbarAnchorInputKeydown_ = function inlineToolbarAnchorInputKeydown_(event) {\n\t\n\t if (event.keyCode != editor.core.keys.ENTER) {\n\t\n\t return;\n\t }\n\t\n\t var editable = editor.content.currentNode,\n\t storedSelection = editor.toolbar.inline.storedSelection;\n\t\n\t editor.toolbar.inline.restoreSelection(editable, storedSelection);\n\t editor.toolbar.inline.setAnchor(this.value);\n\t\n\t /**\r\n\t * Preventing events that will be able to happen\r\n\t */\n\t event.preventDefault();\n\t event.stopImmediatePropagation();\n\t\n\t editor.toolbar.inline.clearRange();\n\t };\n\t\n\t /** Action for link creation or for setting anchor */\n\t inline.createLinkAction = function (event) {\n\t\n\t var isActive = this.isLinkActive();\n\t\n\t var editable = editor.content.currentNode,\n\t storedSelection = editor.toolbar.inline.saveSelection(editable);\n\t\n\t /** Save globally selection */\n\t editor.toolbar.inline.storedSelection = storedSelection;\n\t\n\t if (isActive) {\n\t\n\t /**\r\n\t * Changing stored selection. if we want to remove anchor from word\r\n\t * we should remove anchor from whole word, not only selected part.\r\n\t * The solution is than we get the length of current link\r\n\t * Change start position to - end of selection minus length of anchor\r\n\t */\n\t editor.toolbar.inline.restoreSelection(editable, storedSelection);\n\t\n\t editor.toolbar.inline.defaultToolAction('unlink');\n\t } else {\n\t\n\t /** Create input and close buttons */\n\t var action = editor.draw.inputForLink();\n\t\n\t editor.nodes.inlineToolbar.actions.appendChild(action);\n\t\n\t editor.toolbar.inline.closeButtons();\n\t editor.toolbar.inline.showActions();\n\t\n\t /**\r\n\t * focus to input\r\n\t * Solution: https://developer.mozilla.org/ru/docs/Web/API/HTMLElement/focus\r\n\t * Prevents event after showing input and when we need to focus an input which is in unexisted form\r\n\t */\n\t action.focus();\n\t event.preventDefault();\n\t\n\t /** Callback to link action */\n\t editor.listeners.add(action, 'keydown', inlineToolbarAnchorInputKeydown_, false);\n\t }\n\t };\n\t\n\t inline.isLinkActive = function () {\n\t\n\t var isActive = false;\n\t\n\t editor.nodes.inlineToolbar.buttons.childNodes.forEach(function (tool) {\n\t\n\t var dataType = tool.dataset.type;\n\t\n\t if (dataType == 'link' && tool.classList.contains('hightlighted')) {\n\t\n\t isActive = true;\n\t }\n\t });\n\t\n\t return isActive;\n\t };\n\t\n\t /** default action behavior of tool */\n\t inline.defaultToolAction = function (type) {\n\t\n\t document.execCommand(type, false, null);\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Sets URL\r\n\t *\r\n\t * @param {String} url - URL\r\n\t */\n\t inline.setAnchor = function (url) {\n\t\n\t document.execCommand('createLink', false, url);\n\t\n\t /** Close after URL inserting */\n\t editor.toolbar.inline.closeAction();\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Saves selection\r\n\t */\n\t inline.saveSelection = function (containerEl) {\n\t\n\t var range = window.getSelection().getRangeAt(0),\n\t preSelectionRange = range.cloneRange(),\n\t start;\n\t\n\t preSelectionRange.selectNodeContents(containerEl);\n\t preSelectionRange.setEnd(range.startContainer, range.startOffset);\n\t\n\t start = preSelectionRange.toString().length;\n\t\n\t return {\n\t start: start,\n\t end: start + range.toString().length\n\t };\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Sets to previous selection (Range)\r\n\t *\r\n\t * @param {Element} containerEl - editable element where we restore range\r\n\t * @param {Object} savedSel - range basic information to restore\r\n\t */\n\t inline.restoreSelection = function (containerEl, savedSel) {\n\t\n\t var range = document.createRange(),\n\t charIndex = 0;\n\t\n\t range.setStart(containerEl, 0);\n\t range.collapse(true);\n\t\n\t var nodeStack = [containerEl],\n\t node,\n\t foundStart = false,\n\t stop = false,\n\t nextCharIndex;\n\t\n\t while (!stop && (node = nodeStack.pop())) {\n\t\n\t if (node.nodeType == 3) {\n\t\n\t nextCharIndex = charIndex + node.length;\n\t\n\t if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {\n\t\n\t range.setStart(node, savedSel.start - charIndex);\n\t foundStart = true;\n\t }\n\t if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {\n\t\n\t range.setEnd(node, savedSel.end - charIndex);\n\t stop = true;\n\t }\n\t charIndex = nextCharIndex;\n\t } else {\n\t\n\t var i = node.childNodes.length;\n\t\n\t while (i--) {\n\t\n\t nodeStack.push(node.childNodes[i]);\n\t }\n\t }\n\t }\n\t\n\t var sel = window.getSelection();\n\t\n\t sel.removeAllRanges();\n\t sel.addRange(range);\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Removes all ranges from window selection\r\n\t */\n\t inline.clearRange = function () {\n\t\n\t var selection = window.getSelection();\n\t\n\t selection.removeAllRanges();\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * sets or removes hightlight\r\n\t */\n\t inline.hightlight = function (tool) {\n\t\n\t var dataType = tool.dataset.type;\n\t\n\t if (document.queryCommandState(dataType)) {\n\t\n\t editor.toolbar.inline.setButtonHighlighted(tool);\n\t } else {\n\t\n\t editor.toolbar.inline.removeButtonsHighLight(tool);\n\t }\n\t\n\t /**\r\n\t *\r\n\t * hightlight for anchors\r\n\t */\n\t var selection = window.getSelection(),\n\t tag = selection.anchorNode.parentNode;\n\t\n\t if (tag.tagName == 'A' && dataType == 'link') {\n\t\n\t editor.toolbar.inline.setButtonHighlighted(tool);\n\t }\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Mark button if text is already executed\r\n\t */\n\t inline.setButtonHighlighted = function (button) {\n\t\n\t button.classList.add('hightlighted');\n\t\n\t /** At link tool we also change icon */\n\t if (button.dataset.type == 'link') {\n\t\n\t var icon = button.childNodes[0];\n\t\n\t icon.classList.remove('ce-icon-link');\n\t icon.classList.add('ce-icon-unlink');\n\t }\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t *\r\n\t * Removes hightlight\r\n\t */\n\t inline.removeButtonsHighLight = function (button) {\n\t\n\t button.classList.remove('hightlighted');\n\t\n\t /** At link tool we also change icon */\n\t if (button.dataset.type == 'link') {\n\t\n\t var icon = button.childNodes[0];\n\t\n\t icon.classList.remove('ce-icon-unlink');\n\t icon.classList.add('ce-icon-link');\n\t }\n\t };\n\t\n\t return inline;\n\t}({});\n\n/***/ },\n/* 11 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor toolbox\r\n\t *\r\n\t * All tools be able to appended here\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0\r\n\t */\n\t\n\tmodule.exports = function (toolbox) {\n\t\n\t var editor = codex.editor;\n\t\n\t toolbox.opened = false;\n\t\n\t /** Shows toolbox */\n\t toolbox.open = function () {\n\t\n\t /** Close setting if toolbox is opened */\n\t if (editor.toolbar.settings.opened) {\n\t\n\t editor.toolbar.settings.close();\n\t }\n\t\n\t /** display toolbox */\n\t editor.nodes.toolbox.classList.add('opened');\n\t\n\t /** Animate plus button */\n\t editor.nodes.plusButton.classList.add('clicked');\n\t\n\t /** toolbox state */\n\t editor.toolbar.toolbox.opened = true;\n\t };\n\t\n\t /** Closes toolbox */\n\t toolbox.close = function () {\n\t\n\t /** Makes toolbox disapear */\n\t editor.nodes.toolbox.classList.remove('opened');\n\t\n\t /** Rotate plus button */\n\t editor.nodes.plusButton.classList.remove('clicked');\n\t\n\t /** toolbox state */\n\t editor.toolbar.toolbox.opened = false;\n\t\n\t editor.toolbar.current = null;\n\t };\n\t\n\t toolbox.leaf = function () {\n\t\n\t var currentTool = editor.toolbar.current,\n\t tools = Object.keys(editor.tools),\n\t barButtons = editor.nodes.toolbarButtons,\n\t nextToolIndex = 0,\n\t toolToSelect = void 0,\n\t visibleTool = void 0,\n\t tool = void 0;\n\t\n\t if (!currentTool) {\n\t\n\t /** Get first tool from object*/\n\t for (tool in editor.tools) {\n\t\n\t if (editor.tools[tool].displayInToolbox) {\n\t\n\t break;\n\t }\n\t\n\t nextToolIndex++;\n\t }\n\t } else {\n\t\n\t nextToolIndex = (tools.indexOf(currentTool) + 1) % tools.length;\n\t visibleTool = tools[nextToolIndex];\n\t\n\t while (!editor.tools[visibleTool].displayInToolbox) {\n\t\n\t nextToolIndex = (nextToolIndex + 1) % tools.length;\n\t visibleTool = tools[nextToolIndex];\n\t }\n\t }\n\t\n\t toolToSelect = tools[nextToolIndex];\n\t\n\t for (var button in barButtons) {\n\t\n\t barButtons[button].classList.remove('selected');\n\t }\n\t\n\t barButtons[toolToSelect].classList.add('selected');\n\t editor.toolbar.current = toolToSelect;\n\t };\n\t\n\t /**\r\n\t * Transforming selected node type into selected toolbar element type\r\n\t * @param {event} event\r\n\t */\n\t toolbox.toolClicked = function (event) {\n\t\n\t /**\r\n\t * UNREPLACEBLE_TOOLS this types of tools are forbidden to replace even they are empty\r\n\t */\n\t var UNREPLACEBLE_TOOLS = ['image', 'link', 'list', 'instagram', 'twitter', 'embed'],\n\t tool = editor.tools[editor.toolbar.current],\n\t workingNode = editor.content.currentNode,\n\t currentInputIndex = editor.caret.inputIndex,\n\t newBlockContent,\n\t appendCallback,\n\t blockData;\n\t\n\t /** Make block from plugin */\n\t newBlockContent = tool.render();\n\t\n\t /** information about block */\n\t blockData = {\n\t block: newBlockContent,\n\t type: tool.type,\n\t stretched: false\n\t };\n\t\n\t if (workingNode && UNREPLACEBLE_TOOLS.indexOf(workingNode.dataset.tool) === -1 && workingNode.textContent.trim() === '') {\n\t\n\t /** Replace current block */\n\t editor.content.switchBlock(workingNode, newBlockContent, tool.type);\n\t } else {\n\t\n\t /** Insert new Block from plugin */\n\t editor.content.insertBlock(blockData);\n\t\n\t /** increase input index */\n\t currentInputIndex++;\n\t }\n\t\n\t /** Fire tool append callback */\n\t appendCallback = tool.appendCallback;\n\t\n\t if (appendCallback && typeof appendCallback == 'function') {\n\t\n\t appendCallback.call(event);\n\t }\n\t\n\t window.setTimeout(function () {\n\t\n\t /** Set caret to current block */\n\t editor.caret.setToBlock(currentInputIndex);\n\t }, 10);\n\t\n\t /**\r\n\t * Changing current Node\r\n\t */\n\t editor.content.workingNodeChanged();\n\t\n\t /**\r\n\t * Move toolbar when node is changed\r\n\t */\n\t editor.toolbar.move();\n\t };\n\t\n\t return toolbox;\n\t}({});\n\n/***/ },\n/* 12 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * @module Codex Editor Callbacks module\r\n\t * @description Module works with editor added Elements\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.4.0\r\n\t */\n\t\n\tmodule.exports = function (callbacks) {\n\t\n\t var editor = codex.editor;\n\t\n\t /**\r\n\t * used by UI module\r\n\t * @description Routes all keydowns on document\r\n\t * @param {Object} event\r\n\t */\n\t callbacks.globalKeydown = function (event) {\n\t\n\t switch (event.keyCode) {\n\t case editor.core.keys.ENTER:\n\t enterKeyPressed_(event);break;\n\t }\n\t };\n\t\n\t /**\r\n\t * used by UI module\r\n\t * @description Routes all keydowns on redactors area\r\n\t * @param {Object} event\r\n\t */\n\t callbacks.redactorKeyDown = function (event) {\n\t\n\t switch (event.keyCode) {\n\t case editor.core.keys.TAB:\n\t tabKeyPressedOnRedactorsZone_(event);break;\n\t case editor.core.keys.ENTER:\n\t enterKeyPressedOnRedactorsZone_(event);break;\n\t case editor.core.keys.ESC:\n\t escapeKeyPressedOnRedactorsZone_(event);break;\n\t default:\n\t defaultKeyPressedOnRedactorsZone_(event);break;\n\t }\n\t };\n\t\n\t /**\r\n\t * used by UI module\r\n\t * @description Routes all keyup events\r\n\t * @param {Object} event\r\n\t */\n\t callbacks.globalKeyup = function (event) {\n\t\n\t switch (event.keyCode) {\n\t case editor.core.keys.UP:\n\t case editor.core.keys.LEFT:\n\t case editor.core.keys.RIGHT:\n\t case editor.core.keys.DOWN:\n\t arrowKeyPressed_(event);break;\n\t }\n\t };\n\t\n\t /**\r\n\t * @param {Object} event\r\n\t * @private\r\n\t *\r\n\t * Handles behaviour when tab pressed\r\n\t * @description if Content is empty show toolbox (if it is closed) or leaf tools\r\n\t * uses Toolbars toolbox module to handle the situation\r\n\t */\n\t var tabKeyPressedOnRedactorsZone_ = function tabKeyPressedOnRedactorsZone_(event) {\n\t\n\t /**\r\n\t * Wait for solution. Would like to know the behaviour\r\n\t * @todo Add spaces\r\n\t */\n\t event.preventDefault();\n\t\n\t if (!editor.core.isBlockEmpty(editor.content.currentNode)) {\n\t\n\t return;\n\t }\n\t\n\t if (!editor.toolbar.opened) {\n\t\n\t editor.toolbar.open();\n\t }\n\t\n\t if (editor.toolbar.opened && !editor.toolbar.toolbox.opened) {\n\t\n\t editor.toolbar.toolbox.open();\n\t } else {\n\t\n\t editor.toolbar.toolbox.leaf();\n\t }\n\t };\n\t\n\t /**\r\n\t * Handles global EnterKey Press\r\n\t * @see enterPressedOnBlock_\r\n\t * @param {Object} event\r\n\t */\n\t var enterKeyPressed_ = function enterKeyPressed_() {\n\t\n\t if (editor.content.editorAreaHightlighted) {\n\t\n\t /**\r\n\t * it means that we lose input index, saved index before is not correct\r\n\t * therefore we need to set caret when we insert new block\r\n\t */\n\t editor.caret.inputIndex = -1;\n\t\n\t enterPressedOnBlock_();\n\t }\n\t };\n\t\n\t /**\r\n\t * Callback for enter key pressing in first-level block area\r\n\t *\r\n\t * @param {Event} event\r\n\t * @private\r\n\t *\r\n\t * @description Inserts new block with initial type from settings\r\n\t */\n\t var enterPressedOnBlock_ = function enterPressedOnBlock_() {\n\t\n\t var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\n\t\n\t editor.content.insertBlock({\n\t type: NEW_BLOCK_TYPE,\n\t block: editor.tools[NEW_BLOCK_TYPE].render()\n\t }, true);\n\t\n\t editor.toolbar.move();\n\t editor.toolbar.open();\n\t };\n\t\n\t /**\r\n\t * ENTER key handler\r\n\t *\r\n\t * @param {Object} event\r\n\t * @private\r\n\t *\r\n\t * @description Makes new block with initial type from settings\r\n\t */\n\t var enterKeyPressedOnRedactorsZone_ = function enterKeyPressedOnRedactorsZone_(event) {\n\t\n\t if (event.target.contentEditable == 'true') {\n\t\n\t /** Update input index */\n\t editor.caret.saveCurrentInputIndex();\n\t }\n\t\n\t var currentInputIndex = editor.caret.getCurrentInputIndex() || 0,\n\t workingNode = editor.content.currentNode,\n\t tool = workingNode.dataset.tool,\n\t isEnterPressedOnToolbar = editor.toolbar.opened && editor.toolbar.current && event.target == editor.state.inputs[currentInputIndex];\n\t\n\t /** The list of tools which needs the default browser behaviour */\n\t var enableLineBreaks = editor.tools[tool].enableLineBreaks;\n\t\n\t /** This type of block creates when enter is pressed */\n\t var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\n\t\n\t /**\r\n\t * When toolbar is opened, select tool instead of making new paragraph\r\n\t */\n\t if (isEnterPressedOnToolbar) {\n\t\n\t event.preventDefault();\n\t\n\t editor.toolbar.toolbox.toolClicked(event);\n\t\n\t editor.toolbar.close();\n\t\n\t /**\r\n\t * Stop other listeners callback executions\r\n\t */\n\t event.stopPropagation();\n\t event.stopImmediatePropagation();\n\t\n\t return;\n\t }\n\t\n\t /**\r\n\t * Allow paragraph lineBreaks with shift enter\r\n\t * Or if shiftkey pressed and enter and enabledLineBreaks, the let new block creation\r\n\t */\n\t if (event.shiftKey || enableLineBreaks) {\n\t\n\t event.stopPropagation();\n\t event.stopImmediatePropagation();\n\t return;\n\t }\n\t\n\t var currentSelection = window.getSelection(),\n\t currentSelectedNode = currentSelection.anchorNode,\n\t caretAtTheEndOfText = editor.caret.position.atTheEnd(),\n\t isTextNodeHasParentBetweenContenteditable = false;\n\t\n\t /**\r\n\t * Allow making new

    in same block by SHIFT+ENTER and forbids to prevent default browser behaviour\r\n\t */\n\t if (event.shiftKey && !enableLineBreaks) {\n\t\n\t editor.callback.enterPressedOnBlock(editor.content.currentBlock, event);\n\t event.preventDefault();\n\t return;\n\t }\n\t\n\t /**\r\n\t * Workaround situation when caret at the Text node that has some wrapper Elements\r\n\t * Split block cant handle this.\r\n\t * We need to save default behavior\r\n\t */\n\t isTextNodeHasParentBetweenContenteditable = currentSelectedNode && currentSelectedNode.parentNode.contentEditable != 'true';\n\t\n\t /**\r\n\t * Split blocks when input has several nodes and caret placed in textNode\r\n\t */\n\t if (currentSelectedNode.nodeType == editor.core.nodeTypes.TEXT && !isTextNodeHasParentBetweenContenteditable && !caretAtTheEndOfText) {\n\t\n\t event.preventDefault();\n\t\n\t editor.core.log('Splitting Text node...');\n\t\n\t editor.content.splitBlock(currentInputIndex);\n\t\n\t /** Show plus button when next input after split is empty*/\n\t if (!editor.state.inputs[currentInputIndex + 1].textContent.trim()) {\n\t\n\t editor.toolbar.showPlusButton();\n\t }\n\t } else {\n\t\n\t var islastNode = editor.content.isLastNode(currentSelectedNode);\n\t\n\t if (islastNode && caretAtTheEndOfText) {\n\t\n\t event.preventDefault();\n\t event.stopPropagation();\n\t event.stopImmediatePropagation();\n\t\n\t editor.core.log('ENTER clicked in last textNode. Create new BLOCK');\n\t\n\t editor.content.insertBlock({\n\t type: NEW_BLOCK_TYPE,\n\t block: editor.tools[NEW_BLOCK_TYPE].render()\n\t }, true);\n\t\n\t editor.toolbar.move();\n\t editor.toolbar.open();\n\t\n\t /** Show plus button with empty block */\n\t editor.toolbar.showPlusButton();\n\t }\n\t }\n\t\n\t /** get all inputs after new appending block */\n\t editor.ui.saveInputs();\n\t };\n\t\n\t /**\r\n\t * Escape behaviour\r\n\t * @param event\r\n\t * @private\r\n\t *\r\n\t * @description Closes toolbox and toolbar. Prevents default behaviour\r\n\t */\n\t var escapeKeyPressedOnRedactorsZone_ = function escapeKeyPressedOnRedactorsZone_(event) {\n\t\n\t /** Close all toolbar */\n\t editor.toolbar.close();\n\t\n\t /** Close toolbox */\n\t editor.toolbar.toolbox.close();\n\t\n\t event.preventDefault();\n\t };\n\t\n\t /**\r\n\t * @param {Event} event\r\n\t * @private\r\n\t *\r\n\t * closes and moves toolbar\r\n\t */\n\t var arrowKeyPressed_ = function arrowKeyPressed_(event) {\n\t\n\t editor.content.workingNodeChanged();\n\t\n\t /* Closing toolbar */\n\t editor.toolbar.close();\n\t editor.toolbar.move();\n\t };\n\t\n\t /**\r\n\t * @private\r\n\t * @param {Event} event\r\n\t *\r\n\t * @description Closes all opened bars from toolbar.\r\n\t * If block is mark, clears highlightning\r\n\t */\n\t var defaultKeyPressedOnRedactorsZone_ = function defaultKeyPressedOnRedactorsZone_() {\n\t\n\t editor.toolbar.close();\n\t\n\t if (!editor.toolbar.inline.actionsOpened) {\n\t\n\t editor.toolbar.inline.close();\n\t editor.content.clearMark();\n\t }\n\t };\n\t\n\t /**\r\n\t * Handler when clicked on redactors area\r\n\t *\r\n\t * @protected\r\n\t * @param event\r\n\t *\r\n\t * @description Detects clicked area. If it is first-level block area, marks as detected and\r\n\t * on next enter press will be inserted new block\r\n\t * Otherwise, save carets position (input index) and put caret to the editable zone.\r\n\t *\r\n\t * @see detectWhenClickedOnFirstLevelBlockArea_\r\n\t *\r\n\t */\n\t callbacks.redactorClicked = function (event) {\n\t\n\t detectWhenClickedOnFirstLevelBlockArea_();\n\t\n\t editor.content.workingNodeChanged(event.target);\n\t editor.ui.saveInputs();\n\t\n\t var selectedText = editor.toolbar.inline.getSelectionText(),\n\t firstLevelBlock;\n\t\n\t /** If selection range took off, then we hide inline toolbar */\n\t if (selectedText.length === 0) {\n\t\n\t editor.toolbar.inline.close();\n\t }\n\t\n\t /** Update current input index in memory when caret focused into existed input */\n\t if (event.target.contentEditable == 'true') {\n\t\n\t editor.caret.saveCurrentInputIndex();\n\t }\n\t\n\t if (editor.content.currentNode === null) {\n\t\n\t /**\r\n\t * If inputs in redactor does not exits, then we put input index 0 not -1\r\n\t */\n\t var indexOfLastInput = editor.state.inputs.length > 0 ? editor.state.inputs.length - 1 : 0;\n\t\n\t /** If we have any inputs */\n\t if (editor.state.inputs.length) {\n\t\n\t /** getting firstlevel parent of input */\n\t firstLevelBlock = editor.content.getFirstLevelBlock(editor.state.inputs[indexOfLastInput]);\n\t }\n\t\n\t /** If input is empty, then we set caret to the last input */\n\t if (editor.state.inputs.length && editor.state.inputs[indexOfLastInput].textContent === '' && firstLevelBlock.dataset.tool == editor.settings.initialBlockPlugin) {\n\t\n\t editor.caret.setToBlock(indexOfLastInput);\n\t } else {\n\t\n\t /** Create new input when caret clicked in redactors area */\n\t var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\n\t\n\t editor.content.insertBlock({\n\t type: NEW_BLOCK_TYPE,\n\t block: editor.tools[NEW_BLOCK_TYPE].render()\n\t });\n\t\n\t /** If there is no inputs except inserted */\n\t if (editor.state.inputs.length === 1) {\n\t\n\t editor.caret.setToBlock(indexOfLastInput);\n\t } else {\n\t\n\t /** Set caret to this appended input */\n\t editor.caret.setToNextBlock(indexOfLastInput);\n\t }\n\t }\n\t } else {\n\t\n\t /** Close all panels */\n\t editor.toolbar.settings.close();\n\t editor.toolbar.toolbox.close();\n\t }\n\t\n\t /**\r\n\t * Move toolbar and open\r\n\t */\n\t editor.toolbar.move();\n\t editor.toolbar.open();\n\t\n\t var inputIsEmpty = !editor.content.currentNode.textContent.trim(),\n\t currentNodeType = editor.content.currentNode.dataset.tool,\n\t isInitialType = currentNodeType == editor.settings.initialBlockPlugin;\n\t\n\t /** Hide plus buttons */\n\t editor.toolbar.hidePlusButton();\n\t\n\t if (!inputIsEmpty) {\n\t\n\t /** Mark current block */\n\t editor.content.markBlock();\n\t }\n\t\n\t if (isInitialType && inputIsEmpty) {\n\t\n\t /** Show plus button */\n\t editor.toolbar.showPlusButton();\n\t }\n\t };\n\t\n\t /**\r\n\t * This method allows to define, is caret in contenteditable element or not.\r\n\t *\r\n\t * @private\r\n\t *\r\n\t * @description Otherwise, if we get TEXT node from range container, that will means we have input index.\r\n\t * In this case we use default browsers behaviour (if plugin allows that) or overwritten action.\r\n\t * Therefore, to be sure that we've clicked first-level block area, we should have currentNode, which always\r\n\t * specifies to the first-level block. Other cases we just ignore.\r\n\t */\n\t var detectWhenClickedOnFirstLevelBlockArea_ = function detectWhenClickedOnFirstLevelBlockArea_() {\n\t\n\t var selection = window.getSelection(),\n\t anchorNode = selection.anchorNode,\n\t flag = false;\n\t\n\t if (selection.rangeCount === 0) {\n\t\n\t editor.content.editorAreaHightlighted = true;\n\t } else {\n\t\n\t if (!editor.core.isDomNode(anchorNode)) {\n\t\n\t anchorNode = anchorNode.parentNode;\n\t }\n\t\n\t /** Already founded, without loop */\n\t if (anchorNode.contentEditable == 'true') {\n\t\n\t flag = true;\n\t }\n\t\n\t while (anchorNode.contentEditable != 'true') {\n\t\n\t anchorNode = anchorNode.parentNode;\n\t\n\t if (anchorNode.contentEditable == 'true') {\n\t\n\t flag = true;\n\t }\n\t\n\t if (anchorNode == document.body) {\n\t\n\t break;\n\t }\n\t }\n\t\n\t /** If editable element founded, flag is \"TRUE\", Therefore we return \"FALSE\" */\n\t editor.content.editorAreaHightlighted = !flag;\n\t }\n\t };\n\t\n\t /**\r\n\t * Toolbar button click handler\r\n\t *\r\n\t * @param {Object} event - cursor to the button\r\n\t * @protected\r\n\t *\r\n\t * @description gets current tool and calls render method\r\n\t */\n\t callbacks.toolbarButtonClicked = function (event) {\n\t\n\t var button = this;\n\t\n\t editor.toolbar.current = button.dataset.type;\n\t\n\t editor.toolbar.toolbox.toolClicked(event);\n\t editor.toolbar.close();\n\t };\n\t\n\t /**\r\n\t * Show or Hide toolbox when plus button is clicked\r\n\t */\n\t callbacks.plusButtonClicked = function () {\n\t\n\t if (!editor.nodes.toolbox.classList.contains('opened')) {\n\t\n\t editor.toolbar.toolbox.open();\n\t } else {\n\t\n\t editor.toolbar.toolbox.close();\n\t }\n\t };\n\t\n\t /**\r\n\t * Block handlers for KeyDown events\r\n\t *\r\n\t * @protected\r\n\t * @param {Object} event\r\n\t *\r\n\t * Handles keydowns on block\r\n\t * @see blockRightOrDownArrowPressed_\r\n\t * @see backspacePressed_\r\n\t * @see blockLeftOrUpArrowPressed_\r\n\t */\n\t callbacks.blockKeydown = function (event) {\n\t\n\t var block = event.target; // event.target is input\n\t\n\t switch (event.keyCode) {\n\t\n\t case editor.core.keys.DOWN:\n\t case editor.core.keys.RIGHT:\n\t blockRightOrDownArrowPressed_(event);\n\t break;\n\t\n\t case editor.core.keys.BACKSPACE:\n\t backspacePressed_(block, event);\n\t break;\n\t\n\t case editor.core.keys.UP:\n\t case editor.core.keys.LEFT:\n\t blockLeftOrUpArrowPressed_(event);\n\t break;\n\t\n\t }\n\t };\n\t\n\t /**\r\n\t * RIGHT or DOWN keydowns on block\r\n\t *\r\n\t * @param {Object} event\r\n\t * @private\r\n\t *\r\n\t * @description watches the selection and gets closest editable element.\r\n\t * Uses method getDeepestTextNodeFromPosition to get the last node of next block\r\n\t * Sets caret if it is contenteditable\r\n\t */\n\t var blockRightOrDownArrowPressed_ = function blockRightOrDownArrowPressed_(event) {\n\t\n\t var selection = window.getSelection(),\n\t inputs = editor.state.inputs,\n\t focusedNode = selection.anchorNode,\n\t focusedNodeHolder;\n\t\n\t /** Check for caret existance */\n\t if (!focusedNode) {\n\t\n\t return false;\n\t }\n\t\n\t /** Looking for closest (parent) contentEditable element of focused node */\n\t while (focusedNode.contentEditable != 'true') {\n\t\n\t focusedNodeHolder = focusedNode.parentNode;\n\t focusedNode = focusedNodeHolder;\n\t }\n\t\n\t /** Input index in DOM level */\n\t var editableElementIndex = 0;\n\t\n\t while (focusedNode != inputs[editableElementIndex]) {\n\t\n\t editableElementIndex++;\n\t }\n\t\n\t /**\r\n\t * Founded contentEditable element doesn't have childs\r\n\t * Or maybe New created block\r\n\t */\n\t if (!focusedNode.textContent) {\n\t\n\t editor.caret.setToNextBlock(editableElementIndex);\n\t return;\n\t }\n\t\n\t /**\r\n\t * Do nothing when caret doesn not reaches the end of last child\r\n\t */\n\t var caretInLastChild = false,\n\t caretAtTheEndOfText = false;\n\t\n\t var lastChild, deepestTextnode;\n\t\n\t lastChild = focusedNode.childNodes[focusedNode.childNodes.length - 1];\n\t\n\t if (editor.core.isDomNode(lastChild)) {\n\t\n\t deepestTextnode = editor.content.getDeepestTextNodeFromPosition(lastChild, lastChild.childNodes.length);\n\t } else {\n\t\n\t deepestTextnode = lastChild;\n\t }\n\t\n\t caretInLastChild = selection.anchorNode == deepestTextnode;\n\t caretAtTheEndOfText = deepestTextnode.length == selection.anchorOffset;\n\t\n\t if (!caretInLastChild || !caretAtTheEndOfText) {\n\t\n\t editor.core.log('arrow [down|right] : caret does not reached the end');\n\t return false;\n\t }\n\t\n\t editor.caret.setToNextBlock(editableElementIndex);\n\t };\n\t\n\t /**\r\n\t * LEFT or UP keydowns on block\r\n\t *\r\n\t * @param {Object} event\r\n\t * @private\r\n\t *\r\n\t * watches the selection and gets closest editable element.\r\n\t * Uses method getDeepestTextNodeFromPosition to get the last node of previous block\r\n\t * Sets caret if it is contenteditable\r\n\t *\r\n\t */\n\t var blockLeftOrUpArrowPressed_ = function blockLeftOrUpArrowPressed_(event) {\n\t\n\t var selection = window.getSelection(),\n\t inputs = editor.state.inputs,\n\t focusedNode = selection.anchorNode,\n\t focusedNodeHolder;\n\t\n\t /** Check for caret existance */\n\t if (!focusedNode) {\n\t\n\t return false;\n\t }\n\t\n\t /**\r\n\t * LEFT or UP not at the beginning\r\n\t */\n\t if (selection.anchorOffset !== 0) {\n\t\n\t return false;\n\t }\n\t\n\t /** Looking for parent contentEditable block */\n\t while (focusedNode.contentEditable != 'true') {\n\t\n\t focusedNodeHolder = focusedNode.parentNode;\n\t focusedNode = focusedNodeHolder;\n\t }\n\t\n\t /** Input index in DOM level */\n\t var editableElementIndex = 0;\n\t\n\t while (focusedNode != inputs[editableElementIndex]) {\n\t\n\t editableElementIndex++;\n\t }\n\t\n\t /**\r\n\t * Do nothing if caret is not at the beginning of first child\r\n\t */\n\t var caretInFirstChild = false,\n\t caretAtTheBeginning = false;\n\t\n\t var firstChild, deepestTextnode;\n\t\n\t /**\r\n\t * Founded contentEditable element doesn't have childs\r\n\t * Or maybe New created block\r\n\t */\n\t if (!focusedNode.textContent) {\n\t\n\t editor.caret.setToPreviousBlock(editableElementIndex);\n\t return;\n\t }\n\t\n\t firstChild = focusedNode.childNodes[0];\n\t\n\t if (editor.core.isDomNode(firstChild)) {\n\t\n\t deepestTextnode = editor.content.getDeepestTextNodeFromPosition(firstChild, 0);\n\t } else {\n\t\n\t deepestTextnode = firstChild;\n\t }\n\t\n\t caretInFirstChild = selection.anchorNode == deepestTextnode;\n\t caretAtTheBeginning = selection.anchorOffset === 0;\n\t\n\t if (caretInFirstChild && caretAtTheBeginning) {\n\t\n\t editor.caret.setToPreviousBlock(editableElementIndex);\n\t }\n\t };\n\t\n\t /**\r\n\t * Handles backspace keydown\r\n\t *\r\n\t * @param {Element} block\r\n\t * @param {Object} event\r\n\t * @private\r\n\t *\r\n\t * @description if block is empty, delete the block and set caret to the previous block\r\n\t * If block is not empty, try to merge two blocks - current and previous\r\n\t * But it we try'n to remove first block, then we should set caret to the next block, not previous.\r\n\t * If we removed the last block, create new one\r\n\t */\n\t var backspacePressed_ = function backspacePressed_(block, event) {\n\t\n\t var currentInputIndex = editor.caret.getCurrentInputIndex(),\n\t range,\n\t selectionLength,\n\t firstLevelBlocksCount;\n\t\n\t if (editor.core.isNativeInput(event.target)) {\n\t\n\t /** If input value is empty - remove block */\n\t if (event.target.value.trim() == '') {\n\t\n\t block.remove();\n\t } else {\n\t\n\t return;\n\t }\n\t }\n\t\n\t if (block.textContent.trim()) {\n\t\n\t range = editor.content.getRange();\n\t selectionLength = range.endOffset - range.startOffset;\n\t\n\t if (editor.caret.position.atStart() && !selectionLength && editor.state.inputs[currentInputIndex - 1]) {\n\t\n\t editor.content.mergeBlocks(currentInputIndex);\n\t } else {\n\t\n\t return;\n\t }\n\t }\n\t\n\t if (!selectionLength) {\n\t\n\t block.remove();\n\t }\n\t\n\t firstLevelBlocksCount = editor.nodes.redactor.childNodes.length;\n\t\n\t /**\r\n\t * If all blocks are removed\r\n\t */\n\t if (firstLevelBlocksCount === 0) {\n\t\n\t /** update currentNode variable */\n\t editor.content.currentNode = null;\n\t\n\t /** Inserting new empty initial block */\n\t editor.ui.addInitialBlock();\n\t\n\t /** Updating inputs state after deleting last block */\n\t editor.ui.saveInputs();\n\t\n\t /** Set to current appended block */\n\t window.setTimeout(function () {\n\t\n\t editor.caret.setToPreviousBlock(1);\n\t }, 10);\n\t } else {\n\t\n\t if (editor.caret.inputIndex !== 0) {\n\t\n\t /** Target block is not first */\n\t editor.caret.setToPreviousBlock(editor.caret.inputIndex);\n\t } else {\n\t\n\t /** If we try to delete first block */\n\t editor.caret.setToNextBlock(editor.caret.inputIndex);\n\t }\n\t }\n\t\n\t editor.toolbar.move();\n\t\n\t if (!editor.toolbar.opened) {\n\t\n\t editor.toolbar.open();\n\t }\n\t\n\t /** Updating inputs state */\n\t editor.ui.saveInputs();\n\t\n\t /** Prevent default browser behaviour */\n\t event.preventDefault();\n\t };\n\t\n\t /**\r\n\t * used by UI module\r\n\t * Clicks on block settings button\r\n\t *\r\n\t * @param {Object} event\r\n\t * @protected\r\n\t * @description Opens toolbar settings\r\n\t */\n\t callbacks.showSettingsButtonClicked = function (event) {\n\t\n\t /**\r\n\t * Get type of current block\r\n\t * It uses to append settings from tool.settings property.\r\n\t * ...\r\n\t * Type is stored in data-type attribute on block\r\n\t */\n\t var currentToolType = editor.content.currentNode.dataset.tool;\n\t\n\t editor.toolbar.settings.toggle(currentToolType);\n\t\n\t /** Close toolbox when settings button is active */\n\t editor.toolbar.toolbox.close();\n\t editor.toolbar.settings.hideRemoveActions();\n\t };\n\t\n\t return callbacks;\n\t}({});\n\n/***/ },\n/* 13 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor Draw module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0.\r\n\t */\n\t\n\tmodule.exports = function (draw) {\n\t\n\t /**\r\n\t * Base editor wrapper\r\n\t */\n\t draw.wrapper = function () {\n\t\n\t var wrapper = document.createElement('div');\n\t\n\t wrapper.className += 'codex-editor';\n\t\n\t return wrapper;\n\t };\n\t\n\t /**\r\n\t * Content-editable holder\r\n\t */\n\t draw.redactor = function () {\n\t\n\t var redactor = document.createElement('div');\n\t\n\t redactor.className += 'ce-redactor';\n\t\n\t return redactor;\n\t };\n\t\n\t draw.ceBlock = function () {\n\t\n\t var block = document.createElement('DIV');\n\t\n\t block.className += 'ce_block';\n\t\n\t return block;\n\t };\n\t\n\t /**\r\n\t * Empty toolbar with toggler\r\n\t */\n\t draw.toolbar = function () {\n\t\n\t var bar = document.createElement('div');\n\t\n\t bar.className += 'ce-toolbar';\n\t\n\t return bar;\n\t };\n\t\n\t draw.toolbarContent = function () {\n\t\n\t var wrapper = document.createElement('DIV');\n\t\n\t wrapper.classList.add('ce-toolbar__content');\n\t\n\t return wrapper;\n\t };\n\t\n\t /**\r\n\t * Inline toolbar\r\n\t */\n\t draw.inlineToolbar = function () {\n\t\n\t var bar = document.createElement('DIV');\n\t\n\t bar.className += 'ce-toolbar-inline';\n\t\n\t return bar;\n\t };\n\t\n\t /**\r\n\t * Wrapper for inline toobar buttons\r\n\t */\n\t draw.inlineToolbarButtons = function () {\n\t\n\t var wrapper = document.createElement('DIV');\n\t\n\t wrapper.className += 'ce-toolbar-inline__buttons';\n\t\n\t return wrapper;\n\t };\n\t\n\t /**\r\n\t * For some actions\r\n\t */\n\t draw.inlineToolbarActions = function () {\n\t\n\t var wrapper = document.createElement('DIV');\n\t\n\t wrapper.className += 'ce-toolbar-inline__actions';\n\t\n\t return wrapper;\n\t };\n\t\n\t draw.inputForLink = function () {\n\t\n\t var input = document.createElement('INPUT');\n\t\n\t input.type = 'input';\n\t input.className += 'inputForLink';\n\t input.placeholder = 'Вставьте ссылку ...';\n\t input.setAttribute('form', 'defaultForm');\n\t\n\t input.setAttribute('autofocus', 'autofocus');\n\t\n\t return input;\n\t };\n\t\n\t /**\r\n\t * @todo Desc\r\n\t */\n\t draw.blockButtons = function () {\n\t\n\t var block = document.createElement('div');\n\t\n\t block.className += 'ce-toolbar__actions';\n\t\n\t return block;\n\t };\n\t\n\t /**\r\n\t * Block settings panel\r\n\t */\n\t draw.blockSettings = function () {\n\t\n\t var settings = document.createElement('div');\n\t\n\t settings.className += 'ce-settings';\n\t\n\t return settings;\n\t };\n\t\n\t draw.defaultSettings = function () {\n\t\n\t var div = document.createElement('div');\n\t\n\t div.classList.add('ce-settings_default');\n\t\n\t return div;\n\t };\n\t\n\t draw.pluginsSettings = function () {\n\t\n\t var div = document.createElement('div');\n\t\n\t div.classList.add('ce-settings_plugin');\n\t\n\t return div;\n\t };\n\t\n\t draw.plusButton = function () {\n\t\n\t var button = document.createElement('span');\n\t\n\t button.className = 'ce-toolbar__plus';\n\t // button.innerHTML = '';\n\t\n\t return button;\n\t };\n\t\n\t /**\r\n\t * Settings button in toolbar\r\n\t */\n\t draw.settingsButton = function () {\n\t\n\t var toggler = document.createElement('span');\n\t\n\t toggler.className = 'ce-toolbar__settings-btn';\n\t\n\t /** Toggler button*/\n\t toggler.innerHTML = '';\n\t\n\t return toggler;\n\t };\n\t\n\t /**\r\n\t * Redactor tools wrapper\r\n\t */\n\t\n\t draw.toolbox = function () {\n\t\n\t var wrapper = document.createElement('div');\n\t\n\t wrapper.className = 'ce-toolbar__tools';\n\t\n\t return wrapper;\n\t };\n\t\n\t /**\r\n\t * @protected\r\n\t *\r\n\t * Draws tool buttons for toolbox\r\n\t *\r\n\t * @param {String} type\r\n\t * @param {String} classname\r\n\t * @returns {Element}\r\n\t */\n\t draw.toolbarButton = function (type, classname) {\n\t\n\t var button = document.createElement('li'),\n\t toolIcon = document.createElement('i'),\n\t toolTitle = document.createElement('span');\n\t\n\t button.dataset.type = type;\n\t button.setAttribute('title', type);\n\t\n\t toolIcon.classList.add(classname);\n\t toolTitle.classList.add('ce_toolbar_tools--title');\n\t\n\t button.appendChild(toolIcon);\n\t button.appendChild(toolTitle);\n\t\n\t return button;\n\t };\n\t\n\t /**\r\n\t * @protected\r\n\t *\r\n\t * Draws tools for inline toolbar\r\n\t *\r\n\t * @param {String} type\r\n\t * @param {String} classname\r\n\t */\n\t draw.toolbarButtonInline = function (type, classname) {\n\t\n\t var button = document.createElement('BUTTON'),\n\t toolIcon = document.createElement('I');\n\t\n\t button.type = 'button';\n\t button.dataset.type = type;\n\t toolIcon.classList.add(classname);\n\t\n\t button.appendChild(toolIcon);\n\t\n\t return button;\n\t };\n\t\n\t /**\r\n\t * Redactor block\r\n\t */\n\t draw.block = function (tagName, content) {\n\t\n\t var node = document.createElement(tagName);\n\t\n\t node.innerHTML = content || '';\n\t\n\t return node;\n\t };\n\t\n\t /**\r\n\t * Creates Node with passed tagName and className\r\n\t * @param {string} tagName\r\n\t * @param {string} className\r\n\t * @param {object} properties - allow to assign properties\r\n\t */\n\t draw.node = function (tagName, className, properties) {\n\t\n\t var el = document.createElement(tagName);\n\t\n\t if (className) el.className = className;\n\t\n\t if (properties) {\n\t\n\t for (var name in properties) {\n\t\n\t el[name] = properties[name];\n\t }\n\t }\n\t\n\t return el;\n\t };\n\t\n\t /**\r\n\t * Unavailable plugin block\r\n\t */\n\t draw.unavailableBlock = function () {\n\t\n\t var wrapper = document.createElement('DIV');\n\t\n\t wrapper.classList.add('cdx-unavailable-block');\n\t\n\t return wrapper;\n\t };\n\t\n\t return draw;\n\t}({});\n\n/***/ },\n/* 14 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor Caret Module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0\r\n\t */\n\t\n\tmodule.exports = function (caret) {\n\t\n\t var editor = codex.editor;\n\t\n\t /**\r\n\t * @var {int} InputIndex - editable element in DOM\r\n\t */\n\t caret.inputIndex = null;\n\t\n\t /**\r\n\t * @var {int} offset - caret position in a text node.\r\n\t */\n\t caret.offset = null;\n\t\n\t /**\r\n\t * @var {int} focusedNodeIndex - we get index of child node from first-level block\r\n\t */\n\t caret.focusedNodeIndex = null;\n\t\n\t /**\r\n\t * Creates Document Range and sets caret to the element.\r\n\t * @protected\r\n\t * @uses caret.save — if you need to save caret position\r\n\t * @param {Element} el - Changed Node.\r\n\t */\n\t caret.set = function (el, index, offset) {\n\t\n\t offset = offset || caret.offset || 0;\n\t index = index || caret.focusedNodeIndex || 0;\n\t\n\t var childs = el.childNodes,\n\t nodeToSet;\n\t\n\t if (childs.length === 0) {\n\t\n\t nodeToSet = el;\n\t } else {\n\t\n\t nodeToSet = childs[index];\n\t }\n\t\n\t /** If Element is INPUT */\n\t if (el.contentEditable != 'true') {\n\t\n\t el.focus();\n\t return;\n\t }\n\t\n\t if (editor.core.isDomNode(nodeToSet)) {\n\t\n\t nodeToSet = editor.content.getDeepestTextNodeFromPosition(nodeToSet, nodeToSet.childNodes.length);\n\t }\n\t\n\t var range = document.createRange(),\n\t selection = window.getSelection();\n\t\n\t window.setTimeout(function () {\n\t\n\t range.setStart(nodeToSet, offset);\n\t range.setEnd(nodeToSet, offset);\n\t\n\t selection.removeAllRanges();\n\t selection.addRange(range);\n\t\n\t editor.caret.saveCurrentInputIndex();\n\t }, 20);\n\t };\n\t\n\t /**\r\n\t * @protected\r\n\t * Updates index of input and saves it in caret object\r\n\t */\n\t caret.saveCurrentInputIndex = function () {\n\t\n\t /** Index of Input that we paste sanitized content */\n\t var selection = window.getSelection(),\n\t inputs = editor.state.inputs,\n\t focusedNode = selection.anchorNode,\n\t focusedNodeHolder;\n\t\n\t if (!focusedNode) {\n\t\n\t return;\n\t }\n\t\n\t /** Looking for parent contentEditable block */\n\t while (focusedNode.contentEditable != 'true') {\n\t\n\t focusedNodeHolder = focusedNode.parentNode;\n\t focusedNode = focusedNodeHolder;\n\t }\n\t\n\t /** Input index in DOM level */\n\t var editableElementIndex = 0;\n\t\n\t while (focusedNode != inputs[editableElementIndex]) {\n\t\n\t editableElementIndex++;\n\t }\n\t\n\t caret.inputIndex = editableElementIndex;\n\t };\n\t\n\t /**\r\n\t * Returns current input index (caret object)\r\n\t */\n\t caret.getCurrentInputIndex = function () {\n\t\n\t return caret.inputIndex;\n\t };\n\t\n\t /**\r\n\t * @param {int} index - index of first-level block after that we set caret into next input\r\n\t */\n\t caret.setToNextBlock = function (index) {\n\t\n\t var inputs = editor.state.inputs,\n\t nextInput = inputs[index + 1];\n\t\n\t if (!nextInput) {\n\t\n\t editor.core.log('We are reached the end');\n\t return;\n\t }\n\t\n\t /**\r\n\t * When new Block created or deleted content of input\r\n\t * We should add some text node to set caret\r\n\t */\n\t if (!nextInput.childNodes.length) {\n\t\n\t var emptyTextElement = document.createTextNode('');\n\t\n\t nextInput.appendChild(emptyTextElement);\n\t }\n\t\n\t editor.caret.inputIndex = index + 1;\n\t editor.caret.set(nextInput, 0, 0);\n\t editor.content.workingNodeChanged(nextInput);\n\t };\n\t\n\t /**\r\n\t * @param {int} index - index of target input.\r\n\t * Sets caret to input with this index\r\n\t */\n\t caret.setToBlock = function (index) {\n\t\n\t var inputs = editor.state.inputs,\n\t targetInput = inputs[index];\n\t\n\t if (!targetInput) {\n\t\n\t return;\n\t }\n\t\n\t /**\r\n\t * When new Block created or deleted content of input\r\n\t * We should add some text node to set caret\r\n\t */\n\t if (!targetInput.childNodes.length) {\n\t\n\t var emptyTextElement = document.createTextNode('');\n\t\n\t targetInput.appendChild(emptyTextElement);\n\t }\n\t\n\t editor.caret.inputIndex = index;\n\t editor.caret.set(targetInput, 0, 0);\n\t editor.content.workingNodeChanged(targetInput);\n\t };\n\t\n\t /**\r\n\t * @param {int} index - index of input\r\n\t */\n\t caret.setToPreviousBlock = function (index) {\n\t\n\t index = index || 0;\n\t\n\t var inputs = editor.state.inputs,\n\t previousInput = inputs[index - 1],\n\t lastChildNode,\n\t lengthOfLastChildNode,\n\t emptyTextElement;\n\t\n\t if (!previousInput) {\n\t\n\t editor.core.log('We are reached first node');\n\t return;\n\t }\n\t\n\t lastChildNode = editor.content.getDeepestTextNodeFromPosition(previousInput, previousInput.childNodes.length);\n\t lengthOfLastChildNode = lastChildNode.length;\n\t\n\t /**\r\n\t * When new Block created or deleted content of input\r\n\t * We should add some text node to set caret\r\n\t */\n\t if (!previousInput.childNodes.length) {\n\t\n\t emptyTextElement = document.createTextNode('');\n\t previousInput.appendChild(emptyTextElement);\n\t }\n\t editor.caret.inputIndex = index - 1;\n\t editor.caret.set(previousInput, previousInput.childNodes.length - 1, lengthOfLastChildNode);\n\t editor.content.workingNodeChanged(inputs[index - 1]);\n\t };\n\t\n\t caret.position = {\n\t\n\t atStart: function atStart() {\n\t\n\t var selection = window.getSelection(),\n\t anchorOffset = selection.anchorOffset,\n\t anchorNode = selection.anchorNode,\n\t firstLevelBlock = editor.content.getFirstLevelBlock(anchorNode),\n\t pluginsRender = firstLevelBlock.childNodes[0];\n\t\n\t if (!editor.core.isDomNode(anchorNode)) {\n\t\n\t anchorNode = anchorNode.parentNode;\n\t }\n\t\n\t var isFirstNode = anchorNode === pluginsRender.childNodes[0],\n\t isOffsetZero = anchorOffset === 0;\n\t\n\t return isFirstNode && isOffsetZero;\n\t },\n\t\n\t atTheEnd: function atTheEnd() {\n\t\n\t var selection = window.getSelection(),\n\t anchorOffset = selection.anchorOffset,\n\t anchorNode = selection.anchorNode;\n\t\n\t /** Caret is at the end of input */\n\t return !anchorNode || !anchorNode.length || anchorOffset === anchorNode.length;\n\t }\n\t };\n\t\n\t /**\r\n\t * Inserts node at the caret location\r\n\t * @param {HTMLElement|DocumentFragment} node\r\n\t */\n\t caret.insertNode = function (node) {\n\t\n\t var selection,\n\t range,\n\t lastNode = node;\n\t\n\t if (node.nodeType == editor.core.nodeTypes.DOCUMENT_FRAGMENT) {\n\t\n\t lastNode = node.lastChild;\n\t }\n\t\n\t selection = window.getSelection();\n\t\n\t range = selection.getRangeAt(0);\n\t range.deleteContents();\n\t\n\t range.insertNode(node);\n\t\n\t range.setStartAfter(lastNode);\n\t range.collapse(true);\n\t\n\t selection.removeAllRanges();\n\t selection.addRange(range);\n\t };\n\t\n\t return caret;\n\t}({});\n\n/***/ },\n/* 15 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor Notification Module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0\r\n\t */\n\t\n\tmodule.exports = function (notifications) {\n\t\n\t var editor = codex.editor;\n\t\n\t var queue = [];\n\t\n\t var addToQueue = function addToQueue(settings) {\n\t\n\t queue.push(settings);\n\t\n\t var index = 0;\n\t\n\t while (index < queue.length && queue.length > 5) {\n\t\n\t if (queue[index].type == 'confirm' || queue[index].type == 'prompt') {\n\t\n\t index++;\n\t continue;\n\t }\n\t\n\t queue[index].close();\n\t queue.splice(index, 1);\n\t }\n\t };\n\t\n\t notifications.createHolder = function () {\n\t\n\t var holder = editor.draw.node('DIV', 'cdx-notifications-block');\n\t\n\t editor.nodes.notifications = document.body.appendChild(holder);\n\t\n\t return holder;\n\t };\n\t\n\t /**\r\n\t * Error notificator. Shows block with message\r\n\t * @protected\r\n\t */\n\t notifications.errorThrown = function (errorMsg, event) {\n\t\n\t editor.notifications.notification({ message: 'This action is not available currently', type: event.type });\n\t };\n\t\n\t /**\r\n\t *\r\n\t * Appends notification\r\n\t *\r\n\t * settings = {\r\n\t * type - notification type (reserved types: alert, confirm, prompt). Just add class 'cdx-notification-'+type\r\n\t * message - notification message\r\n\t * okMsg - confirm button text (default - 'Ok')\r\n\t * cancelBtn - cancel button text (default - 'Cancel'). Only for confirm and prompt types\r\n\t * confirm - function-handler for ok button click\r\n\t * cancel - function-handler for cancel button click. Only for confirm and prompt types\r\n\t * time - time (in seconds) after which notification will close (default - 10s)\r\n\t * }\r\n\t *\r\n\t * @param settings\r\n\t */\n\t notifications.notification = function (constructorSettings) {\n\t\n\t /** Private vars and methods */\n\t var notification = null,\n\t cancel = null,\n\t type = null,\n\t confirm = null,\n\t inputField = null;\n\t\n\t var confirmHandler = function confirmHandler() {\n\t\n\t close();\n\t\n\t if (typeof confirm !== 'function') {\n\t\n\t return;\n\t }\n\t\n\t if (type == 'prompt') {\n\t\n\t confirm(inputField.value);\n\t return;\n\t }\n\t\n\t confirm();\n\t };\n\t\n\t var cancelHandler = function cancelHandler() {\n\t\n\t close();\n\t\n\t if (typeof cancel !== 'function') {\n\t\n\t return;\n\t }\n\t\n\t cancel();\n\t };\n\t\n\t /** Public methods */\n\t function create(settings) {\n\t\n\t if (!(settings && settings.message)) {\n\t\n\t editor.core.log('Can\\'t create notification. Message is missed');\n\t return;\n\t }\n\t\n\t settings.type = settings.type || 'alert';\n\t settings.time = settings.time * 1000 || 10000;\n\t\n\t var wrapper = editor.draw.node('DIV', 'cdx-notification'),\n\t message = editor.draw.node('DIV', 'cdx-notification__message'),\n\t input = editor.draw.node('INPUT', 'cdx-notification__input'),\n\t okBtn = editor.draw.node('SPAN', 'cdx-notification__ok-btn'),\n\t cancelBtn = editor.draw.node('SPAN', 'cdx-notification__cancel-btn');\n\t\n\t message.textContent = settings.message;\n\t okBtn.textContent = settings.okMsg || 'ОК';\n\t cancelBtn.textContent = settings.cancelMsg || 'Отмена';\n\t\n\t editor.listeners.add(okBtn, 'click', confirmHandler);\n\t editor.listeners.add(cancelBtn, 'click', cancelHandler);\n\t\n\t wrapper.appendChild(message);\n\t\n\t if (settings.type == 'prompt') {\n\t\n\t wrapper.appendChild(input);\n\t }\n\t\n\t wrapper.appendChild(okBtn);\n\t\n\t if (settings.type == 'prompt' || settings.type == 'confirm') {\n\t\n\t wrapper.appendChild(cancelBtn);\n\t }\n\t\n\t wrapper.classList.add('cdx-notification-' + settings.type);\n\t wrapper.dataset.type = settings.type;\n\t\n\t notification = wrapper;\n\t type = settings.type;\n\t confirm = settings.confirm;\n\t cancel = settings.cancel;\n\t inputField = input;\n\t\n\t if (settings.type != 'prompt' && settings.type != 'confirm') {\n\t\n\t window.setTimeout(close, settings.time);\n\t }\n\t };\n\t\n\t function send() {\n\t\n\t editor.nodes.notifications.appendChild(notification);\n\t inputField.focus();\n\t\n\t editor.nodes.notifications.classList.add('cdx-notification__notification-appending');\n\t\n\t window.setTimeout(function () {\n\t\n\t editor.nodes.notifications.classList.remove('cdx-notification__notification-appending');\n\t }, 100);\n\t\n\t addToQueue({ type: type, close: close });\n\t };\n\t\n\t function close() {\n\t\n\t notification.remove();\n\t };\n\t\n\t if (constructorSettings) {\n\t\n\t create(constructorSettings);\n\t send();\n\t }\n\t\n\t return {\n\t create: create,\n\t send: send,\n\t close: close\n\t };\n\t };\n\t\n\t notifications.clear = function () {\n\t\n\t editor.nodes.notifications.innerHTML = '';\n\t queue = [];\n\t };\n\t\n\t return notifications;\n\t}({});\n\n/***/ },\n/* 16 */\n/***/ function(module, exports) {\n\n\t\"use strict\";\n\t\n\t/**\r\n\t * Codex Editor Parser Module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.1\r\n\t */\n\t\n\tmodule.exports = function (parser) {\n\t\n\t var editor = codex.editor;\n\t\n\t /** inserting text */\n\t parser.insertPastedContent = function (blockType, tag) {\n\t\n\t editor.content.insertBlock({\n\t type: blockType.type,\n\t block: blockType.render({\n\t text: tag.innerHTML\n\t })\n\t });\n\t };\n\t\n\t /**\r\n\t * Check DOM node for display style: separated block or child-view\r\n\t */\n\t parser.isFirstLevelBlock = function (node) {\n\t\n\t return node.nodeType == editor.core.nodeTypes.TAG && node.classList.contains(editor.ui.className.BLOCK_CLASSNAME);\n\t };\n\t\n\t return parser;\n\t}({});\n\n/***/ },\n/* 17 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Sanitizer\r\n\t */\n\t\n\tmodule.exports = function (sanitizer) {\n\t\n\t /** HTML Janitor library */\n\t var janitor = __webpack_require__(18);\n\t\n\t /** Codex Editor */\n\t var editor = codex.editor;\n\t\n\t sanitizer.prepare = function () {\n\t\n\t if (editor.settings.sanitizer && !editor.core.isEmpty(editor.settings.sanitizer)) {\n\t\n\t Config.CUSTOM = editor.settings.sanitizer;\n\t }\n\t };\n\t\n\t /**\r\n\t * Basic config\r\n\t */\n\t var Config = {\n\t\n\t /** User configuration */\n\t CUSTOM: null,\n\t\n\t BASIC: {\n\t\n\t tags: {\n\t p: {},\n\t a: {\n\t href: true,\n\t target: '_blank',\n\t rel: 'nofollow'\n\t },\n\t i: {},\n\t b: {},\n\t strong: {},\n\t em: {},\n\t span: {}\n\t }\n\t }\n\t };\n\t\n\t sanitizer.Config = Config;\n\t\n\t /**\r\n\t *\r\n\t * @param userCustomConfig\r\n\t * @returns {*}\r\n\t * @private\r\n\t *\r\n\t * @description If developer uses editor's API, then he can customize sane restrictions.\r\n\t * Or, sane config can be defined globally in editors initialization. That config will be used everywhere\r\n\t * At least, if there is no config overrides, that API uses BASIC Default configation\r\n\t */\n\t var init_ = function init_(userCustomConfig) {\n\t\n\t var configuration = userCustomConfig || Config.CUSTOM || Config.BASIC;\n\t\n\t return new janitor(configuration);\n\t };\n\t\n\t /**\r\n\t * Cleans string from unwanted tags\r\n\t * @protected\r\n\t * @param {String} dirtyString - taint string\r\n\t * @param {Object} customConfig - allowed tags\r\n\t */\n\t sanitizer.clean = function (dirtyString, customConfig) {\n\t\n\t var janitorInstance = init_(customConfig);\n\t\n\t return janitorInstance.clean(dirtyString);\n\t };\n\t\n\t return sanitizer;\n\t}({});\n\n/***/ },\n/* 18 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;(function (root, factory) {\n\t if (true) {\n\t !(__WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t } else if (typeof exports === 'object') {\n\t module.exports = factory();\n\t } else {\n\t root.HTMLJanitor = factory();\n\t }\n\t}(this, function () {\n\t\n\t /**\n\t * @param {Object} config.tags Dictionary of allowed tags.\n\t * @param {boolean} config.keepNestedBlockElements Default false.\n\t */\n\t function HTMLJanitor(config) {\n\t\n\t var tagDefinitions = config['tags'];\n\t var tags = Object.keys(tagDefinitions);\n\t\n\t var validConfigValues = tags\n\t .map(function(k) { return typeof tagDefinitions[k]; })\n\t .every(function(type) { return type === 'object' || type === 'boolean' || type === 'function'; });\n\t\n\t if(!validConfigValues) {\n\t throw new Error(\"The configuration was invalid\");\n\t }\n\t\n\t this.config = config;\n\t }\n\t\n\t // TODO: not exhaustive?\n\t var blockElementNames = ['P', 'LI', 'TD', 'TH', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'PRE'];\n\t function isBlockElement(node) {\n\t return blockElementNames.indexOf(node.nodeName) !== -1;\n\t }\n\t\n\t var inlineElementNames = ['A', 'B', 'STRONG', 'I', 'EM', 'SUB', 'SUP', 'U', 'STRIKE'];\n\t function isInlineElement(node) {\n\t return inlineElementNames.indexOf(node.nodeName) !== -1;\n\t }\n\t\n\t HTMLJanitor.prototype.clean = function (html) {\n\t var sandbox = document.createElement('div');\n\t sandbox.innerHTML = html;\n\t\n\t this._sanitize(sandbox);\n\t\n\t return sandbox.innerHTML;\n\t };\n\t\n\t HTMLJanitor.prototype._sanitize = function (parentNode) {\n\t var treeWalker = createTreeWalker(parentNode);\n\t var node = treeWalker.firstChild();\n\t if (!node) { return; }\n\t\n\t do {\n\t // Ignore nodes that have already been sanitized\n\t if (node._sanitized) {\n\t continue;\n\t }\n\t\n\t if (node.nodeType === Node.TEXT_NODE) {\n\t // If this text node is just whitespace and the previous or next element\n\t // sibling is a block element, remove it\n\t // N.B.: This heuristic could change. Very specific to a bug with\n\t // `contenteditable` in Firefox: http://jsbin.com/EyuKase/1/edit?js,output\n\t // FIXME: make this an option?\n\t if (node.data.trim() === ''\n\t && ((node.previousElementSibling && isBlockElement(node.previousElementSibling))\n\t || (node.nextElementSibling && isBlockElement(node.nextElementSibling)))) {\n\t parentNode.removeChild(node);\n\t this._sanitize(parentNode);\n\t break;\n\t } else {\n\t continue;\n\t }\n\t }\n\t\n\t // Remove all comments\n\t if (node.nodeType === Node.COMMENT_NODE) {\n\t parentNode.removeChild(node);\n\t this._sanitize(parentNode);\n\t break;\n\t }\n\t\n\t var isInline = isInlineElement(node);\n\t var containsBlockElement;\n\t if (isInline) {\n\t containsBlockElement = Array.prototype.some.call(node.childNodes, isBlockElement);\n\t }\n\t\n\t // Block elements should not be nested (e.g.

  • ...); if\n\t // they are, we want to unwrap the inner block element.\n\t var isNotTopContainer = !! parentNode.parentNode;\n\t var isNestedBlockElement =\n\t isBlockElement(parentNode) &&\n\t isBlockElement(node) &&\n\t isNotTopContainer;\n\t\n\t var nodeName = node.nodeName.toLowerCase();\n\t\n\t var allowedAttrs = getAllowedAttrs(this.config, nodeName, node);\n\t\n\t var isInvalid = isInline && containsBlockElement;\n\t\n\t // Drop tag entirely according to the whitelist *and* if the markup\n\t // is invalid.\n\t if (isInvalid || shouldRejectNode(node, allowedAttrs)\n\t || (!this.config.keepNestedBlockElements && isNestedBlockElement)) {\n\t // Do not keep the inner text of SCRIPT/STYLE elements.\n\t if (! (node.nodeName === 'SCRIPT' || node.nodeName === 'STYLE')) {\n\t while (node.childNodes.length > 0) {\n\t parentNode.insertBefore(node.childNodes[0], node);\n\t }\n\t }\n\t parentNode.removeChild(node);\n\t\n\t this._sanitize(parentNode);\n\t break;\n\t }\n\t\n\t // Sanitize attributes\n\t for (var a = 0; a < node.attributes.length; a += 1) {\n\t var attr = node.attributes[a];\n\t\n\t if (shouldRejectAttr(attr, allowedAttrs, node)) {\n\t node.removeAttribute(attr.name);\n\t // Shift the array to continue looping.\n\t a = a - 1;\n\t }\n\t }\n\t\n\t // Sanitize children\n\t this._sanitize(node);\n\t\n\t // Mark node as sanitized so it's ignored in future runs\n\t node._sanitized = true;\n\t } while ((node = treeWalker.nextSibling()));\n\t };\n\t\n\t function createTreeWalker(node) {\n\t return document.createTreeWalker(node,\n\t NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT,\n\t null, false);\n\t }\n\t\n\t function getAllowedAttrs(config, nodeName, node){\n\t if (typeof config.tags[nodeName] === 'function') {\n\t return config.tags[nodeName](node);\n\t } else {\n\t return config.tags[nodeName];\n\t }\n\t }\n\t\n\t function shouldRejectNode(node, allowedAttrs){\n\t if (typeof allowedAttrs === 'undefined') {\n\t return true;\n\t } else if (typeof allowedAttrs === 'boolean') {\n\t return !allowedAttrs;\n\t }\n\t\n\t return false;\n\t }\n\t\n\t function shouldRejectAttr(attr, allowedAttrs, node){\n\t var attrName = attr.name.toLowerCase();\n\t\n\t if (allowedAttrs === true){\n\t return false;\n\t } else if (typeof allowedAttrs[attrName] === 'function'){\n\t return !allowedAttrs[attrName](attr.value, node);\n\t } else if (typeof allowedAttrs[attrName] === 'undefined'){\n\t return true;\n\t } else if (allowedAttrs[attrName] === false) {\n\t return true;\n\t } else if (typeof allowedAttrs[attrName] === 'string') {\n\t return (allowedAttrs[attrName] !== attr.value);\n\t }\n\t\n\t return false;\n\t }\n\t\n\t return HTMLJanitor;\n\t\n\t}));\n\n\n/***/ },\n/* 19 */\n/***/ function(module, exports) {\n\n\t\"use strict\";\n\t\n\t/**\r\n\t * Codex Editor Listeners module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.0\r\n\t */\n\t\n\t/**\r\n\t * Module-decorator for event listeners assignment\r\n\t */\n\tmodule.exports = function (listeners) {\n\t\n\t var allListeners = [];\n\t\n\t /**\r\n\t * Search methods\r\n\t *\r\n\t * byElement, byType and byHandler returns array of suitable listeners\r\n\t * one and all takes element, eventType, and handler and returns first (all) suitable listener\r\n\t *\r\n\t */\n\t listeners.search = function () {\n\t\n\t var byElement = function byElement(element, context) {\n\t\n\t var listenersOnElement = [];\n\t\n\t context = context || allListeners;\n\t\n\t for (var i = 0; i < context.length; i++) {\n\t\n\t var listener = context[i];\n\t\n\t if (listener.element === element) {\n\t\n\t listenersOnElement.push(listener);\n\t }\n\t }\n\t\n\t return listenersOnElement;\n\t };\n\t\n\t var byType = function byType(eventType, context) {\n\t\n\t var listenersWithType = [];\n\t\n\t context = context || allListeners;\n\t\n\t for (var i = 0; i < context.length; i++) {\n\t\n\t var listener = context[i];\n\t\n\t if (listener.type === eventType) {\n\t\n\t listenersWithType.push(listener);\n\t }\n\t }\n\t\n\t return listenersWithType;\n\t };\n\t\n\t var byHandler = function byHandler(handler, context) {\n\t\n\t var listenersWithHandler = [];\n\t\n\t context = context || allListeners;\n\t\n\t for (var i = 0; i < context.length; i++) {\n\t\n\t var listener = context[i];\n\t\n\t if (listener.handler === handler) {\n\t\n\t listenersWithHandler.push(listener);\n\t }\n\t }\n\t\n\t return listenersWithHandler;\n\t };\n\t\n\t var one = function one(element, eventType, handler) {\n\t\n\t var result = allListeners;\n\t\n\t if (element) result = byElement(element, result);\n\t\n\t if (eventType) result = byType(eventType, result);\n\t\n\t if (handler) result = byHandler(handler, result);\n\t\n\t return result[0];\n\t };\n\t\n\t var all = function all(element, eventType, handler) {\n\t\n\t var result = allListeners;\n\t\n\t if (element) result = byElement(element, result);\n\t\n\t if (eventType) result = byType(eventType, result);\n\t\n\t if (handler) result = byHandler(handler, result);\n\t\n\t return result;\n\t };\n\t\n\t return {\n\t byElement: byElement,\n\t byType: byType,\n\t byHandler: byHandler,\n\t one: one,\n\t all: all\n\t };\n\t }();\n\t\n\t listeners.add = function (element, eventType, handler, isCapture) {\n\t\n\t element.addEventListener(eventType, handler, isCapture);\n\t\n\t var data = {\n\t element: element,\n\t type: eventType,\n\t handler: handler\n\t };\n\t\n\t var alreadyAddedListener = listeners.search.one(element, eventType, handler);\n\t\n\t if (!alreadyAddedListener) {\n\t\n\t allListeners.push(data);\n\t }\n\t };\n\t\n\t listeners.remove = function (element, eventType, handler) {\n\t\n\t element.removeEventListener(eventType, handler);\n\t\n\t var existingListeners = listeners.search.all(element, eventType, handler);\n\t\n\t for (var i = 0; i < existingListeners.length; i++) {\n\t\n\t var index = allListeners.indexOf(existingListeners[i]);\n\t\n\t if (index > 0) {\n\t\n\t allListeners.splice(index, 1);\n\t }\n\t }\n\t };\n\t\n\t listeners.removeAll = function () {\n\t\n\t allListeners.map(function (current) {\n\t\n\t listeners.remove(current.element, current.type, current.handler);\n\t });\n\t };\n\t\n\t listeners.get = function (element, eventType, handler) {\n\t\n\t return listeners.search.all(element, eventType, handler);\n\t };\n\t\n\t return listeners;\n\t}({});\n\n/***/ },\n/* 20 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\tvar _typeof = typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; };\n\t\n\t/**\r\n\t * Codex Editor Destroyer module\r\n\t *\r\n\t * @auhor Codex Team\r\n\t * @version 1.0\r\n\t */\n\t\n\tmodule.exports = function (destroyer) {\n\t\n\t var editor = codex.editor;\n\t\n\t destroyer.removeNodes = function () {\n\t\n\t editor.nodes.wrapper.remove();\n\t editor.nodes.notifications.remove();\n\t };\n\t\n\t destroyer.destroyPlugins = function () {\n\t\n\t for (var tool in editor.tools) {\n\t\n\t if (typeof editor.tools[tool].destroy === 'function') {\n\t\n\t editor.tools[tool].destroy();\n\t }\n\t }\n\t };\n\t\n\t destroyer.destroyScripts = function () {\n\t\n\t var scripts = document.getElementsByTagName('SCRIPT');\n\t\n\t for (var i = 0; i < scripts.length; i++) {\n\t\n\t if (scripts[i].id.indexOf(editor.scriptPrefix) + 1) {\n\t\n\t scripts[i].remove();\n\t i--;\n\t }\n\t }\n\t };\n\t\n\t /**\r\n\t * Delete editor data from webpage.\r\n\t * You should send settings argument with boolean flags:\r\n\t * @param settings.ui- remove redactor event listeners and DOM nodes\r\n\t * @param settings.scripts - remove redactor scripts from DOM\r\n\t * @param settings.plugins - remove plugin's objects\r\n\t * @param settings.core - remove editor core. You can remove core only if UI and scripts flags is true\r\n\t * }\r\n\t *\r\n\t */\n\t destroyer.destroy = function (settings) {\n\t\n\t if (!settings || (typeof settings === 'undefined' ? 'undefined' : _typeof(settings)) !== 'object') {\n\t\n\t return;\n\t }\n\t\n\t if (settings.ui) {\n\t\n\t destroyer.removeNodes();\n\t editor.listeners.removeAll();\n\t }\n\t\n\t if (settings.scripts) {\n\t\n\t destroyer.destroyScripts();\n\t }\n\t\n\t if (settings.plugins) {\n\t\n\t destroyer.destroyPlugins();\n\t }\n\t\n\t if (settings.ui && settings.scripts && settings.core) {\n\t\n\t delete codex.editor;\n\t }\n\t };\n\t\n\t return destroyer;\n\t}({});\n\n/***/ },\n/* 21 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\t\n\t/**\r\n\t * Codex Editor Paste module\r\n\t *\r\n\t * @author Codex Team\r\n\t * @version 1.1.1\r\n\t */\n\t\n\tmodule.exports = function (paste) {\n\t\n\t var editor = codex.editor;\n\t\n\t var patterns = [];\n\t\n\t paste.prepare = function () {\n\t\n\t var tools = editor.tools;\n\t\n\t for (var tool in tools) {\n\t\n\t if (!tools[tool].renderOnPastePatterns || !Array.isArray(tools[tool].renderOnPastePatterns)) {\n\t\n\t continue;\n\t }\n\t\n\t tools[tool].renderOnPastePatterns.map(function (pattern) {\n\t\n\t patterns.push(pattern);\n\t });\n\t }\n\t\n\t return Promise.resolve();\n\t };\n\t\n\t /**\r\n\t * Saves data\r\n\t * @param event\r\n\t */\n\t paste.pasted = function (event) {\n\t\n\t var clipBoardData = event.clipboardData || window.clipboardData,\n\t content = clipBoardData.getData('Text');\n\t\n\t var result = analize(content);\n\t\n\t if (result) {\n\t\n\t event.preventDefault();\n\t event.stopImmediatePropagation();\n\t }\n\t\n\t return result;\n\t };\n\t\n\t /**\r\n\t * Analizes pated string and calls necessary method\r\n\t */\n\t\n\t var analize = function analize(string) {\n\t\n\t var result = false,\n\t content = editor.content.currentNode,\n\t plugin = content.dataset.tool;\n\t\n\t patterns.map(function (pattern) {\n\t\n\t var execArray = pattern.regex.exec(string),\n\t match = execArray && execArray[0];\n\t\n\t if (match && match === string.trim()) {\n\t\n\t /** current block is not empty */\n\t if (content.textContent.trim() && plugin == editor.settings.initialBlockPlugin) {\n\t\n\t pasteToNewBlock_();\n\t }\n\t\n\t pattern.callback(string, pattern);\n\t result = true;\n\t }\n\t });\n\t\n\t return result;\n\t };\n\t\n\t var pasteToNewBlock_ = function pasteToNewBlock_() {\n\t\n\t /** Create new initial block */\n\t editor.content.insertBlock({\n\t\n\t type: editor.settings.initialBlockPlugin,\n\t block: editor.tools[editor.settings.initialBlockPlugin].render({\n\t text: ''\n\t })\n\t\n\t }, false);\n\t };\n\t\n\t /**\r\n\t * This method prevents default behaviour.\r\n\t *\r\n\t * @param {Object} event\r\n\t * @protected\r\n\t *\r\n\t * @description We get from clipboard pasted data, sanitize, make a fragment that contains of this sanitized nodes.\r\n\t * Firstly, we need to memorize the caret position. We can do that by getting the range of selection.\r\n\t * After all, we insert clear fragment into caret placed position. Then, we should move the caret to the last node\r\n\t */\n\t paste.blockPasteCallback = function (event) {\n\t\n\t if (!needsToHandlePasteEvent(event.target)) {\n\t\n\t return;\n\t }\n\t\n\t /** Prevent default behaviour */\n\t event.preventDefault();\n\t\n\t /** get html pasted data - dirty data */\n\t var htmlData = event.clipboardData.getData('text/html'),\n\t plainData = event.clipboardData.getData('text/plain');\n\t\n\t /** Temporary DIV that is used to work with text's paragraphs as DOM-elements*/\n\t var paragraphs = editor.draw.node('DIV', '', {}),\n\t cleanData,\n\t wrappedData;\n\t\n\t /** Create fragment, that we paste to range after proccesing */\n\t cleanData = editor.sanitizer.clean(htmlData);\n\t\n\t /**\r\n\t * We wrap pasted text with

    tags to split it logically\r\n\t * @type {string}\r\n\t */\n\t wrappedData = editor.content.wrapTextWithParagraphs(cleanData, plainData);\n\t paragraphs.innerHTML = wrappedData;\n\t\n\t /**\r\n\t * If there only one paragraph, just insert in at the caret location\r\n\t */\n\t if (paragraphs.childNodes.length == 1) {\n\t\n\t emulateUserAgentBehaviour(paragraphs.firstChild);\n\t return;\n\t }\n\t\n\t insertPastedParagraphs(paragraphs.childNodes);\n\t };\n\t\n\t /**\r\n\t * Checks if we should handle paste event on block\r\n\t * @param block\r\n\t *\r\n\t * @return {boolean}\r\n\t */\n\t var needsToHandlePasteEvent = function needsToHandlePasteEvent(block) {\n\t\n\t /** If area is input or textarea then allow default behaviour */\n\t if (editor.core.isNativeInput(block)) {\n\t\n\t return false;\n\t }\n\t\n\t var editableParent = editor.content.getEditableParent(block);\n\t\n\t /** Allow paste when event target placed in Editable element */\n\t if (!editableParent) {\n\t\n\t return false;\n\t }\n\t\n\t return true;\n\t };\n\t\n\t /**\r\n\t * Inserts new initial plugin blocks with data in paragraphs\r\n\t *\r\n\t * @param {Array} paragraphs - array of paragraphs (

    ) whit content, that should be inserted\r\n\t */\n\t var insertPastedParagraphs = function insertPastedParagraphs(paragraphs) {\n\t\n\t var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\n\t\n\t paragraphs.forEach(function (paragraph, index) {\n\t\n\t /** Don't allow empty paragraphs */\n\t if (editor.core.isBlockEmpty(paragraph)) {\n\t\n\t return;\n\t }\n\t\n\t /**\r\n\t * If there was no data in working node, replace it with first paragraph of pasted text\r\n\t */\n\t if (index == 0 && editor.core.isBlockEmpty(editor.content.currentNode)) {\n\t\n\t editor.content.currentNode.remove();\n\t editor.caret.setToPreviousBlock(editor.caret.inputIndex);\n\t }\n\t\n\t editor.content.insertBlock({\n\t type: NEW_BLOCK_TYPE,\n\t block: editor.tools[NEW_BLOCK_TYPE].render({\n\t text: paragraph.innerHTML\n\t })\n\t });\n\t\n\t editor.caret.inputIndex++;\n\t });\n\t\n\t editor.caret.setToPreviousBlock(editor.caret.getCurrentInputIndex() + 1);\n\t };\n\t\n\t /**\r\n\t * Inserts node content at the caret position\r\n\t *\r\n\t * @param {Node} node - DOM node (could be DocumentFragment), that should be inserted at the caret location\r\n\t */\n\t var emulateUserAgentBehaviour = function emulateUserAgentBehaviour(node) {\n\t\n\t var newNode;\n\t\n\t if (node.childElementCount) {\n\t\n\t newNode = document.createDocumentFragment();\n\t\n\t node.childNodes.forEach(function (current) {\n\t\n\t if (!editor.core.isDomNode(current) && current.data.trim() === '') {\n\t\n\t return;\n\t }\n\t\n\t newNode.appendChild(current.cloneNode(true));\n\t });\n\t } else {\n\t\n\t newNode = document.createTextNode(node.textContent);\n\t }\n\t\n\t editor.caret.insertNode(newNode);\n\t };\n\t\n\t return paste;\n\t}({});\n\n/***/ }\n/******/ ]);\n\n\n// WEBPACK FOOTER //\n// codex-editor.js"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(0);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 0918e0998f907537cc62","/**\r\n *\r\n * Codex Editor\r\n *\r\n * @author Codex Team\r\n */\r\n\r\nmodule.exports = (function (editor) {\r\n\r\n 'use strict';\r\n\r\n editor.version = VERSION;\r\n editor.scriptPrefix = 'cdx-script-';\r\n\r\n var init = function () {\r\n\r\n editor.core = require('./modules/core');\r\n editor.tools = require('./modules/tools');\r\n editor.ui = require('./modules/ui');\r\n editor.transport = require('./modules/transport');\r\n editor.renderer = require('./modules/renderer');\r\n editor.saver = require('./modules/saver');\r\n editor.content = require('./modules/content');\r\n editor.toolbar = require('./modules/toolbar/toolbar');\r\n editor.callback = require('./modules/callbacks');\r\n editor.draw = require('./modules/draw');\r\n editor.caret = require('./modules/caret');\r\n editor.notifications = require('./modules/notifications');\r\n editor.parser = require('./modules/parser');\r\n editor.sanitizer = require('./modules/sanitizer');\r\n editor.listeners = require('./modules/listeners');\r\n editor.destroyer = require('./modules/destroyer');\r\n editor.paste = require('./modules/paste');\r\n\r\n };\r\n\r\n /**\r\n * @public\r\n * holds initial settings\r\n */\r\n editor.settings = {\r\n tools : ['paragraph', 'header', 'picture', 'list', 'quote', 'code', 'twitter', 'instagram', 'smile'],\r\n holderId : 'codex-editor',\r\n\r\n // Type of block showing on empty editor\r\n initialBlockPlugin: 'paragraph'\r\n };\r\n\r\n /**\r\n * public\r\n *\r\n * Static nodes\r\n */\r\n editor.nodes = {\r\n holder : null,\r\n wrapper : null,\r\n toolbar : null,\r\n inlineToolbar : {\r\n wrapper : null,\r\n buttons : null,\r\n actions : null\r\n },\r\n toolbox : null,\r\n notifications : null,\r\n plusButton : null,\r\n showSettingsButton: null,\r\n showTrashButton : null,\r\n blockSettings : null,\r\n pluginSettings : null,\r\n defaultSettings : null,\r\n toolbarButtons : {}, // { type : DomEl, ... }\r\n redactor : null\r\n };\r\n\r\n /**\r\n * @public\r\n *\r\n * Output state\r\n */\r\n editor.state = {\r\n jsonOutput : [],\r\n blocks : [],\r\n inputs : []\r\n };\r\n\r\n /**\r\n * @public\r\n * Editor plugins\r\n */\r\n editor.tools = {};\r\n\r\n /**\r\n * Initialization\r\n * @uses Promise cEditor.core.prepare\r\n * @param {Object} userSettings\r\n * @param {Array} userSettings.tools list of plugins\r\n * @param {String} userSettings.holderId Element's id to append editor\r\n *\r\n * Load user defined tools\r\n * Tools must contain this important objects :\r\n * @param {String} type - this is a type of plugin. It can be used as plugin name\r\n * @param {String} iconClassname - this a icon in toolbar\r\n * @param {Object} make - what should plugin do, when it is clicked\r\n * @param {Object} appendCallback - callback after clicking\r\n * @param {Element} settings - what settings does it have\r\n * @param {Object} render - plugin get JSON, and should return HTML\r\n * @param {Object} save - plugin gets HTML content, returns JSON\r\n * @param {Boolean} displayInToolbox - will be displayed in toolbox. Default value is TRUE\r\n * @param {Boolean} enableLineBreaks - inserts new block or break lines. Default value is FALSE\r\n *\r\n * @example\r\n * - type : 'header',\r\n * - iconClassname : 'ce-icon-header',\r\n * - make : headerTool.make,\r\n * - appendCallback : headerTool.appendCallback,\r\n * - settings : headerTool.makeSettings(),\r\n * - render : headerTool.render,\r\n * - save : headerTool.save,\r\n * - displayInToolbox : true,\r\n * - enableLineBreaks : false\r\n */\r\n editor.start = function (userSettings) {\r\n\r\n init();\r\n\r\n editor.core.prepare(userSettings)\r\n\r\n // If all ok, make UI, bind events and parse initial-content\r\n .then(editor.ui.prepare)\r\n .then(editor.tools.prepare)\r\n .then(editor.sanitizer.prepare)\r\n .then(editor.paste.prepare)\r\n .then(editor.transport.prepare)\r\n .then(editor.renderer.makeBlocksFromData)\r\n .then(editor.ui.saveInputs)\r\n .catch(function (error) {\r\n\r\n editor.core.log('Initialization failed with error: %o', 'warn', error);\r\n\r\n });\r\n\r\n };\r\n\r\n return editor;\r\n\r\n})({});\r\n\n\n\n// WEBPACK FOOTER //\n// ./codex.js","/**\r\n * Codex Editor Core\r\n *\r\n * @author Codex Team\r\n * @version 1.1.3\r\n */\r\n\r\nmodule.exports = (function (core) {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * @public\r\n *\r\n * Editor preparing method\r\n * @return Promise\r\n */\r\n core.prepare = function (userSettings) {\r\n\r\n return new Promise(function (resolve, reject) {\r\n\r\n if ( userSettings ) {\r\n\r\n editor.settings.tools = userSettings.tools || editor.settings.tools;\r\n\r\n }\r\n\r\n if (userSettings.data) {\r\n\r\n editor.state.blocks = userSettings.data;\r\n\r\n }\r\n\r\n if (userSettings.initialBlockPlugin) {\r\n\r\n editor.settings.initialBlockPlugin = userSettings.initialBlockPlugin;\r\n\r\n }\r\n\r\n if (userSettings.sanitizer) {\r\n\r\n editor.settings.sanitizer = userSettings.sanitizer;\r\n\r\n }\r\n\r\n editor.hideToolbar = userSettings.hideToolbar;\r\n\r\n editor.nodes.holder = document.getElementById(userSettings.holderId || editor.settings.holderId);\r\n\r\n if (typeof editor.nodes.holder === undefined || editor.nodes.holder === null) {\r\n\r\n reject(Error(\"Holder wasn't found by ID: #\" + userSettings.holderId));\r\n\r\n } else {\r\n\r\n resolve();\r\n\r\n }\r\n\r\n });\r\n\r\n };\r\n\r\n /**\r\n * Logging method\r\n * @param type = ['log', 'info', 'warn']\r\n */\r\n core.log = function (msg, type, arg) {\r\n\r\n type = type || 'log';\r\n\r\n if (!arg) {\r\n\r\n arg = msg || 'undefined';\r\n msg = '[codex-editor]: %o';\r\n\r\n } else {\r\n\r\n msg = '[codex-editor]: ' + msg;\r\n\r\n }\r\n\r\n try{\r\n\r\n if ( 'console' in window && window.console[ type ] ) {\r\n\r\n if ( arg ) window.console[ type ]( msg, arg );\r\n else window.console[ type ]( msg );\r\n\r\n }\r\n\r\n }catch(e) {}\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n *\r\n * Helper for insert one element after another\r\n */\r\n core.insertAfter = function (target, element) {\r\n\r\n target.parentNode.insertBefore(element, target.nextSibling);\r\n\r\n };\r\n\r\n /**\r\n * @const\r\n *\r\n * Readable DOM-node types map\r\n */\r\n core.nodeTypes = {\r\n TAG : 1,\r\n TEXT : 3,\r\n COMMENT : 8,\r\n DOCUMENT_FRAGMENT: 11\r\n };\r\n\r\n /**\r\n * @const\r\n * Readable keys map\r\n */\r\n core.keys = { BACKSPACE: 8, TAB: 9, ENTER: 13, SHIFT: 16, CTRL: 17, ALT: 18, ESC: 27, SPACE: 32, LEFT: 37, UP: 38, DOWN: 40, RIGHT: 39, DELETE: 46, META: 91 };\r\n\r\n /**\r\n * @protected\r\n *\r\n * Check object for DOM node\r\n */\r\n core.isDomNode = function (el) {\r\n\r\n return el && typeof el === 'object' && el.nodeType && el.nodeType == this.nodeTypes.TAG;\r\n\r\n };\r\n\r\n /**\r\n * Checks passed object for emptiness\r\n * @require ES5 - Object.keys\r\n * @param {object}\r\n */\r\n core.isEmpty = function ( obj ) {\r\n\r\n return Object.keys(obj).length === 0;\r\n\r\n };\r\n\r\n /**\r\n * Native Ajax\r\n * @param {String} settings.url - request URL\r\n * @param {function} settings.beforeSend - returned value will be passed as context to the Success, Error and Progress callbacks\r\n * @param {function} settings.success\r\n * @param {function} settings.progress\r\n */\r\n core.ajax = function (settings) {\r\n\r\n if (!settings || !settings.url) {\r\n\r\n return;\r\n\r\n }\r\n\r\n var XMLHTTP = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'),\r\n encodedString,\r\n isFormData,\r\n prop;\r\n\r\n\r\n settings.async = true;\r\n settings.type = settings.type || 'GET';\r\n settings.data = settings.data || '';\r\n settings['content-type'] = settings['content-type'] || 'application/json; charset=utf-8';\r\n\r\n if (settings.type == 'GET' && settings.data) {\r\n\r\n settings.url = /\\?/.test(settings.url) ? settings.url + '&' + settings.data : settings.url + '?' + settings.data;\r\n\r\n } else {\r\n\r\n encodedString = '';\r\n for(prop in settings.data) {\r\n\r\n encodedString += (prop + '=' + encodeURIComponent(settings.data[prop]) + '&');\r\n\r\n }\r\n\r\n }\r\n\r\n if (settings.withCredentials) {\r\n\r\n XMLHTTP.withCredentials = true;\r\n\r\n }\r\n\r\n /**\r\n * Value returned in beforeSend funtion will be passed as context to the other response callbacks\r\n * If beforeSend returns false, AJAX will be blocked\r\n */\r\n let responseContext,\r\n beforeSendResult;\r\n\r\n if (typeof settings.beforeSend === 'function') {\r\n\r\n beforeSendResult = settings.beforeSend.call();\r\n\r\n if (beforeSendResult === false) {\r\n\r\n return;\r\n\r\n }\r\n\r\n }\r\n\r\n XMLHTTP.open( settings.type, settings.url, settings.async );\r\n\r\n /**\r\n * If we send FormData, we need no content-type header\r\n */\r\n isFormData = isFormData_(settings.data);\r\n\r\n if (!isFormData) {\r\n\r\n if (settings.type !== 'POST') {\r\n\r\n XMLHTTP.setRequestHeader('Content-type', settings['content-type']);\r\n\r\n } else {\r\n\r\n XMLHTTP.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');\r\n\r\n }\r\n\r\n }\r\n\r\n XMLHTTP.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\r\n\r\n responseContext = beforeSendResult || XMLHTTP;\r\n\r\n if (typeof settings.progress === 'function') {\r\n\r\n XMLHTTP.upload.onprogress = settings.progress.bind(responseContext);\r\n\r\n }\r\n\r\n XMLHTTP.onreadystatechange = function () {\r\n\r\n if (XMLHTTP.readyState === 4) {\r\n\r\n if (XMLHTTP.status === 200) {\r\n\r\n if (typeof settings.success === 'function') {\r\n\r\n settings.success.call(responseContext, XMLHTTP.responseText);\r\n\r\n }\r\n\r\n } else {\r\n\r\n if (typeof settings.error === 'function') {\r\n\r\n settings.error.call(responseContext, XMLHTTP.responseText, XMLHTTP.status);\r\n\r\n }\r\n\r\n }\r\n\r\n }\r\n\r\n };\r\n\r\n if (isFormData) {\r\n\r\n // Sending FormData\r\n XMLHTTP.send(settings.data);\r\n\r\n } else {\r\n\r\n // POST requests\r\n XMLHTTP.send(encodedString);\r\n\r\n }\r\n\r\n return XMLHTTP;\r\n\r\n };\r\n\r\n /**\r\n * Appends script to head of document\r\n * @return Promise\r\n */\r\n core.importScript = function (scriptPath, instanceName) {\r\n\r\n return new Promise(function (resolve, reject) {\r\n\r\n let script;\r\n\r\n /** Script is already loaded */\r\n if ( !instanceName ) {\r\n\r\n reject('Instance name is missed');\r\n\r\n } else if ( document.getElementById(editor.scriptPrefix + instanceName) ) {\r\n\r\n resolve(scriptPath);\r\n\r\n }\r\n\r\n script = document.createElement('SCRIPT');\r\n script.async = true;\r\n script.defer = true;\r\n script.id = editor.scriptPrefix + instanceName;\r\n\r\n script.onload = function () {\r\n\r\n resolve(scriptPath);\r\n\r\n };\r\n\r\n script.onerror = function () {\r\n\r\n reject(scriptPath);\r\n\r\n };\r\n\r\n script.src = scriptPath;\r\n document.head.appendChild(script);\r\n\r\n });\r\n\r\n };\r\n\r\n /**\r\n * Function for checking is it FormData object to send.\r\n * @param {Object} object to check\r\n * @return boolean\r\n */\r\n var isFormData_ = function (object) {\r\n\r\n return object instanceof FormData;\r\n\r\n };\r\n\r\n /**\r\n * Check block\r\n * @param target\r\n * @description Checks target is it native input\r\n */\r\n core.isNativeInput = function (target) {\r\n\r\n var nativeInputAreas = ['INPUT', 'TEXTAREA'];\r\n\r\n return nativeInputAreas.indexOf(target.tagName) != -1;\r\n\r\n };\r\n\r\n /**\r\n * Check if block is empty\r\n * We should check block textContent, child native inputs and some exceptions like IMG and IFRAME\r\n *\r\n * @param block\r\n * @returns {boolean}\r\n */\r\n core.isBlockEmpty = function (block) {\r\n\r\n const EXCEPTION_TAGS = ['IMG', 'IFRAME'];\r\n\r\n var nativeInputs = block.querySelectorAll('textarea, input'),\r\n nativeInputsAreEmpty = true,\r\n textContentIsEmpty = !block.textContent.trim();\r\n\r\n Array.prototype.forEach.call(nativeInputs, function (input) {\r\n\r\n if (input.type == 'textarea' || input.type == 'text') {\r\n\r\n nativeInputsAreEmpty = nativeInputsAreEmpty && !input.value.trim();\r\n\r\n }\r\n\r\n });\r\n\r\n return textContentIsEmpty && nativeInputsAreEmpty && !(EXCEPTION_TAGS.indexOf(block.tagName) + 1);\r\n\r\n };\r\n\r\n\r\n return core;\r\n\r\n})({});\r\n\n\n\n// WEBPACK FOOTER //\n// ./modules/core.js","/**\r\n* Module working with plugins\r\n*/\r\nmodule.exports = (function () {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * Initialize plugins before using\r\n * Ex. Load scripts or call some internal methods\r\n * @return Promise\r\n */\r\n function prepare() {\r\n\r\n return new Promise(function (resolve_, reject_) {\r\n\r\n Promise.resolve()\r\n\r\n /**\r\n * Compose a sequence of plugins that requires preparation\r\n */\r\n .then(function () {\r\n\r\n let pluginsRequiresPreparation = [],\r\n allPlugins = editor.tools;\r\n\r\n for ( let pluginName in allPlugins ) {\r\n\r\n let plugin = allPlugins[pluginName];\r\n\r\n if (plugin.prepare && typeof plugin.prepare != 'function' || !plugin.prepare) {\r\n\r\n continue;\r\n\r\n }\r\n\r\n pluginsRequiresPreparation.push(plugin);\r\n\r\n }\r\n\r\n /**\r\n * If no one passed plugins requires preparation, finish prepare() and go ahead\r\n */\r\n if (!pluginsRequiresPreparation.length) {\r\n\r\n resolve_();\r\n\r\n }\r\n\r\n return pluginsRequiresPreparation;\r\n\r\n })\r\n\r\n /** Wait plugins while they prepares */\r\n .then(waitAllPluginsPreparation_)\r\n\r\n .then(function () {\r\n\r\n editor.core.log('Plugins loaded', 'info');\r\n resolve_();\r\n\r\n }).catch(function (error) {\r\n\r\n reject_(error);\r\n\r\n });\r\n\r\n });\r\n\r\n }\r\n\r\n /**\r\n * @param {array} plugins - list of tools that requires preparation\r\n * @return {Promise} resolved while all plugins will be ready or failed\r\n */\r\n function waitAllPluginsPreparation_(plugins) {\r\n\r\n /**\r\n * @calls allPluginsProcessed__ when all plugins prepared or failed\r\n */\r\n return new Promise (function (allPluginsProcessed__) {\r\n\r\n /**\r\n * pluck each element from queue\r\n * First, send resolved Promise as previous value\r\n * Each plugins \"prepare\" method returns a Promise, that's why\r\n * reduce current element will not be able to continue while can't get\r\n * a resolved Promise\r\n *\r\n * If last plugin is \"prepared\" then go to the next stage of initialization\r\n */\r\n plugins.reduce(function (previousValue, plugin, iteration) {\r\n\r\n return previousValue.then(function () {\r\n\r\n /**\r\n * Wait till plugins prepared\r\n * @calls pluginIsReady__ when plugin is ready or failed\r\n */\r\n return new Promise ( function (pluginIsReady__) {\r\n\r\n callPluginsPrepareMethod_( plugin )\r\n\r\n .then( pluginIsReady__ )\r\n .then( function () {\r\n\r\n plugin.available = true;\r\n\r\n })\r\n\r\n .catch(function (error) {\r\n\r\n editor.core.log(`Plugin «${plugin.type}» was not loaded. Preparation failed because %o`, 'warn', error);\r\n plugin.available = false;\r\n plugin.loadingMessage = error;\r\n\r\n /** Go ahead even some plugin has problems */\r\n pluginIsReady__();\r\n\r\n })\r\n\r\n .then(function () {\r\n\r\n /** If last plugin has problems then just ignore and continue */\r\n if (iteration == plugins.length - 1) {\r\n\r\n allPluginsProcessed__();\r\n\r\n }\r\n\r\n });\r\n\r\n });\r\n\r\n });\r\n\r\n }, Promise.resolve() );\r\n\r\n });\r\n\r\n }\r\n\r\n var callPluginsPrepareMethod_ = function (plugin) {\r\n\r\n return plugin.prepare( plugin.config || {} );\r\n\r\n };\r\n\r\n return {\r\n prepare: prepare\r\n };\r\n\r\n}());\n\n\n// WEBPACK FOOTER //\n// ./modules/tools.js","/**\r\n * Codex Editor UI module\r\n *\r\n * @author Codex Team\r\n * @version 1.2.0\r\n */\r\n\r\nmodule.exports = (function (ui) {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * Basic editor classnames\r\n */\r\n ui.className = {\r\n\r\n /**\r\n * @const {string} BLOCK_CLASSNAME - redactor blocks name\r\n */\r\n BLOCK_CLASSNAME : 'ce-block',\r\n\r\n /**\r\n * @const {String} wrapper for plugins content\r\n */\r\n BLOCK_CONTENT : 'ce-block__content',\r\n\r\n /**\r\n * @const {String} BLOCK_STRETCHED - makes block stretched\r\n */\r\n BLOCK_STRETCHED : 'ce-block--stretched',\r\n\r\n /**\r\n * @const {String} BLOCK_HIGHLIGHTED - adds background\r\n */\r\n BLOCK_HIGHLIGHTED : 'ce-block--focused',\r\n\r\n /**\r\n * @const {String} - for all default settings\r\n */\r\n SETTINGS_ITEM : 'ce-settings__item'\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n *\r\n * Making main interface\r\n */\r\n ui.prepare = function () {\r\n\r\n return new Promise(function (resolve) {\r\n\r\n let wrapper = editor.draw.wrapper(),\r\n redactor = editor.draw.redactor(),\r\n toolbar = makeToolBar_();\r\n\r\n wrapper.appendChild(toolbar);\r\n wrapper.appendChild(redactor);\r\n\r\n /** Save created ui-elements to static nodes state */\r\n editor.nodes.wrapper = wrapper;\r\n editor.nodes.redactor = redactor;\r\n\r\n /** Append editor wrapper with redactor zone into holder */\r\n editor.nodes.holder.appendChild(wrapper);\r\n\r\n resolve();\r\n\r\n })\r\n\r\n /** Add toolbox tools */\r\n .then(addTools_)\r\n\r\n /** Make container for inline toolbar */\r\n .then(makeInlineToolbar_)\r\n\r\n /** Add inline toolbar tools */\r\n .then(addInlineToolbarTools_)\r\n\r\n /** Draw wrapper for notifications */\r\n .then(makeNotificationHolder_)\r\n\r\n /** Add eventlisteners to redactor elements */\r\n .then(bindEvents_)\r\n\r\n .catch( function () {\r\n\r\n editor.core.log(\"Can't draw editor interface\");\r\n\r\n });\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n * Draws inline toolbar zone\r\n */\r\n var makeInlineToolbar_ = function () {\r\n\r\n var container = editor.draw.inlineToolbar();\r\n\r\n /** Append to redactor new inline block */\r\n editor.nodes.inlineToolbar.wrapper = container;\r\n\r\n /** Draw toolbar buttons */\r\n editor.nodes.inlineToolbar.buttons = editor.draw.inlineToolbarButtons();\r\n\r\n /** Buttons action or settings */\r\n editor.nodes.inlineToolbar.actions = editor.draw.inlineToolbarActions();\r\n\r\n /** Append to inline toolbar buttons as part of it */\r\n editor.nodes.inlineToolbar.wrapper.appendChild(editor.nodes.inlineToolbar.buttons);\r\n editor.nodes.inlineToolbar.wrapper.appendChild(editor.nodes.inlineToolbar.actions);\r\n\r\n editor.nodes.wrapper.appendChild(editor.nodes.inlineToolbar.wrapper);\r\n\r\n };\r\n\r\n var makeToolBar_ = function () {\r\n\r\n let toolbar = editor.draw.toolbar(),\r\n blockButtons = makeToolbarSettings_(),\r\n toolbarContent = makeToolbarContent_();\r\n\r\n /** Appending first-level block buttons */\r\n toolbar.appendChild(blockButtons);\r\n\r\n /** Append toolbarContent to toolbar */\r\n toolbar.appendChild(toolbarContent);\r\n\r\n /** Make toolbar global */\r\n editor.nodes.toolbar = toolbar;\r\n\r\n return toolbar;\r\n\r\n };\r\n\r\n var makeToolbarContent_ = function () {\r\n\r\n let toolbarContent = editor.draw.toolbarContent(),\r\n toolbox = editor.draw.toolbox(),\r\n plusButton = editor.draw.plusButton();\r\n\r\n /** Append plus button */\r\n toolbarContent.appendChild(plusButton);\r\n\r\n /** Appending toolbar tools */\r\n toolbarContent.appendChild(toolbox);\r\n\r\n /** Make Toolbox and plusButton global */\r\n editor.nodes.toolbox = toolbox;\r\n editor.nodes.plusButton = plusButton;\r\n\r\n return toolbarContent;\r\n\r\n };\r\n\r\n var makeToolbarSettings_ = function () {\r\n\r\n let blockSettings = editor.draw.blockSettings(),\r\n blockButtons = editor.draw.blockButtons(),\r\n defaultSettings = editor.draw.defaultSettings(),\r\n showSettingsButton = editor.draw.settingsButton(),\r\n showTrashButton = editor.toolbar.settings.makeRemoveBlockButton(),\r\n pluginSettings = editor.draw.pluginsSettings();\r\n\r\n /** Add default and plugins settings */\r\n blockSettings.appendChild(pluginSettings);\r\n blockSettings.appendChild(defaultSettings);\r\n\r\n /**\r\n * Make blocks buttons\r\n * This block contains settings button and remove block button\r\n */\r\n blockButtons.appendChild(showSettingsButton);\r\n blockButtons.appendChild(showTrashButton);\r\n blockButtons.appendChild(blockSettings);\r\n\r\n /** Make BlockSettings, PluginSettings, DefaultSettings global */\r\n editor.nodes.blockSettings = blockSettings;\r\n editor.nodes.pluginSettings = pluginSettings;\r\n editor.nodes.defaultSettings = defaultSettings;\r\n editor.nodes.showSettingsButton = showSettingsButton;\r\n editor.nodes.showTrashButton = showTrashButton;\r\n\r\n return blockButtons;\r\n\r\n };\r\n\r\n /** Draw notifications holder */\r\n var makeNotificationHolder_ = function () {\r\n\r\n /** Append block with notifications to the document */\r\n editor.nodes.notifications = editor.notifications.createHolder();\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n * Append tools passed in editor.tools\r\n */\r\n var addTools_ = function () {\r\n\r\n var tool,\r\n toolName,\r\n toolButton;\r\n\r\n for ( toolName in editor.settings.tools ) {\r\n\r\n tool = editor.settings.tools[toolName];\r\n\r\n editor.tools[toolName] = tool;\r\n\r\n if (!tool.iconClassname && tool.displayInToolbox) {\r\n\r\n editor.core.log('Toolbar icon classname missed. Tool %o skipped', 'warn', toolName);\r\n continue;\r\n\r\n }\r\n\r\n if (typeof tool.render != 'function') {\r\n\r\n editor.core.log('render method missed. Tool %o skipped', 'warn', toolName);\r\n continue;\r\n\r\n }\r\n\r\n if (!tool.displayInToolbox) {\r\n\r\n continue;\r\n\r\n } else {\r\n\r\n /** if tools is for toolbox */\r\n toolButton = editor.draw.toolbarButton(toolName, tool.iconClassname);\r\n\r\n editor.nodes.toolbox.appendChild(toolButton);\r\n\r\n editor.nodes.toolbarButtons[toolName] = toolButton;\r\n\r\n }\r\n\r\n }\r\n\r\n };\r\n\r\n var addInlineToolbarTools_ = function () {\r\n\r\n var tools = {\r\n\r\n bold: {\r\n icon : 'ce-icon-bold',\r\n command : 'bold'\r\n },\r\n\r\n italic: {\r\n icon : 'ce-icon-italic',\r\n command : 'italic'\r\n },\r\n\r\n underline: {\r\n icon : 'ce-icon-underline',\r\n command : 'underline'\r\n },\r\n\r\n link: {\r\n icon : 'ce-icon-link',\r\n command : 'createLink'\r\n }\r\n };\r\n\r\n var toolButton,\r\n tool;\r\n\r\n for(var name in tools) {\r\n\r\n tool = tools[name];\r\n\r\n toolButton = editor.draw.toolbarButtonInline(name, tool.icon);\r\n\r\n editor.nodes.inlineToolbar.buttons.appendChild(toolButton);\r\n /**\r\n * Add callbacks to this buttons\r\n */\r\n editor.ui.setInlineToolbarButtonBehaviour(toolButton, tool.command);\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n * Bind editor UI events\r\n */\r\n var bindEvents_ = function () {\r\n\r\n editor.core.log('ui.bindEvents fired', 'info');\r\n\r\n // window.addEventListener('error', function (errorMsg, url, lineNumber) {\r\n // editor.notifications.errorThrown(errorMsg, event);\r\n // }, false );\r\n\r\n /** All keydowns on Document */\r\n editor.listeners.add(document, 'keydown', editor.callback.globalKeydown, false);\r\n\r\n /** All keydowns on Redactor zone */\r\n editor.listeners.add(editor.nodes.redactor, 'keydown', editor.callback.redactorKeyDown, false);\r\n\r\n /** All keydowns on Document */\r\n editor.listeners.add(document, 'keyup', editor.callback.globalKeyup, false );\r\n\r\n /**\r\n * Mouse click to radactor\r\n */\r\n editor.listeners.add(editor.nodes.redactor, 'click', editor.callback.redactorClicked, false );\r\n\r\n /**\r\n * Clicks to the Plus button\r\n */\r\n editor.listeners.add(editor.nodes.plusButton, 'click', editor.callback.plusButtonClicked, false);\r\n\r\n /**\r\n * Clicks to SETTINGS button in toolbar\r\n */\r\n editor.listeners.add(editor.nodes.showSettingsButton, 'click', editor.callback.showSettingsButtonClicked, false );\r\n\r\n /** Bind click listeners on toolbar buttons */\r\n for (var button in editor.nodes.toolbarButtons) {\r\n\r\n editor.listeners.add(editor.nodes.toolbarButtons[button], 'click', editor.callback.toolbarButtonClicked, false);\r\n\r\n }\r\n\r\n };\r\n\r\n ui.addBlockHandlers = function (block) {\r\n\r\n if (!block) return;\r\n\r\n /**\r\n * Block keydowns\r\n */\r\n editor.listeners.add(block, 'keydown', editor.callback.blockKeydown, false);\r\n\r\n /**\r\n * Pasting content from another source\r\n * We have two type of sanitization\r\n * First - uses deep-first search algorithm to get sub nodes,\r\n * sanitizes whole Block_content and replaces cleared nodes\r\n * This method is deprecated\r\n * Method is used in editor.callback.blockPaste(event)\r\n *\r\n * Secont - uses Mutation observer.\r\n * Observer \"observe\" DOM changes and send changings to callback.\r\n * Callback gets changed node, not whole Block_content.\r\n * Inserted or changed node, which we've gotten have been cleared and replaced with diry node\r\n *\r\n * Method is used in editor.callback.blockPasteViaSanitize(event)\r\n *\r\n * @uses html-janitor\r\n * @example editor.callback.blockPasteViaSanitize(event), the second method.\r\n *\r\n */\r\n editor.listeners.add(block, 'paste', editor.paste.blockPasteCallback, false);\r\n\r\n editor.listeners.add(block, 'mouseup', editor.toolbar.inline.show, false);\r\n\r\n };\r\n\r\n /** getting all contenteditable elements */\r\n ui.saveInputs = function () {\r\n\r\n var redactor = editor.nodes.redactor;\r\n\r\n editor.state.inputs = [];\r\n\r\n /** Save all inputs in global variable state */\r\n var inputs = redactor.querySelectorAll('[contenteditable], input, textarea');\r\n\r\n Array.prototype.map.call(inputs, function (current) {\r\n\r\n if (!current.type || current.type == 'text' || current.type == 'textarea') {\r\n\r\n editor.state.inputs.push(current);\r\n\r\n }\r\n\r\n });\r\n\r\n };\r\n\r\n /**\r\n * Adds first initial block on empty redactor\r\n */\r\n ui.addInitialBlock = function () {\r\n\r\n var initialBlockType = editor.settings.initialBlockPlugin,\r\n initialBlock;\r\n\r\n if ( !editor.tools[initialBlockType] ) {\r\n\r\n editor.core.log('Plugin %o was not implemented and can\\'t be used as initial block', 'warn', initialBlockType);\r\n return;\r\n\r\n }\r\n\r\n initialBlock = editor.tools[initialBlockType].render();\r\n\r\n initialBlock.setAttribute('data-placeholder', 'Расскажите свою историю...');\r\n\r\n editor.content.insertBlock({\r\n type : initialBlockType,\r\n block : initialBlock\r\n });\r\n\r\n editor.content.workingNodeChanged(initialBlock);\r\n\r\n };\r\n\r\n ui.setInlineToolbarButtonBehaviour = function (button, type) {\r\n\r\n editor.listeners.add(button, 'mousedown', function (event) {\r\n\r\n editor.toolbar.inline.toolClicked(event, type);\r\n\r\n }, false);\r\n\r\n };\r\n\r\n return ui;\r\n\r\n})({});\r\n\n\n\n// WEBPACK FOOTER //\n// ./modules/ui.js","/**\r\n *\r\n * Codex.Editor Transport Module\r\n *\r\n * @copyright 2017 Codex-Team\r\n * @version 1.2.0\r\n */\r\n\r\nmodule.exports = (function (transport) {\r\n\r\n let editor = codex.editor;\r\n\r\n\r\n /**\r\n * @private {Object} current XmlHttpRequest instance\r\n */\r\n var currentRequest = null;\r\n\r\n\r\n /**\r\n * @type {null} | {DOMElement} input - keeps input element in memory\r\n */\r\n transport.input = null;\r\n\r\n /**\r\n * @property {Object} arguments - keep plugin settings and defined callbacks\r\n */\r\n transport.arguments = null;\r\n\r\n /**\r\n * Prepares input element where will be files\r\n */\r\n transport.prepare = function () {\r\n\r\n let input = editor.draw.node( 'INPUT', '', { type : 'file' } );\r\n\r\n editor.listeners.add(input, 'change', editor.transport.fileSelected);\r\n editor.transport.input = input;\r\n\r\n };\r\n\r\n /** Clear input when files is uploaded */\r\n transport.clearInput = function () {\r\n\r\n /** Remove old input */\r\n transport.input = null;\r\n\r\n /** Prepare new one */\r\n transport.prepare();\r\n\r\n };\r\n\r\n /**\r\n * Callback for file selection\r\n * @param {Event} event\r\n */\r\n transport.fileSelected = function () {\r\n\r\n var input = this,\r\n i,\r\n files = input.files,\r\n formData = new FormData();\r\n\r\n if (editor.transport.arguments.multiple === true) {\r\n\r\n for ( i = 0; i < files.length; i++) {\r\n\r\n formData.append('files[]', files[i], files[i].name);\r\n\r\n }\r\n\r\n } else {\r\n\r\n formData.append('files', files[0], files[0].name);\r\n\r\n }\r\n\r\n currentRequest = editor.core.ajax({\r\n type : 'POST',\r\n data : formData,\r\n url : editor.transport.arguments.url,\r\n beforeSend : editor.transport.arguments.beforeSend,\r\n success : editor.transport.arguments.success,\r\n error : editor.transport.arguments.error,\r\n progress : editor.transport.arguments.progress\r\n });\r\n\r\n /** Clear input */\r\n transport.clearInput();\r\n\r\n };\r\n\r\n /**\r\n * Use plugin callbacks\r\n * @protected\r\n *\r\n * @param {Object} args - can have :\r\n * @param {String} args.url - fetch URL\r\n * @param {Function} args.beforeSend - function calls before sending ajax\r\n * @param {Function} args.success - success callback\r\n * @param {Function} args.error - on error handler\r\n * @param {Function} args.progress - xhr onprogress handler\r\n * @param {Boolean} args.multiple - allow select several files\r\n * @param {String} args.accept - adds accept attribute\r\n */\r\n transport.selectAndUpload = function (args) {\r\n\r\n transport.arguments = args;\r\n\r\n if ( args.multiple === true) {\r\n\r\n transport.input.setAttribute('multiple', 'multiple');\r\n\r\n }\r\n\r\n if ( args.accept ) {\r\n\r\n transport.input.setAttribute('accept', args.accept);\r\n\r\n }\r\n\r\n transport.input.click();\r\n\r\n };\r\n\r\n transport.abort = function () {\r\n\r\n currentRequest.abort();\r\n\r\n currentRequest = null;\r\n\r\n };\r\n\r\n return transport;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/transport.js","/**\r\n * Codex Editor Renderer Module\r\n *\r\n * @author Codex Team\r\n * @version 1.0\r\n */\r\n\r\nmodule.exports = (function (renderer) {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * Asyncronously parses input JSON to redactor blocks\r\n */\r\n renderer.makeBlocksFromData = function () {\r\n\r\n /**\r\n * If redactor is empty, add first paragraph to start writing\r\n */\r\n if (editor.core.isEmpty(editor.state.blocks) || !editor.state.blocks.items.length) {\r\n\r\n editor.ui.addInitialBlock();\r\n return;\r\n\r\n }\r\n\r\n Promise.resolve()\r\n\r\n /** First, get JSON from state */\r\n .then(function () {\r\n\r\n return editor.state.blocks;\r\n\r\n })\r\n\r\n /** Then, start to iterate they */\r\n .then(editor.renderer.appendBlocks)\r\n\r\n /** Write log if something goes wrong */\r\n .catch(function (error) {\r\n\r\n editor.core.log('Error while parsing JSON: %o', 'error', error);\r\n\r\n });\r\n\r\n };\r\n\r\n /**\r\n * Parses JSON to blocks\r\n * @param {object} data\r\n * @return Primise -> nodeList\r\n */\r\n renderer.appendBlocks = function (data) {\r\n\r\n var blocks = data.items;\r\n\r\n /**\r\n * Sequence of one-by-one blocks appending\r\n * Uses to save blocks order after async-handler\r\n */\r\n var nodeSequence = Promise.resolve();\r\n\r\n for (var index = 0; index < blocks.length ; index++ ) {\r\n\r\n /** Add node to sequence at specified index */\r\n editor.renderer.appendNodeAtIndex(nodeSequence, blocks, index);\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Append node at specified index\r\n */\r\n renderer.appendNodeAtIndex = function (nodeSequence, blocks, index) {\r\n\r\n /** We need to append node to sequence */\r\n nodeSequence\r\n\r\n /** first, get node async-aware */\r\n .then(function () {\r\n\r\n return editor.renderer.getNodeAsync(blocks, index);\r\n\r\n })\r\n\r\n /**\r\n * second, compose editor-block from JSON object\r\n */\r\n .then(editor.renderer.createBlockFromData)\r\n\r\n /**\r\n * now insert block to redactor\r\n */\r\n .then(function (blockData) {\r\n\r\n /**\r\n * blockData has 'block', 'type' and 'stretched' information\r\n */\r\n editor.content.insertBlock(blockData);\r\n\r\n /** Pass created block to next step */\r\n return blockData.block;\r\n\r\n })\r\n\r\n /** Log if something wrong with node */\r\n .catch(function (error) {\r\n\r\n editor.core.log('Node skipped while parsing because %o', 'error', error);\r\n\r\n });\r\n\r\n };\r\n\r\n /**\r\n * Asynchronously returns block data from blocksList by index\r\n * @return Promise to node\r\n */\r\n renderer.getNodeAsync = function (blocksList, index) {\r\n\r\n return Promise.resolve().then(function () {\r\n\r\n return {\r\n tool : blocksList[index],\r\n position : index\r\n };\r\n\r\n });\r\n\r\n };\r\n\r\n /**\r\n * Creates editor block by JSON-data\r\n *\r\n * @uses render method of each plugin\r\n *\r\n * @param {Object} toolData.tool\r\n * { header : {\r\n * text: '',\r\n * type: 'H3', ...\r\n * }\r\n * }\r\n * @param {Number} toolData.position - index in input-blocks array\r\n * @return {Object} with type and Element\r\n */\r\n renderer.createBlockFromData = function ( toolData ) {\r\n\r\n /** New parser */\r\n var block,\r\n tool = toolData.tool,\r\n pluginName = tool.type;\r\n\r\n /** Get first key of object that stores plugin name */\r\n // for (var pluginName in blockData) break;\r\n\r\n /** Check for plugin existance */\r\n if (!editor.tools[pluginName]) {\r\n\r\n throw Error(`Plugin «${pluginName}» not found`);\r\n\r\n }\r\n\r\n /** Check for plugin having render method */\r\n if (typeof editor.tools[pluginName].render != 'function') {\r\n\r\n throw Error(`Plugin «${pluginName}» must have «render» method`);\r\n\r\n }\r\n\r\n if ( editor.tools[pluginName].available === false ) {\r\n\r\n block = editor.draw.unavailableBlock();\r\n\r\n block.innerHTML = editor.tools[pluginName].loadingMessage;\r\n\r\n /**\r\n * Saver will extract data from initial block data by position in array\r\n */\r\n block.dataset.inputPosition = toolData.position;\r\n\r\n } else {\r\n\r\n /** New Parser */\r\n block = editor.tools[pluginName].render(tool.data);\r\n\r\n }\r\n\r\n /** is first-level block stretched */\r\n var stretched = editor.tools[pluginName].isStretched || false;\r\n\r\n /** Retrun type and block */\r\n return {\r\n type : pluginName,\r\n block : block,\r\n stretched : stretched\r\n };\r\n\r\n };\r\n\r\n return renderer;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/renderer.js","/**\r\n * Codex Editor Saver\r\n *\r\n * @author Codex Team\r\n * @version 1.0.2\r\n */\r\n\r\nmodule.exports = (function (saver) {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * Saves blocks\r\n * @private\r\n */\r\n saver.saveBlocks = function () {\r\n\r\n /** Save html content of redactor to memory */\r\n editor.state.html = editor.nodes.redactor.innerHTML;\r\n\r\n /** Empty jsonOutput state */\r\n editor.state.jsonOutput = [];\r\n\r\n Promise.resolve()\r\n\r\n .then(function () {\r\n\r\n return editor.nodes.redactor.childNodes;\r\n\r\n })\r\n /** Making a sequence from separate blocks */\r\n .then(editor.saver.makeQueue)\r\n\r\n .then(function () {\r\n // editor.nodes.textarea.innerHTML = editor.state.html;\r\n })\r\n\r\n .catch( function (error) {\r\n\r\n editor.core.log(error);\r\n\r\n });\r\n\r\n };\r\n\r\n saver.makeQueue = function (blocks) {\r\n\r\n var queue = Promise.resolve();\r\n\r\n for(var index = 0; index < blocks.length; index++) {\r\n\r\n /** Add node to sequence at specified index */\r\n editor.saver.getBlockData(queue, blocks, index);\r\n\r\n }\r\n\r\n };\r\n\r\n /** Gets every block and makes From Data */\r\n saver.getBlockData = function (queue, blocks, index) {\r\n\r\n queue.then(function () {\r\n\r\n return editor.saver.getNodeAsync(blocks, index);\r\n\r\n })\r\n\r\n .then(editor.saver.makeFormDataFromBlocks);\r\n\r\n };\r\n\r\n\r\n /**\r\n * Asynchronously returns block data from blocksList by index\r\n * @return Promise to node\r\n */\r\n saver.getNodeAsync = function (blocksList, index) {\r\n\r\n return Promise.resolve().then(function () {\r\n\r\n return blocksList[index];\r\n\r\n });\r\n\r\n };\r\n\r\n saver.makeFormDataFromBlocks = function (block) {\r\n\r\n var pluginName = block.dataset.tool;\r\n\r\n /** Check for plugin existance */\r\n if (!editor.tools[pluginName]) {\r\n\r\n throw Error(`Plugin «${pluginName}» not found`);\r\n\r\n }\r\n\r\n /** Check for plugin having render method */\r\n if (typeof editor.tools[pluginName].save != 'function') {\r\n\r\n throw Error(`Plugin «${pluginName}» must have save method`);\r\n\r\n }\r\n\r\n /** Result saver */\r\n var blockContent = block.childNodes[0],\r\n pluginsContent = blockContent.childNodes[0],\r\n savedData,\r\n position,\r\n output;\r\n\r\n /** If plugin wasn't available then return data from cache */\r\n if ( editor.tools[pluginName].available === false ) {\r\n\r\n position = pluginsContent.dataset.inputPosition;\r\n savedData = codex.editor.state.blocks.items[position].data;\r\n\r\n } else {\r\n\r\n savedData = editor.tools[pluginName].save(pluginsContent);\r\n\r\n if (editor.tools[pluginName].validate) {\r\n\r\n var result = editor.tools[pluginName].validate(savedData);\r\n\r\n /**\r\n * Do not allow invalid data\r\n */\r\n if (!result)\r\n return;\r\n\r\n }\r\n\r\n }\r\n\r\n output = {\r\n type : pluginName,\r\n data : savedData\r\n };\r\n\r\n editor.state.jsonOutput.push(output);\r\n\r\n };\r\n\r\n return saver;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/saver.js","/**\r\n * Codex Editor Content Module\r\n * Works with DOM\r\n *\r\n * @module Codex Editor content module\r\n *\r\n * @author Codex Team\r\n * @version 1.3.13\r\n *\r\n * @description Module works with Elements that have been appended to the main DOM\r\n */\r\n\r\nmodule.exports = (function (content) {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * Links to current active block\r\n * @type {null | Element}\r\n */\r\n content.currentNode = null;\r\n\r\n /**\r\n * clicked in redactor area\r\n * @type {null | Boolean}\r\n */\r\n content.editorAreaHightlighted = null;\r\n\r\n /**\r\n * @deprecated\r\n * Synchronizes redactor with original textarea\r\n */\r\n content.sync = function () {\r\n\r\n editor.core.log('syncing...');\r\n\r\n /**\r\n * Save redactor content to editor.state\r\n */\r\n editor.state.html = editor.nodes.redactor.innerHTML;\r\n\r\n };\r\n\r\n /**\r\n * Appends background to the block\r\n *\r\n * @description add CSS class to highlight visually first-level block area\r\n */\r\n content.markBlock = function () {\r\n\r\n editor.content.currentNode.classList.add(editor.ui.className.BLOCK_HIGHLIGHTED);\r\n\r\n };\r\n\r\n /**\r\n * Clear background\r\n *\r\n * @description clears styles that highlights block\r\n */\r\n content.clearMark = function () {\r\n\r\n if (editor.content.currentNode) {\r\n\r\n editor.content.currentNode.classList.remove(editor.ui.className.BLOCK_HIGHLIGHTED);\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Finds first-level block\r\n *\r\n * @param {Element} node - selected or clicked in redactors area node\r\n * @protected\r\n *\r\n * @description looks for first-level block.\r\n * gets parent while node is not first-level\r\n */\r\n content.getFirstLevelBlock = function (node) {\r\n\r\n if (!editor.core.isDomNode(node)) {\r\n\r\n node = node.parentNode;\r\n\r\n }\r\n\r\n if (node === editor.nodes.redactor || node === document.body) {\r\n\r\n return null;\r\n\r\n } else {\r\n\r\n while(!node.classList.contains(editor.ui.className.BLOCK_CLASSNAME)) {\r\n\r\n node = node.parentNode;\r\n\r\n }\r\n\r\n return node;\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Trigger this event when working node changed\r\n * @param {Element} targetNode - first-level of this node will be current\r\n * @protected\r\n *\r\n * @description If targetNode is first-level then we set it as current else we look for parents to find first-level\r\n */\r\n content.workingNodeChanged = function (targetNode) {\r\n\r\n /** Clear background from previous marked block before we change */\r\n editor.content.clearMark();\r\n\r\n if (!targetNode) {\r\n\r\n return;\r\n\r\n }\r\n\r\n content.currentNode = content.getFirstLevelBlock(targetNode);\r\n\r\n };\r\n\r\n /**\r\n * Replaces one redactor block with another\r\n * @protected\r\n * @param {Element} targetBlock - block to replace. Mostly currentNode.\r\n * @param {Element} newBlock\r\n * @param {string} newBlockType - type of new block; we need to store it to data-attribute\r\n *\r\n * [!] Function does not saves old block content.\r\n * You can get it manually and pass with newBlock.innerHTML\r\n */\r\n content.replaceBlock = function (targetBlock, newBlock) {\r\n\r\n if (!targetBlock || !newBlock) {\r\n\r\n editor.core.log('replaceBlock: missed params');\r\n return;\r\n\r\n }\r\n\r\n /** If target-block is not a frist-level block, then we iterate parents to find it */\r\n while(!targetBlock.classList.contains(editor.ui.className.BLOCK_CLASSNAME)) {\r\n\r\n targetBlock = targetBlock.parentNode;\r\n\r\n }\r\n\r\n /** Replacing */\r\n editor.nodes.redactor.replaceChild(newBlock, targetBlock);\r\n\r\n /**\r\n * Set new node as current\r\n */\r\n editor.content.workingNodeChanged(newBlock);\r\n\r\n /**\r\n * Add block handlers\r\n */\r\n editor.ui.addBlockHandlers(newBlock);\r\n\r\n /**\r\n * Save changes\r\n */\r\n editor.ui.saveInputs();\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n *\r\n * Inserts new block to redactor\r\n * Wrapps block into a DIV with BLOCK_CLASSNAME class\r\n *\r\n * @param blockData {object}\r\n * @param blockData.block {Element} element with block content\r\n * @param blockData.type {string} block plugin\r\n * @param needPlaceCaret {bool} pass true to set caret in new block\r\n *\r\n */\r\n content.insertBlock = function ( blockData, needPlaceCaret ) {\r\n\r\n var workingBlock = editor.content.currentNode,\r\n newBlockContent = blockData.block,\r\n blockType = blockData.type,\r\n isStretched = blockData.stretched;\r\n\r\n var newBlock = composeNewBlock_(newBlockContent, blockType, isStretched);\r\n\r\n if (workingBlock) {\r\n\r\n editor.core.insertAfter(workingBlock, newBlock);\r\n\r\n } else {\r\n\r\n /**\r\n * If redactor is empty, append as first child\r\n */\r\n editor.nodes.redactor.appendChild(newBlock);\r\n\r\n }\r\n\r\n /**\r\n * Block handler\r\n */\r\n editor.ui.addBlockHandlers(newBlock);\r\n\r\n /**\r\n * Set new node as current\r\n */\r\n editor.content.workingNodeChanged(newBlock);\r\n\r\n /**\r\n * Save changes\r\n */\r\n editor.ui.saveInputs();\r\n\r\n\r\n if ( needPlaceCaret ) {\r\n\r\n /**\r\n * If we don't know input index then we set default value -1\r\n */\r\n var currentInputIndex = editor.caret.getCurrentInputIndex() || -1;\r\n\r\n\r\n if (currentInputIndex == -1) {\r\n\r\n\r\n var editableElement = newBlock.querySelector('[contenteditable]'),\r\n emptyText = document.createTextNode('');\r\n\r\n editableElement.appendChild(emptyText);\r\n editor.caret.set(editableElement, 0, 0);\r\n\r\n editor.toolbar.move();\r\n editor.toolbar.showPlusButton();\r\n\r\n\r\n } else {\r\n\r\n if (currentInputIndex === editor.state.inputs.length - 1)\r\n return;\r\n\r\n /** Timeout for browsers execution */\r\n window.setTimeout(function () {\r\n\r\n /** Setting to the new input */\r\n editor.caret.setToNextBlock(currentInputIndex);\r\n editor.toolbar.move();\r\n editor.toolbar.open();\r\n\r\n }, 10);\r\n\r\n }\r\n\r\n }\r\n\r\n /**\r\n * Block is inserted, wait for new click that defined focusing on editors area\r\n * @type {boolean}\r\n */\r\n content.editorAreaHightlighted = false;\r\n\r\n };\r\n\r\n /**\r\n * Replaces blocks with saving content\r\n * @protected\r\n * @param {Element} noteToReplace\r\n * @param {Element} newNode\r\n * @param {Element} blockType\r\n */\r\n content.switchBlock = function (blockToReplace, newBlock, tool) {\r\n\r\n tool = tool || editor.content.currentNode.dataset.tool;\r\n var newBlockComposed = composeNewBlock_(newBlock, tool);\r\n\r\n /** Replacing */\r\n editor.content.replaceBlock(blockToReplace, newBlockComposed);\r\n\r\n /** Save new Inputs when block is changed */\r\n editor.ui.saveInputs();\r\n\r\n };\r\n\r\n /**\r\n * Iterates between child noted and looking for #text node on deepest level\r\n * @protected\r\n *\r\n * @param {Element} block - node where find\r\n * @param {int} postiton - starting postion\r\n * Example: childNodex.length to find from the end\r\n * or 0 to find from the start\r\n * @return {Text} block\r\n * @uses DFS\r\n */\r\n content.getDeepestTextNodeFromPosition = function (block, position) {\r\n\r\n /**\r\n * Clear Block from empty and useless spaces with trim.\r\n * Such nodes we should remove\r\n */\r\n var blockChilds = block.childNodes,\r\n index,\r\n node,\r\n text;\r\n\r\n for(index = 0; index < blockChilds.length; index++) {\r\n\r\n node = blockChilds[index];\r\n\r\n if (node.nodeType == editor.core.nodeTypes.TEXT) {\r\n\r\n text = node.textContent.trim();\r\n\r\n /** Text is empty. We should remove this child from node before we start DFS\r\n * decrease the quantity of childs.\r\n */\r\n if (text === '') {\r\n\r\n block.removeChild(node);\r\n position--;\r\n\r\n }\r\n\r\n }\r\n\r\n }\r\n\r\n if (block.childNodes.length === 0) {\r\n\r\n return document.createTextNode('');\r\n\r\n }\r\n\r\n /** Setting default position when we deleted all empty nodes */\r\n if ( position < 0 )\r\n position = 1;\r\n\r\n var lookingFromStart = false;\r\n\r\n /** For looking from START */\r\n if (position === 0) {\r\n\r\n lookingFromStart = true;\r\n position = 1;\r\n\r\n }\r\n\r\n while ( position ) {\r\n\r\n /** initial verticle of node. */\r\n if ( lookingFromStart ) {\r\n\r\n block = block.childNodes[0];\r\n\r\n } else {\r\n\r\n block = block.childNodes[position - 1];\r\n\r\n }\r\n\r\n if ( block.nodeType == editor.core.nodeTypes.TAG ) {\r\n\r\n position = block.childNodes.length;\r\n\r\n } else if (block.nodeType == editor.core.nodeTypes.TEXT ) {\r\n\r\n position = 0;\r\n\r\n }\r\n\r\n }\r\n\r\n return block;\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n * @param {Element} block - current plugins render\r\n * @param {String} tool - plugins name\r\n * @param {Boolean} isStretched - make stretched block or not\r\n *\r\n * @description adds necessary information to wrap new created block by first-level holder\r\n */\r\n var composeNewBlock_ = function (block, tool, isStretched) {\r\n\r\n var newBlock = editor.draw.node('DIV', editor.ui.className.BLOCK_CLASSNAME, {}),\r\n blockContent = editor.draw.node('DIV', editor.ui.className.BLOCK_CONTENT, {});\r\n\r\n blockContent.appendChild(block);\r\n newBlock.appendChild(blockContent);\r\n\r\n if (isStretched) {\r\n\r\n blockContent.classList.add(editor.ui.className.BLOCK_STRETCHED);\r\n\r\n }\r\n\r\n newBlock.dataset.tool = tool;\r\n return newBlock;\r\n\r\n };\r\n\r\n /**\r\n * Returns Range object of current selection\r\n * @protected\r\n */\r\n content.getRange = function () {\r\n\r\n var selection = window.getSelection().getRangeAt(0);\r\n\r\n return selection;\r\n\r\n };\r\n\r\n /**\r\n * Divides block in two blocks (after and before caret)\r\n *\r\n * @protected\r\n * @param {int} inputIndex - target input index\r\n *\r\n * @description splits current input content to the separate blocks\r\n * When enter is pressed among the words, that text will be splited.\r\n */\r\n content.splitBlock = function (inputIndex) {\r\n\r\n var selection = window.getSelection(),\r\n anchorNode = selection.anchorNode,\r\n anchorNodeText = anchorNode.textContent,\r\n caretOffset = selection.anchorOffset,\r\n textBeforeCaret,\r\n textNodeBeforeCaret,\r\n textAfterCaret,\r\n textNodeAfterCaret;\r\n\r\n var currentBlock = editor.content.currentNode.querySelector('[contentEditable]');\r\n\r\n\r\n textBeforeCaret = anchorNodeText.substring(0, caretOffset);\r\n textAfterCaret = anchorNodeText.substring(caretOffset);\r\n\r\n textNodeBeforeCaret = document.createTextNode(textBeforeCaret);\r\n\r\n if (textAfterCaret) {\r\n\r\n textNodeAfterCaret = document.createTextNode(textAfterCaret);\r\n\r\n }\r\n\r\n var previousChilds = [],\r\n nextChilds = [],\r\n reachedCurrent = false;\r\n\r\n if (textNodeAfterCaret) {\r\n\r\n nextChilds.push(textNodeAfterCaret);\r\n\r\n }\r\n\r\n for ( var i = 0, child; !!(child = currentBlock.childNodes[i]); i++) {\r\n\r\n if ( child != anchorNode ) {\r\n\r\n if ( !reachedCurrent ) {\r\n\r\n previousChilds.push(child);\r\n\r\n } else {\r\n\r\n nextChilds.push(child);\r\n\r\n }\r\n\r\n } else {\r\n\r\n reachedCurrent = true;\r\n\r\n }\r\n\r\n }\r\n\r\n /** Clear current input */\r\n editor.state.inputs[inputIndex].innerHTML = '';\r\n\r\n /**\r\n * Append all childs founded before anchorNode\r\n */\r\n var previousChildsLength = previousChilds.length;\r\n\r\n for(i = 0; i < previousChildsLength; i++) {\r\n\r\n editor.state.inputs[inputIndex].appendChild(previousChilds[i]);\r\n\r\n }\r\n\r\n editor.state.inputs[inputIndex].appendChild(textNodeBeforeCaret);\r\n\r\n /**\r\n * Append text node which is after caret\r\n */\r\n var nextChildsLength = nextChilds.length,\r\n newNode = document.createElement('div');\r\n\r\n for(i = 0; i < nextChildsLength; i++) {\r\n\r\n newNode.appendChild(nextChilds[i]);\r\n\r\n }\r\n\r\n newNode = newNode.innerHTML;\r\n\r\n /** This type of block creates when enter is pressed */\r\n var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\r\n\r\n /**\r\n * Make new paragraph with text after caret\r\n */\r\n editor.content.insertBlock({\r\n type : NEW_BLOCK_TYPE,\r\n block : editor.tools[NEW_BLOCK_TYPE].render({\r\n text : newNode\r\n })\r\n }, true );\r\n\r\n };\r\n\r\n /**\r\n * Merges two blocks — current and target\r\n * If target index is not exist, then previous will be as target\r\n *\r\n * @protected\r\n * @param {int} currentInputIndex\r\n * @param {int} targetInputIndex\r\n *\r\n * @description gets two inputs indexes and merges into one\r\n */\r\n content.mergeBlocks = function (currentInputIndex, targetInputIndex) {\r\n\r\n /** If current input index is zero, then prevent method execution */\r\n if (currentInputIndex === 0) {\r\n\r\n return;\r\n\r\n }\r\n\r\n var targetInput,\r\n currentInputContent = editor.state.inputs[currentInputIndex].innerHTML;\r\n\r\n if (!targetInputIndex) {\r\n\r\n targetInput = editor.state.inputs[currentInputIndex - 1];\r\n\r\n } else {\r\n\r\n targetInput = editor.state.inputs[targetInputIndex];\r\n\r\n }\r\n\r\n targetInput.innerHTML += currentInputContent;\r\n\r\n };\r\n\r\n /**\r\n * Iterates all right siblings and parents, which has right siblings\r\n * while it does not reached the first-level block\r\n *\r\n * @param {Element} node\r\n * @return {boolean}\r\n */\r\n content.isLastNode = function (node) {\r\n\r\n // console.log('погнали перебор родителей');\r\n\r\n var allChecked = false;\r\n\r\n while ( !allChecked ) {\r\n\r\n // console.log('Смотрим на %o', node);\r\n // console.log('Проверим, пустые ли соседи справа');\r\n\r\n if ( !allSiblingsEmpty_(node) ) {\r\n\r\n // console.log('Есть непустые соседи. Узел не последний. Выходим.');\r\n return false;\r\n\r\n }\r\n\r\n node = node.parentNode;\r\n\r\n /**\r\n * Проверяем родителей до тех пор, пока не найдем блок первого уровня\r\n */\r\n if ( node.classList.contains(editor.ui.className.BLOCK_CONTENT) ) {\r\n\r\n allChecked = true;\r\n\r\n }\r\n\r\n }\r\n\r\n return true;\r\n\r\n };\r\n\r\n /**\r\n * Checks if all element right siblings is empty\r\n * @param node\r\n */\r\n var allSiblingsEmpty_ = function (node) {\r\n\r\n /**\r\n * Нужно убедиться, что после пустого соседа ничего нет\r\n */\r\n var sibling = node.nextSibling;\r\n\r\n while ( sibling ) {\r\n\r\n if (sibling.textContent.length) {\r\n\r\n return false;\r\n\r\n }\r\n\r\n sibling = sibling.nextSibling;\r\n\r\n }\r\n\r\n return true;\r\n\r\n };\r\n\r\n /**\r\n * @public\r\n *\r\n * @param {string} htmlData - html content as string\r\n * @param {string} plainData - plain text\r\n * @return {string} - html content as string\r\n */\r\n content.wrapTextWithParagraphs = function (htmlData, plainData) {\r\n\r\n if (!htmlData.trim()) {\r\n\r\n return wrapPlainTextWithParagraphs(plainData);\r\n\r\n }\r\n\r\n var wrapper = document.createElement('DIV'),\r\n newWrapper = document.createElement('DIV'),\r\n i,\r\n paragraph,\r\n firstLevelBlocks = ['DIV', 'P'],\r\n blockTyped,\r\n node;\r\n\r\n /**\r\n * Make HTML Element to Wrap Text\r\n * It allows us to work with input data as HTML content\r\n */\r\n wrapper.innerHTML = htmlData;\r\n paragraph = document.createElement('P');\r\n\r\n for (i = 0; i < wrapper.childNodes.length; i++) {\r\n\r\n node = wrapper.childNodes[i];\r\n\r\n blockTyped = firstLevelBlocks.indexOf(node.tagName) != -1;\r\n\r\n /**\r\n * If node is first-levet\r\n * we add this node to our new wrapper\r\n */\r\n if ( blockTyped ) {\r\n\r\n /**\r\n * If we had splitted inline nodes to paragraph before\r\n */\r\n if ( paragraph.childNodes.length ) {\r\n\r\n newWrapper.appendChild(paragraph.cloneNode(true));\r\n\r\n /** empty paragraph */\r\n paragraph = null;\r\n paragraph = document.createElement('P');\r\n\r\n }\r\n\r\n newWrapper.appendChild(node.cloneNode(true));\r\n\r\n } else {\r\n\r\n /** Collect all inline nodes to one as paragraph */\r\n paragraph.appendChild(node.cloneNode(true));\r\n\r\n /** if node is last we should append this node to paragraph and paragraph to new wrapper */\r\n if ( i == wrapper.childNodes.length - 1 ) {\r\n\r\n newWrapper.appendChild(paragraph.cloneNode(true));\r\n\r\n }\r\n\r\n }\r\n\r\n }\r\n\r\n return newWrapper.innerHTML;\r\n\r\n };\r\n\r\n /**\r\n * Splits strings on new line and wraps paragraphs with

    tag\r\n * @param plainText\r\n * @returns {string}\r\n */\r\n var wrapPlainTextWithParagraphs = function (plainText) {\r\n\r\n return '

    ' + plainText.split('\\n\\n').join('

    ') + '

    ';\r\n\r\n };\r\n\r\n /**\r\n * Finds closest Contenteditable parent from Element\r\n * @param {Element} node element looking from\r\n * @return {Element} node contenteditable\r\n */\r\n content.getEditableParent = function (node) {\r\n\r\n while (node && node.contentEditable != 'true') {\r\n\r\n node = node.parentNode;\r\n\r\n }\r\n\r\n return node;\r\n\r\n };\r\n\r\n return content;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/content.js","/**\r\n * Codex Editor toolbar module\r\n *\r\n * Contains:\r\n * - Inline toolbox\r\n * - Toolbox within plus button\r\n * - Settings section\r\n *\r\n * @author Codex Team\r\n * @version 1.0\r\n */\r\n\r\nmodule.exports = (function (toolbar) {\r\n\r\n let editor = codex.editor;\r\n\r\n toolbar.settings = require('./settings');\r\n toolbar.inline = require('./inline');\r\n toolbar.toolbox = require('./toolbox');\r\n\r\n /**\r\n * Margin between focused node and toolbar\r\n */\r\n toolbar.defaultToolbarHeight = 49;\r\n\r\n toolbar.defaultOffset = 34;\r\n\r\n toolbar.opened = false;\r\n\r\n toolbar.current = null;\r\n\r\n /**\r\n * @protected\r\n */\r\n toolbar.open = function () {\r\n\r\n if (editor.hideToolbar) {\r\n\r\n return;\r\n\r\n }\r\n\r\n editor.nodes.toolbar.classList.add('opened');\r\n this.opened = true;\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n */\r\n toolbar.close = function () {\r\n\r\n editor.nodes.toolbar.classList.remove('opened');\r\n\r\n toolbar.opened = false;\r\n toolbar.current = null;\r\n\r\n for (var button in editor.nodes.toolbarButtons) {\r\n\r\n editor.nodes.toolbarButtons[button].classList.remove('selected');\r\n\r\n }\r\n\r\n /** Close toolbox when toolbar is not displayed */\r\n editor.toolbar.toolbox.close();\r\n editor.toolbar.settings.close();\r\n\r\n };\r\n\r\n toolbar.toggle = function () {\r\n\r\n if ( !this.opened ) {\r\n\r\n this.open();\r\n\r\n } else {\r\n\r\n this.close();\r\n\r\n }\r\n\r\n };\r\n\r\n toolbar.hidePlusButton = function () {\r\n\r\n editor.nodes.plusButton.classList.add('hide');\r\n\r\n };\r\n\r\n toolbar.showPlusButton = function () {\r\n\r\n editor.nodes.plusButton.classList.remove('hide');\r\n\r\n };\r\n\r\n /**\r\n * Moving toolbar to the specified node\r\n */\r\n toolbar.move = function () {\r\n\r\n /** Close Toolbox when we move toolbar */\r\n editor.toolbar.toolbox.close();\r\n\r\n if (!editor.content.currentNode) {\r\n\r\n return;\r\n\r\n }\r\n\r\n var newYCoordinate = editor.content.currentNode.offsetTop - (editor.toolbar.defaultToolbarHeight / 2) + editor.toolbar.defaultOffset;\r\n\r\n editor.nodes.toolbar.style.transform = `translate3D(0, ${Math.floor(newYCoordinate)}px, 0)`;\r\n\r\n /** Close trash actions */\r\n editor.toolbar.settings.hideRemoveActions();\r\n\r\n };\r\n\r\n return toolbar;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/toolbar/toolbar.js","/**\r\n * Toolbar settings\r\n *\r\n * @version 1.0.4\r\n */\r\n\r\nmodule.exports = (function (settings) {\r\n\r\n let editor = codex.editor;\r\n\r\n settings.opened = false;\r\n\r\n settings.setting = null;\r\n settings.actions = null;\r\n\r\n /**\r\n * Append and open settings\r\n */\r\n settings.open = function (toolType) {\r\n\r\n /**\r\n * Append settings content\r\n * It's stored in tool.settings\r\n */\r\n if (!editor.tools[toolType] || !editor.tools[toolType].makeSettings ) {\r\n\r\n editor.core.log(`Plugin «${toolType}» has no settings`, 'warn');\r\n editor.nodes.pluginSettings.innerHTML = `Плагин «${toolType}» не имеет настроек`;\r\n\r\n } else {\r\n\r\n /**\r\n * Draw settings block\r\n */\r\n var settingsBlock = editor.tools[toolType].makeSettings();\r\n\r\n editor.nodes.pluginSettings.appendChild(settingsBlock);\r\n\r\n }\r\n\r\n /** Open settings block */\r\n editor.nodes.blockSettings.classList.add('opened');\r\n this.opened = true;\r\n\r\n };\r\n\r\n /**\r\n * Close and clear settings\r\n */\r\n settings.close = function () {\r\n\r\n editor.nodes.blockSettings.classList.remove('opened');\r\n editor.nodes.pluginSettings.innerHTML = '';\r\n\r\n this.opened = false;\r\n\r\n };\r\n\r\n /**\r\n * @param {string} toolType - plugin type\r\n */\r\n settings.toggle = function ( toolType ) {\r\n\r\n if ( !this.opened ) {\r\n\r\n this.open(toolType);\r\n\r\n } else {\r\n\r\n this.close();\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Here we will draw buttons and add listeners to components\r\n */\r\n settings.makeRemoveBlockButton = function () {\r\n\r\n var removeBlockWrapper = editor.draw.node('SPAN', 'ce-toolbar__remove-btn', {}),\r\n settingButton = editor.draw.node('SPAN', 'ce-toolbar__remove-setting', { innerHTML : '' }),\r\n actionWrapper = editor.draw.node('DIV', 'ce-toolbar__remove-confirmation', {}),\r\n confirmAction = editor.draw.node('DIV', 'ce-toolbar__remove-confirm', { textContent : 'Удалить блок' }),\r\n cancelAction = editor.draw.node('DIV', 'ce-toolbar__remove-cancel', { textContent : 'Отмена' });\r\n\r\n editor.listeners.add(settingButton, 'click', editor.toolbar.settings.removeButtonClicked, false);\r\n\r\n editor.listeners.add(confirmAction, 'click', editor.toolbar.settings.confirmRemovingRequest, false);\r\n\r\n editor.listeners.add(cancelAction, 'click', editor.toolbar.settings.cancelRemovingRequest, false);\r\n\r\n actionWrapper.appendChild(confirmAction);\r\n actionWrapper.appendChild(cancelAction);\r\n\r\n removeBlockWrapper.appendChild(settingButton);\r\n removeBlockWrapper.appendChild(actionWrapper);\r\n\r\n /** Save setting */\r\n editor.toolbar.settings.setting = settingButton;\r\n editor.toolbar.settings.actions = actionWrapper;\r\n\r\n return removeBlockWrapper;\r\n\r\n };\r\n\r\n settings.removeButtonClicked = function () {\r\n\r\n var action = editor.toolbar.settings.actions;\r\n\r\n if (action.classList.contains('opened')) {\r\n\r\n editor.toolbar.settings.hideRemoveActions();\r\n\r\n } else {\r\n\r\n editor.toolbar.settings.showRemoveActions();\r\n\r\n }\r\n\r\n editor.toolbar.toolbox.close();\r\n editor.toolbar.settings.close();\r\n\r\n };\r\n\r\n settings.cancelRemovingRequest = function () {\r\n\r\n editor.toolbar.settings.actions.classList.remove('opened');\r\n\r\n };\r\n\r\n settings.confirmRemovingRequest = function () {\r\n\r\n var currentBlock = editor.content.currentNode,\r\n firstLevelBlocksCount;\r\n\r\n currentBlock.remove();\r\n\r\n firstLevelBlocksCount = editor.nodes.redactor.childNodes.length;\r\n\r\n /**\r\n * If all blocks are removed\r\n */\r\n if (firstLevelBlocksCount === 0) {\r\n\r\n /** update currentNode variable */\r\n editor.content.currentNode = null;\r\n\r\n /** Inserting new empty initial block */\r\n editor.ui.addInitialBlock();\r\n\r\n }\r\n\r\n editor.ui.saveInputs();\r\n\r\n editor.toolbar.close();\r\n\r\n };\r\n\r\n settings.showRemoveActions = function () {\r\n\r\n editor.toolbar.settings.actions.classList.add('opened');\r\n\r\n };\r\n\r\n settings.hideRemoveActions = function () {\r\n\r\n editor.toolbar.settings.actions.classList.remove('opened');\r\n\r\n };\r\n\r\n return settings;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/toolbar/settings.js","/**\r\n * Inline toolbar\r\n *\r\n * Contains from tools:\r\n * Bold, Italic, Underline and Anchor\r\n *\r\n * @author Codex Team\r\n * @version 1.0\r\n */\r\n\r\nmodule.exports = (function (inline) {\r\n\r\n let editor = codex.editor;\r\n\r\n inline.buttonsOpened = null;\r\n inline.actionsOpened = null;\r\n inline.wrappersOffset = null;\r\n\r\n /**\r\n * saving selection that need for execCommand for styling\r\n *\r\n */\r\n inline.storedSelection = null;\r\n\r\n /**\r\n * @protected\r\n *\r\n * Open inline toobar\r\n */\r\n inline.show = function () {\r\n\r\n var currentNode = editor.content.currentNode,\r\n tool = currentNode.dataset.tool,\r\n plugin;\r\n\r\n /**\r\n * tool allowed to open inline toolbar\r\n */\r\n plugin = editor.tools[tool];\r\n\r\n if (!plugin.showInlineToolbar)\r\n return;\r\n\r\n var selectedText = inline.getSelectionText(),\r\n toolbar = editor.nodes.inlineToolbar.wrapper;\r\n\r\n if (selectedText.length > 0) {\r\n\r\n /** Move toolbar and open */\r\n editor.toolbar.inline.move();\r\n\r\n /** Open inline toolbar */\r\n toolbar.classList.add('opened');\r\n\r\n /** show buttons of inline toolbar */\r\n editor.toolbar.inline.showButtons();\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n *\r\n * Closes inline toolbar\r\n */\r\n inline.close = function () {\r\n\r\n var toolbar = editor.nodes.inlineToolbar.wrapper;\r\n\r\n toolbar.classList.remove('opened');\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Moving toolbar\r\n */\r\n inline.move = function () {\r\n\r\n if (!this.wrappersOffset) {\r\n\r\n this.wrappersOffset = this.getWrappersOffset();\r\n\r\n }\r\n\r\n var coords = this.getSelectionCoords(),\r\n defaultOffset = 0,\r\n toolbar = editor.nodes.inlineToolbar.wrapper,\r\n newCoordinateX,\r\n newCoordinateY;\r\n\r\n if (toolbar.offsetHeight === 0) {\r\n\r\n defaultOffset = 40;\r\n\r\n }\r\n\r\n newCoordinateX = coords.x - this.wrappersOffset.left;\r\n newCoordinateY = coords.y + window.scrollY - this.wrappersOffset.top - defaultOffset - toolbar.offsetHeight;\r\n\r\n toolbar.style.transform = `translate3D(${Math.floor(newCoordinateX)}px, ${Math.floor(newCoordinateY)}px, 0)`;\r\n\r\n /** Close everything */\r\n editor.toolbar.inline.closeButtons();\r\n editor.toolbar.inline.closeAction();\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Tool Clicked\r\n */\r\n\r\n inline.toolClicked = function (event, type) {\r\n\r\n /**\r\n * For simple tools we use default browser function\r\n * For more complicated tools, we should write our own behavior\r\n */\r\n switch (type) {\r\n case 'createLink' : editor.toolbar.inline.createLinkAction(event, type); break;\r\n default : editor.toolbar.inline.defaultToolAction(type); break;\r\n }\r\n\r\n /**\r\n * highlight buttons\r\n * after making some action\r\n */\r\n editor.nodes.inlineToolbar.buttons.childNodes.forEach(editor.toolbar.inline.hightlight);\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Saving wrappers offset in DOM\r\n */\r\n inline.getWrappersOffset = function () {\r\n\r\n var wrapper = editor.nodes.wrapper,\r\n offset = this.getOffset(wrapper);\r\n\r\n this.wrappersOffset = offset;\r\n return offset;\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Calculates offset of DOM element\r\n *\r\n * @param el\r\n * @returns {{top: number, left: number}}\r\n */\r\n inline.getOffset = function ( el ) {\r\n\r\n var _x = 0;\r\n var _y = 0;\r\n\r\n while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {\r\n\r\n _x += (el.offsetLeft + el.clientLeft);\r\n _y += (el.offsetTop + el.clientTop);\r\n el = el.offsetParent;\r\n\r\n }\r\n return { top: _y, left: _x };\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Calculates position of selected text\r\n * @returns {{x: number, y: number}}\r\n */\r\n inline.getSelectionCoords = function () {\r\n\r\n var sel = document.selection, range;\r\n var x = 0, y = 0;\r\n\r\n if (sel) {\r\n\r\n if (sel.type != 'Control') {\r\n\r\n range = sel.createRange();\r\n range.collapse(true);\r\n x = range.boundingLeft;\r\n y = range.boundingTop;\r\n\r\n }\r\n\r\n } else if (window.getSelection) {\r\n\r\n sel = window.getSelection();\r\n\r\n if (sel.rangeCount) {\r\n\r\n range = sel.getRangeAt(0).cloneRange();\r\n if (range.getClientRects) {\r\n\r\n range.collapse(true);\r\n var rect = range.getClientRects()[0];\r\n\r\n if (!rect) {\r\n\r\n return;\r\n\r\n }\r\n\r\n x = rect.left;\r\n y = rect.top;\r\n\r\n }\r\n\r\n }\r\n\r\n }\r\n return { x: x, y: y };\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Returns selected text as String\r\n * @returns {string}\r\n */\r\n inline.getSelectionText = function () {\r\n\r\n var selectedText = '';\r\n\r\n // all modern browsers and IE9+\r\n if (window.getSelection) {\r\n\r\n selectedText = window.getSelection().toString();\r\n\r\n }\r\n\r\n return selectedText;\r\n\r\n };\r\n\r\n /** Opens buttons block */\r\n inline.showButtons = function () {\r\n\r\n var buttons = editor.nodes.inlineToolbar.buttons;\r\n\r\n buttons.classList.add('opened');\r\n\r\n editor.toolbar.inline.buttonsOpened = true;\r\n\r\n /** highlight buttons */\r\n editor.nodes.inlineToolbar.buttons.childNodes.forEach(editor.toolbar.inline.hightlight);\r\n\r\n };\r\n\r\n /** Makes buttons disappear */\r\n inline.closeButtons = function () {\r\n\r\n var buttons = editor.nodes.inlineToolbar.buttons;\r\n\r\n buttons.classList.remove('opened');\r\n\r\n editor.toolbar.inline.buttonsOpened = false;\r\n\r\n };\r\n\r\n /** Open buttons defined action if exist */\r\n inline.showActions = function () {\r\n\r\n var action = editor.nodes.inlineToolbar.actions;\r\n\r\n action.classList.add('opened');\r\n\r\n editor.toolbar.inline.actionsOpened = true;\r\n\r\n };\r\n\r\n /** Close actions block */\r\n inline.closeAction = function () {\r\n\r\n var action = editor.nodes.inlineToolbar.actions;\r\n\r\n action.innerHTML = '';\r\n action.classList.remove('opened');\r\n editor.toolbar.inline.actionsOpened = false;\r\n\r\n };\r\n\r\n\r\n /**\r\n * Callback for keydowns in inline toolbar \"Insert link...\" input\r\n */\r\n let inlineToolbarAnchorInputKeydown_ = function (event) {\r\n\r\n if (event.keyCode != editor.core.keys.ENTER) {\r\n\r\n return;\r\n\r\n }\r\n\r\n let editable = editor.content.currentNode,\r\n storedSelection = editor.toolbar.inline.storedSelection;\r\n\r\n editor.toolbar.inline.restoreSelection(editable, storedSelection);\r\n editor.toolbar.inline.setAnchor(this.value);\r\n\r\n /**\r\n * Preventing events that will be able to happen\r\n */\r\n event.preventDefault();\r\n event.stopImmediatePropagation();\r\n\r\n editor.toolbar.inline.clearRange();\r\n\r\n };\r\n\r\n /** Action for link creation or for setting anchor */\r\n inline.createLinkAction = function (event) {\r\n\r\n var isActive = this.isLinkActive();\r\n\r\n var editable = editor.content.currentNode,\r\n storedSelection = editor.toolbar.inline.saveSelection(editable);\r\n\r\n /** Save globally selection */\r\n editor.toolbar.inline.storedSelection = storedSelection;\r\n\r\n if (isActive) {\r\n\r\n\r\n /**\r\n * Changing stored selection. if we want to remove anchor from word\r\n * we should remove anchor from whole word, not only selected part.\r\n * The solution is than we get the length of current link\r\n * Change start position to - end of selection minus length of anchor\r\n */\r\n editor.toolbar.inline.restoreSelection(editable, storedSelection);\r\n\r\n editor.toolbar.inline.defaultToolAction('unlink');\r\n\r\n } else {\r\n\r\n /** Create input and close buttons */\r\n var action = editor.draw.inputForLink();\r\n\r\n editor.nodes.inlineToolbar.actions.appendChild(action);\r\n\r\n editor.toolbar.inline.closeButtons();\r\n editor.toolbar.inline.showActions();\r\n\r\n /**\r\n * focus to input\r\n * Solution: https://developer.mozilla.org/ru/docs/Web/API/HTMLElement/focus\r\n * Prevents event after showing input and when we need to focus an input which is in unexisted form\r\n */\r\n action.focus();\r\n event.preventDefault();\r\n\r\n /** Callback to link action */\r\n editor.listeners.add(action, 'keydown', inlineToolbarAnchorInputKeydown_, false);\r\n\r\n }\r\n\r\n };\r\n\r\n inline.isLinkActive = function () {\r\n\r\n var isActive = false;\r\n\r\n editor.nodes.inlineToolbar.buttons.childNodes.forEach(function (tool) {\r\n\r\n var dataType = tool.dataset.type;\r\n\r\n if (dataType == 'link' && tool.classList.contains('hightlighted')) {\r\n\r\n isActive = true;\r\n\r\n }\r\n\r\n });\r\n\r\n return isActive;\r\n\r\n };\r\n\r\n /** default action behavior of tool */\r\n inline.defaultToolAction = function (type) {\r\n\r\n document.execCommand(type, false, null);\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Sets URL\r\n *\r\n * @param {String} url - URL\r\n */\r\n inline.setAnchor = function (url) {\r\n\r\n document.execCommand('createLink', false, url);\r\n\r\n /** Close after URL inserting */\r\n editor.toolbar.inline.closeAction();\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Saves selection\r\n */\r\n inline.saveSelection = function (containerEl) {\r\n\r\n var range = window.getSelection().getRangeAt(0),\r\n preSelectionRange = range.cloneRange(),\r\n start;\r\n\r\n preSelectionRange.selectNodeContents(containerEl);\r\n preSelectionRange.setEnd(range.startContainer, range.startOffset);\r\n\r\n start = preSelectionRange.toString().length;\r\n\r\n return {\r\n start: start,\r\n end: start + range.toString().length\r\n };\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Sets to previous selection (Range)\r\n *\r\n * @param {Element} containerEl - editable element where we restore range\r\n * @param {Object} savedSel - range basic information to restore\r\n */\r\n inline.restoreSelection = function (containerEl, savedSel) {\r\n\r\n var range = document.createRange(),\r\n charIndex = 0;\r\n\r\n range.setStart(containerEl, 0);\r\n range.collapse(true);\r\n\r\n var nodeStack = [ containerEl ],\r\n node,\r\n foundStart = false,\r\n stop = false,\r\n nextCharIndex;\r\n\r\n while (!stop && (node = nodeStack.pop())) {\r\n\r\n if (node.nodeType == 3) {\r\n\r\n nextCharIndex = charIndex + node.length;\r\n\r\n if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {\r\n\r\n range.setStart(node, savedSel.start - charIndex);\r\n foundStart = true;\r\n\r\n }\r\n if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {\r\n\r\n range.setEnd(node, savedSel.end - charIndex);\r\n stop = true;\r\n\r\n }\r\n charIndex = nextCharIndex;\r\n\r\n } else {\r\n\r\n var i = node.childNodes.length;\r\n\r\n while (i--) {\r\n\r\n nodeStack.push(node.childNodes[i]);\r\n\r\n }\r\n\r\n }\r\n\r\n }\r\n\r\n var sel = window.getSelection();\r\n\r\n sel.removeAllRanges();\r\n sel.addRange(range);\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Removes all ranges from window selection\r\n */\r\n inline.clearRange = function () {\r\n\r\n var selection = window.getSelection();\r\n\r\n selection.removeAllRanges();\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * sets or removes hightlight\r\n */\r\n inline.hightlight = function (tool) {\r\n\r\n var dataType = tool.dataset.type;\r\n\r\n if (document.queryCommandState(dataType)) {\r\n\r\n editor.toolbar.inline.setButtonHighlighted(tool);\r\n\r\n } else {\r\n\r\n editor.toolbar.inline.removeButtonsHighLight(tool);\r\n\r\n }\r\n\r\n /**\r\n *\r\n * hightlight for anchors\r\n */\r\n var selection = window.getSelection(),\r\n tag = selection.anchorNode.parentNode;\r\n\r\n if (tag.tagName == 'A' && dataType == 'link') {\r\n\r\n editor.toolbar.inline.setButtonHighlighted(tool);\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Mark button if text is already executed\r\n */\r\n inline.setButtonHighlighted = function (button) {\r\n\r\n button.classList.add('hightlighted');\r\n\r\n /** At link tool we also change icon */\r\n if (button.dataset.type == 'link') {\r\n\r\n var icon = button.childNodes[0];\r\n\r\n icon.classList.remove('ce-icon-link');\r\n icon.classList.add('ce-icon-unlink');\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n *\r\n * Removes hightlight\r\n */\r\n inline.removeButtonsHighLight = function (button) {\r\n\r\n button.classList.remove('hightlighted');\r\n\r\n /** At link tool we also change icon */\r\n if (button.dataset.type == 'link') {\r\n\r\n var icon = button.childNodes[0];\r\n\r\n icon.classList.remove('ce-icon-unlink');\r\n icon.classList.add('ce-icon-link');\r\n\r\n }\r\n\r\n };\r\n\r\n\r\n return inline;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/toolbar/inline.js","/**\r\n * Codex Editor toolbox\r\n *\r\n * All tools be able to appended here\r\n *\r\n * @author Codex Team\r\n * @version 1.0\r\n */\r\n\r\nmodule.exports = (function (toolbox) {\r\n\r\n let editor = codex.editor;\r\n\r\n toolbox.opened = false;\r\n\r\n /** Shows toolbox */\r\n toolbox.open = function () {\r\n\r\n /** Close setting if toolbox is opened */\r\n if (editor.toolbar.settings.opened) {\r\n\r\n editor.toolbar.settings.close();\r\n\r\n }\r\n\r\n /** display toolbox */\r\n editor.nodes.toolbox.classList.add('opened');\r\n\r\n /** Animate plus button */\r\n editor.nodes.plusButton.classList.add('clicked');\r\n\r\n /** toolbox state */\r\n editor.toolbar.toolbox.opened = true;\r\n\r\n };\r\n\r\n /** Closes toolbox */\r\n toolbox.close = function () {\r\n\r\n /** Makes toolbox disapear */\r\n editor.nodes.toolbox.classList.remove('opened');\r\n\r\n /** Rotate plus button */\r\n editor.nodes.plusButton.classList.remove('clicked');\r\n\r\n /** toolbox state */\r\n editor.toolbar.toolbox.opened = false;\r\n\r\n editor.toolbar.current = null;\r\n\r\n };\r\n\r\n toolbox.leaf = function () {\r\n\r\n let currentTool = editor.toolbar.current,\r\n tools = Object.keys(editor.tools),\r\n barButtons = editor.nodes.toolbarButtons,\r\n nextToolIndex = 0,\r\n toolToSelect,\r\n visibleTool,\r\n tool;\r\n\r\n if ( !currentTool ) {\r\n\r\n /** Get first tool from object*/\r\n for(tool in editor.tools) {\r\n\r\n if (editor.tools[tool].displayInToolbox) {\r\n\r\n break;\r\n\r\n }\r\n\r\n nextToolIndex ++;\r\n\r\n }\r\n\r\n } else {\r\n\r\n nextToolIndex = (tools.indexOf(currentTool) + 1) % tools.length;\r\n visibleTool = tools[nextToolIndex];\r\n\r\n while (!editor.tools[visibleTool].displayInToolbox) {\r\n\r\n nextToolIndex = (nextToolIndex + 1) % tools.length;\r\n visibleTool = tools[nextToolIndex];\r\n\r\n }\r\n\r\n }\r\n\r\n toolToSelect = tools[nextToolIndex];\r\n\r\n for ( var button in barButtons ) {\r\n\r\n barButtons[button].classList.remove('selected');\r\n\r\n }\r\n\r\n barButtons[toolToSelect].classList.add('selected');\r\n editor.toolbar.current = toolToSelect;\r\n\r\n };\r\n\r\n /**\r\n * Transforming selected node type into selected toolbar element type\r\n * @param {event} event\r\n */\r\n toolbox.toolClicked = function (event) {\r\n\r\n /**\r\n * UNREPLACEBLE_TOOLS this types of tools are forbidden to replace even they are empty\r\n */\r\n var UNREPLACEBLE_TOOLS = ['image', 'link', 'list', 'instagram', 'twitter', 'embed'],\r\n tool = editor.tools[editor.toolbar.current],\r\n workingNode = editor.content.currentNode,\r\n currentInputIndex = editor.caret.inputIndex,\r\n newBlockContent,\r\n appendCallback,\r\n blockData;\r\n\r\n /** Make block from plugin */\r\n newBlockContent = tool.render();\r\n\r\n /** information about block */\r\n blockData = {\r\n block : newBlockContent,\r\n type : tool.type,\r\n stretched : false\r\n };\r\n\r\n if (\r\n workingNode &&\r\n UNREPLACEBLE_TOOLS.indexOf(workingNode.dataset.tool) === -1 &&\r\n workingNode.textContent.trim() === ''\r\n ) {\r\n\r\n /** Replace current block */\r\n editor.content.switchBlock(workingNode, newBlockContent, tool.type);\r\n\r\n } else {\r\n\r\n /** Insert new Block from plugin */\r\n editor.content.insertBlock(blockData);\r\n\r\n /** increase input index */\r\n currentInputIndex++;\r\n\r\n }\r\n\r\n /** Fire tool append callback */\r\n appendCallback = tool.appendCallback;\r\n\r\n if (appendCallback && typeof appendCallback == 'function') {\r\n\r\n appendCallback.call(event);\r\n\r\n }\r\n\r\n window.setTimeout(function () {\r\n\r\n /** Set caret to current block */\r\n editor.caret.setToBlock(currentInputIndex);\r\n\r\n }, 10);\r\n\r\n\r\n /**\r\n * Changing current Node\r\n */\r\n editor.content.workingNodeChanged();\r\n\r\n /**\r\n * Move toolbar when node is changed\r\n */\r\n editor.toolbar.move();\r\n\r\n };\r\n\r\n return toolbox;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/toolbar/toolbox.js","/**\r\n * @module Codex Editor Callbacks module\r\n * @description Module works with editor added Elements\r\n *\r\n * @author Codex Team\r\n * @version 1.4.0\r\n */\r\n\r\nmodule.exports = (function (callbacks) {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * used by UI module\r\n * @description Routes all keydowns on document\r\n * @param {Object} event\r\n */\r\n callbacks.globalKeydown = function (event) {\r\n\r\n switch (event.keyCode) {\r\n case editor.core.keys.ENTER : enterKeyPressed_(event); break;\r\n }\r\n\r\n };\r\n\r\n /**\r\n * used by UI module\r\n * @description Routes all keydowns on redactors area\r\n * @param {Object} event\r\n */\r\n callbacks.redactorKeyDown = function (event) {\r\n\r\n switch (event.keyCode) {\r\n case editor.core.keys.TAB : tabKeyPressedOnRedactorsZone_(event); break;\r\n case editor.core.keys.ENTER : enterKeyPressedOnRedactorsZone_(event); break;\r\n case editor.core.keys.ESC : escapeKeyPressedOnRedactorsZone_(event); break;\r\n default : defaultKeyPressedOnRedactorsZone_(event); break;\r\n }\r\n\r\n };\r\n\r\n /**\r\n * used by UI module\r\n * @description Routes all keyup events\r\n * @param {Object} event\r\n */\r\n callbacks.globalKeyup = function (event) {\r\n\r\n switch (event.keyCode) {\r\n case editor.core.keys.UP :\r\n case editor.core.keys.LEFT :\r\n case editor.core.keys.RIGHT :\r\n case editor.core.keys.DOWN : arrowKeyPressed_(event); break;\r\n }\r\n\r\n };\r\n\r\n /**\r\n * @param {Object} event\r\n * @private\r\n *\r\n * Handles behaviour when tab pressed\r\n * @description if Content is empty show toolbox (if it is closed) or leaf tools\r\n * uses Toolbars toolbox module to handle the situation\r\n */\r\n var tabKeyPressedOnRedactorsZone_ = function (event) {\r\n\r\n /**\r\n * Wait for solution. Would like to know the behaviour\r\n * @todo Add spaces\r\n */\r\n event.preventDefault();\r\n\r\n\r\n if (!editor.core.isBlockEmpty(editor.content.currentNode)) {\r\n\r\n return;\r\n\r\n }\r\n\r\n if ( !editor.toolbar.opened ) {\r\n\r\n editor.toolbar.open();\r\n\r\n }\r\n\r\n if (editor.toolbar.opened && !editor.toolbar.toolbox.opened) {\r\n\r\n editor.toolbar.toolbox.open();\r\n\r\n } else {\r\n\r\n editor.toolbar.toolbox.leaf();\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Handles global EnterKey Press\r\n * @see enterPressedOnBlock_\r\n * @param {Object} event\r\n */\r\n var enterKeyPressed_ = function () {\r\n\r\n if (editor.content.editorAreaHightlighted) {\r\n\r\n /**\r\n * it means that we lose input index, saved index before is not correct\r\n * therefore we need to set caret when we insert new block\r\n */\r\n editor.caret.inputIndex = -1;\r\n\r\n enterPressedOnBlock_();\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Callback for enter key pressing in first-level block area\r\n *\r\n * @param {Event} event\r\n * @private\r\n *\r\n * @description Inserts new block with initial type from settings\r\n */\r\n var enterPressedOnBlock_ = function () {\r\n\r\n var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\r\n\r\n editor.content.insertBlock({\r\n type : NEW_BLOCK_TYPE,\r\n block : editor.tools[NEW_BLOCK_TYPE].render()\r\n }, true );\r\n\r\n editor.toolbar.move();\r\n editor.toolbar.open();\r\n\r\n };\r\n\r\n\r\n /**\r\n * ENTER key handler\r\n *\r\n * @param {Object} event\r\n * @private\r\n *\r\n * @description Makes new block with initial type from settings\r\n */\r\n var enterKeyPressedOnRedactorsZone_ = function (event) {\r\n\r\n if (event.target.contentEditable == 'true') {\r\n\r\n /** Update input index */\r\n editor.caret.saveCurrentInputIndex();\r\n\r\n }\r\n\r\n var currentInputIndex = editor.caret.getCurrentInputIndex() || 0,\r\n workingNode = editor.content.currentNode,\r\n tool = workingNode.dataset.tool,\r\n isEnterPressedOnToolbar = editor.toolbar.opened &&\r\n editor.toolbar.current &&\r\n event.target == editor.state.inputs[currentInputIndex];\r\n\r\n /** The list of tools which needs the default browser behaviour */\r\n var enableLineBreaks = editor.tools[tool].enableLineBreaks;\r\n\r\n /** This type of block creates when enter is pressed */\r\n var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\r\n\r\n /**\r\n * When toolbar is opened, select tool instead of making new paragraph\r\n */\r\n if ( isEnterPressedOnToolbar ) {\r\n\r\n event.preventDefault();\r\n\r\n editor.toolbar.toolbox.toolClicked(event);\r\n\r\n editor.toolbar.close();\r\n\r\n /**\r\n * Stop other listeners callback executions\r\n */\r\n event.stopPropagation();\r\n event.stopImmediatePropagation();\r\n\r\n return;\r\n\r\n }\r\n\r\n /**\r\n * Allow paragraph lineBreaks with shift enter\r\n * Or if shiftkey pressed and enter and enabledLineBreaks, the let new block creation\r\n */\r\n if ( event.shiftKey || enableLineBreaks ) {\r\n\r\n event.stopPropagation();\r\n event.stopImmediatePropagation();\r\n return;\r\n\r\n }\r\n\r\n var currentSelection = window.getSelection(),\r\n currentSelectedNode = currentSelection.anchorNode,\r\n caretAtTheEndOfText = editor.caret.position.atTheEnd(),\r\n isTextNodeHasParentBetweenContenteditable = false;\r\n\r\n /**\r\n * Allow making new

    in same block by SHIFT+ENTER and forbids to prevent default browser behaviour\r\n */\r\n if ( event.shiftKey && !enableLineBreaks ) {\r\n\r\n editor.callback.enterPressedOnBlock(editor.content.currentBlock, event);\r\n event.preventDefault();\r\n return;\r\n\r\n }\r\n\r\n /**\r\n * Workaround situation when caret at the Text node that has some wrapper Elements\r\n * Split block cant handle this.\r\n * We need to save default behavior\r\n */\r\n isTextNodeHasParentBetweenContenteditable = currentSelectedNode && currentSelectedNode.parentNode.contentEditable != 'true';\r\n\r\n /**\r\n * Split blocks when input has several nodes and caret placed in textNode\r\n */\r\n if (\r\n currentSelectedNode.nodeType == editor.core.nodeTypes.TEXT &&\r\n !isTextNodeHasParentBetweenContenteditable &&\r\n !caretAtTheEndOfText\r\n ) {\r\n\r\n event.preventDefault();\r\n\r\n editor.core.log('Splitting Text node...');\r\n\r\n editor.content.splitBlock(currentInputIndex);\r\n\r\n /** Show plus button when next input after split is empty*/\r\n if (!editor.state.inputs[currentInputIndex + 1].textContent.trim()) {\r\n\r\n editor.toolbar.showPlusButton();\r\n\r\n }\r\n\r\n } else {\r\n\r\n var islastNode = editor.content.isLastNode(currentSelectedNode);\r\n\r\n if ( islastNode && caretAtTheEndOfText ) {\r\n\r\n event.preventDefault();\r\n event.stopPropagation();\r\n event.stopImmediatePropagation();\r\n\r\n editor.core.log('ENTER clicked in last textNode. Create new BLOCK');\r\n\r\n editor.content.insertBlock({\r\n type: NEW_BLOCK_TYPE,\r\n block: editor.tools[NEW_BLOCK_TYPE].render()\r\n }, true);\r\n\r\n editor.toolbar.move();\r\n editor.toolbar.open();\r\n\r\n /** Show plus button with empty block */\r\n editor.toolbar.showPlusButton();\r\n\r\n }\r\n\r\n }\r\n\r\n /** get all inputs after new appending block */\r\n editor.ui.saveInputs();\r\n\r\n };\r\n\r\n /**\r\n * Escape behaviour\r\n * @param event\r\n * @private\r\n *\r\n * @description Closes toolbox and toolbar. Prevents default behaviour\r\n */\r\n var escapeKeyPressedOnRedactorsZone_ = function (event) {\r\n\r\n /** Close all toolbar */\r\n editor.toolbar.close();\r\n\r\n /** Close toolbox */\r\n editor.toolbar.toolbox.close();\r\n\r\n event.preventDefault();\r\n\r\n };\r\n\r\n /**\r\n * @param {Event} event\r\n * @private\r\n *\r\n * closes and moves toolbar\r\n */\r\n var arrowKeyPressed_ = function (event) {\r\n\r\n editor.content.workingNodeChanged();\r\n\r\n /* Closing toolbar */\r\n editor.toolbar.close();\r\n editor.toolbar.move();\r\n\r\n };\r\n\r\n /**\r\n * @private\r\n * @param {Event} event\r\n *\r\n * @description Closes all opened bars from toolbar.\r\n * If block is mark, clears highlightning\r\n */\r\n var defaultKeyPressedOnRedactorsZone_ = function () {\r\n\r\n editor.toolbar.close();\r\n\r\n if (!editor.toolbar.inline.actionsOpened) {\r\n\r\n editor.toolbar.inline.close();\r\n editor.content.clearMark();\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Handler when clicked on redactors area\r\n *\r\n * @protected\r\n * @param event\r\n *\r\n * @description Detects clicked area. If it is first-level block area, marks as detected and\r\n * on next enter press will be inserted new block\r\n * Otherwise, save carets position (input index) and put caret to the editable zone.\r\n *\r\n * @see detectWhenClickedOnFirstLevelBlockArea_\r\n *\r\n */\r\n callbacks.redactorClicked = function (event) {\r\n\r\n detectWhenClickedOnFirstLevelBlockArea_();\r\n\r\n editor.content.workingNodeChanged(event.target);\r\n editor.ui.saveInputs();\r\n\r\n var selectedText = editor.toolbar.inline.getSelectionText(),\r\n firstLevelBlock;\r\n\r\n /** If selection range took off, then we hide inline toolbar */\r\n if (selectedText.length === 0) {\r\n\r\n editor.toolbar.inline.close();\r\n\r\n }\r\n\r\n /** Update current input index in memory when caret focused into existed input */\r\n if (event.target.contentEditable == 'true') {\r\n\r\n editor.caret.saveCurrentInputIndex();\r\n\r\n }\r\n\r\n if (editor.content.currentNode === null) {\r\n\r\n /**\r\n * If inputs in redactor does not exits, then we put input index 0 not -1\r\n */\r\n var indexOfLastInput = editor.state.inputs.length > 0 ? editor.state.inputs.length - 1 : 0;\r\n\r\n /** If we have any inputs */\r\n if (editor.state.inputs.length) {\r\n\r\n /** getting firstlevel parent of input */\r\n firstLevelBlock = editor.content.getFirstLevelBlock(editor.state.inputs[indexOfLastInput]);\r\n\r\n }\r\n\r\n /** If input is empty, then we set caret to the last input */\r\n if (editor.state.inputs.length && editor.state.inputs[indexOfLastInput].textContent === '' && firstLevelBlock.dataset.tool == editor.settings.initialBlockPlugin) {\r\n\r\n editor.caret.setToBlock(indexOfLastInput);\r\n\r\n } else {\r\n\r\n /** Create new input when caret clicked in redactors area */\r\n var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\r\n\r\n editor.content.insertBlock({\r\n type : NEW_BLOCK_TYPE,\r\n block : editor.tools[NEW_BLOCK_TYPE].render()\r\n });\r\n\r\n /** If there is no inputs except inserted */\r\n if (editor.state.inputs.length === 1) {\r\n\r\n editor.caret.setToBlock(indexOfLastInput);\r\n\r\n } else {\r\n\r\n /** Set caret to this appended input */\r\n editor.caret.setToNextBlock(indexOfLastInput);\r\n\r\n }\r\n\r\n }\r\n\r\n } else {\r\n\r\n /** Close all panels */\r\n editor.toolbar.settings.close();\r\n editor.toolbar.toolbox.close();\r\n\r\n }\r\n\r\n /**\r\n * Move toolbar and open\r\n */\r\n editor.toolbar.move();\r\n editor.toolbar.open();\r\n\r\n var inputIsEmpty = !editor.content.currentNode.textContent.trim(),\r\n currentNodeType = editor.content.currentNode.dataset.tool,\r\n isInitialType = currentNodeType == editor.settings.initialBlockPlugin;\r\n\r\n\r\n /** Hide plus buttons */\r\n editor.toolbar.hidePlusButton();\r\n\r\n if (!inputIsEmpty) {\r\n\r\n /** Mark current block */\r\n editor.content.markBlock();\r\n\r\n }\r\n\r\n if ( isInitialType && inputIsEmpty ) {\r\n\r\n /** Show plus button */\r\n editor.toolbar.showPlusButton();\r\n\r\n }\r\n\r\n\r\n };\r\n\r\n /**\r\n * This method allows to define, is caret in contenteditable element or not.\r\n *\r\n * @private\r\n *\r\n * @description Otherwise, if we get TEXT node from range container, that will means we have input index.\r\n * In this case we use default browsers behaviour (if plugin allows that) or overwritten action.\r\n * Therefore, to be sure that we've clicked first-level block area, we should have currentNode, which always\r\n * specifies to the first-level block. Other cases we just ignore.\r\n */\r\n var detectWhenClickedOnFirstLevelBlockArea_ = function () {\r\n\r\n var selection = window.getSelection(),\r\n anchorNode = selection.anchorNode,\r\n flag = false;\r\n\r\n if (selection.rangeCount === 0) {\r\n\r\n editor.content.editorAreaHightlighted = true;\r\n\r\n } else {\r\n\r\n if (!editor.core.isDomNode(anchorNode)) {\r\n\r\n anchorNode = anchorNode.parentNode;\r\n\r\n }\r\n\r\n /** Already founded, without loop */\r\n if (anchorNode.contentEditable == 'true') {\r\n\r\n flag = true;\r\n\r\n }\r\n\r\n while (anchorNode.contentEditable != 'true') {\r\n\r\n anchorNode = anchorNode.parentNode;\r\n\r\n if (anchorNode.contentEditable == 'true') {\r\n\r\n flag = true;\r\n\r\n }\r\n\r\n if (anchorNode == document.body) {\r\n\r\n break;\r\n\r\n }\r\n\r\n }\r\n\r\n /** If editable element founded, flag is \"TRUE\", Therefore we return \"FALSE\" */\r\n editor.content.editorAreaHightlighted = !flag;\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Toolbar button click handler\r\n *\r\n * @param {Object} event - cursor to the button\r\n * @protected\r\n *\r\n * @description gets current tool and calls render method\r\n */\r\n callbacks.toolbarButtonClicked = function (event) {\r\n\r\n var button = this;\r\n\r\n editor.toolbar.current = button.dataset.type;\r\n\r\n editor.toolbar.toolbox.toolClicked(event);\r\n editor.toolbar.close();\r\n\r\n };\r\n\r\n /**\r\n * Show or Hide toolbox when plus button is clicked\r\n */\r\n callbacks.plusButtonClicked = function () {\r\n\r\n if (!editor.nodes.toolbox.classList.contains('opened')) {\r\n\r\n editor.toolbar.toolbox.open();\r\n\r\n } else {\r\n\r\n editor.toolbar.toolbox.close();\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Block handlers for KeyDown events\r\n *\r\n * @protected\r\n * @param {Object} event\r\n *\r\n * Handles keydowns on block\r\n * @see blockRightOrDownArrowPressed_\r\n * @see backspacePressed_\r\n * @see blockLeftOrUpArrowPressed_\r\n */\r\n callbacks.blockKeydown = function (event) {\r\n\r\n let block = event.target; // event.target is input\r\n\r\n switch (event.keyCode) {\r\n\r\n case editor.core.keys.DOWN:\r\n case editor.core.keys.RIGHT:\r\n blockRightOrDownArrowPressed_(event);\r\n break;\r\n\r\n case editor.core.keys.BACKSPACE:\r\n backspacePressed_(block, event);\r\n break;\r\n\r\n case editor.core.keys.UP:\r\n case editor.core.keys.LEFT:\r\n blockLeftOrUpArrowPressed_(event);\r\n break;\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * RIGHT or DOWN keydowns on block\r\n *\r\n * @param {Object} event\r\n * @private\r\n *\r\n * @description watches the selection and gets closest editable element.\r\n * Uses method getDeepestTextNodeFromPosition to get the last node of next block\r\n * Sets caret if it is contenteditable\r\n */\r\n var blockRightOrDownArrowPressed_ = function (event) {\r\n\r\n var selection = window.getSelection(),\r\n inputs = editor.state.inputs,\r\n focusedNode = selection.anchorNode,\r\n focusedNodeHolder;\r\n\r\n /** Check for caret existance */\r\n if (!focusedNode) {\r\n\r\n return false;\r\n\r\n }\r\n\r\n /** Looking for closest (parent) contentEditable element of focused node */\r\n while (focusedNode.contentEditable != 'true') {\r\n\r\n focusedNodeHolder = focusedNode.parentNode;\r\n focusedNode = focusedNodeHolder;\r\n\r\n }\r\n\r\n /** Input index in DOM level */\r\n var editableElementIndex = 0;\r\n\r\n while (focusedNode != inputs[editableElementIndex]) {\r\n\r\n editableElementIndex ++;\r\n\r\n }\r\n\r\n /**\r\n * Founded contentEditable element doesn't have childs\r\n * Or maybe New created block\r\n */\r\n if (!focusedNode.textContent) {\r\n\r\n editor.caret.setToNextBlock(editableElementIndex);\r\n return;\r\n\r\n }\r\n\r\n /**\r\n * Do nothing when caret doesn not reaches the end of last child\r\n */\r\n var caretInLastChild = false,\r\n caretAtTheEndOfText = false;\r\n\r\n var lastChild,\r\n deepestTextnode;\r\n\r\n lastChild = focusedNode.childNodes[focusedNode.childNodes.length - 1 ];\r\n\r\n if (editor.core.isDomNode(lastChild)) {\r\n\r\n deepestTextnode = editor.content.getDeepestTextNodeFromPosition(lastChild, lastChild.childNodes.length);\r\n\r\n } else {\r\n\r\n deepestTextnode = lastChild;\r\n\r\n }\r\n\r\n caretInLastChild = selection.anchorNode == deepestTextnode;\r\n caretAtTheEndOfText = deepestTextnode.length == selection.anchorOffset;\r\n\r\n if ( !caretInLastChild || !caretAtTheEndOfText ) {\r\n\r\n editor.core.log('arrow [down|right] : caret does not reached the end');\r\n return false;\r\n\r\n }\r\n\r\n editor.caret.setToNextBlock(editableElementIndex);\r\n\r\n };\r\n\r\n /**\r\n * LEFT or UP keydowns on block\r\n *\r\n * @param {Object} event\r\n * @private\r\n *\r\n * watches the selection and gets closest editable element.\r\n * Uses method getDeepestTextNodeFromPosition to get the last node of previous block\r\n * Sets caret if it is contenteditable\r\n *\r\n */\r\n var blockLeftOrUpArrowPressed_ = function (event) {\r\n\r\n var selection = window.getSelection(),\r\n inputs = editor.state.inputs,\r\n focusedNode = selection.anchorNode,\r\n focusedNodeHolder;\r\n\r\n /** Check for caret existance */\r\n if (!focusedNode) {\r\n\r\n return false;\r\n\r\n }\r\n\r\n /**\r\n * LEFT or UP not at the beginning\r\n */\r\n if ( selection.anchorOffset !== 0) {\r\n\r\n return false;\r\n\r\n }\r\n\r\n /** Looking for parent contentEditable block */\r\n while (focusedNode.contentEditable != 'true') {\r\n\r\n focusedNodeHolder = focusedNode.parentNode;\r\n focusedNode = focusedNodeHolder;\r\n\r\n }\r\n\r\n /** Input index in DOM level */\r\n var editableElementIndex = 0;\r\n\r\n while (focusedNode != inputs[editableElementIndex]) {\r\n\r\n editableElementIndex ++;\r\n\r\n }\r\n\r\n /**\r\n * Do nothing if caret is not at the beginning of first child\r\n */\r\n var caretInFirstChild = false,\r\n caretAtTheBeginning = false;\r\n\r\n var firstChild,\r\n deepestTextnode;\r\n\r\n /**\r\n * Founded contentEditable element doesn't have childs\r\n * Or maybe New created block\r\n */\r\n if (!focusedNode.textContent) {\r\n\r\n editor.caret.setToPreviousBlock(editableElementIndex);\r\n return;\r\n\r\n }\r\n\r\n firstChild = focusedNode.childNodes[0];\r\n\r\n if (editor.core.isDomNode(firstChild)) {\r\n\r\n deepestTextnode = editor.content.getDeepestTextNodeFromPosition(firstChild, 0);\r\n\r\n } else {\r\n\r\n deepestTextnode = firstChild;\r\n\r\n }\r\n\r\n caretInFirstChild = selection.anchorNode == deepestTextnode;\r\n caretAtTheBeginning = selection.anchorOffset === 0;\r\n\r\n if ( caretInFirstChild && caretAtTheBeginning ) {\r\n\r\n editor.caret.setToPreviousBlock(editableElementIndex);\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Handles backspace keydown\r\n *\r\n * @param {Element} block\r\n * @param {Object} event\r\n * @private\r\n *\r\n * @description if block is empty, delete the block and set caret to the previous block\r\n * If block is not empty, try to merge two blocks - current and previous\r\n * But it we try'n to remove first block, then we should set caret to the next block, not previous.\r\n * If we removed the last block, create new one\r\n */\r\n var backspacePressed_ = function (block, event) {\r\n\r\n var currentInputIndex = editor.caret.getCurrentInputIndex(),\r\n range,\r\n selectionLength,\r\n firstLevelBlocksCount;\r\n\r\n if (editor.core.isNativeInput(event.target)) {\r\n\r\n /** If input value is empty - remove block */\r\n if (event.target.value.trim() == '') {\r\n\r\n block.remove();\r\n\r\n } else {\r\n\r\n return;\r\n\r\n }\r\n\r\n }\r\n\r\n if (block.textContent.trim()) {\r\n\r\n range = editor.content.getRange();\r\n selectionLength = range.endOffset - range.startOffset;\r\n\r\n if (editor.caret.position.atStart() && !selectionLength && editor.state.inputs[currentInputIndex - 1]) {\r\n\r\n editor.content.mergeBlocks(currentInputIndex);\r\n\r\n } else {\r\n\r\n return;\r\n\r\n }\r\n\r\n }\r\n\r\n if (!selectionLength) {\r\n\r\n block.remove();\r\n\r\n }\r\n\r\n\r\n firstLevelBlocksCount = editor.nodes.redactor.childNodes.length;\r\n\r\n /**\r\n * If all blocks are removed\r\n */\r\n if (firstLevelBlocksCount === 0) {\r\n\r\n /** update currentNode variable */\r\n editor.content.currentNode = null;\r\n\r\n /** Inserting new empty initial block */\r\n editor.ui.addInitialBlock();\r\n\r\n /** Updating inputs state after deleting last block */\r\n editor.ui.saveInputs();\r\n\r\n /** Set to current appended block */\r\n window.setTimeout(function () {\r\n\r\n editor.caret.setToPreviousBlock(1);\r\n\r\n }, 10);\r\n\r\n } else {\r\n\r\n if (editor.caret.inputIndex !== 0) {\r\n\r\n /** Target block is not first */\r\n editor.caret.setToPreviousBlock(editor.caret.inputIndex);\r\n\r\n } else {\r\n\r\n /** If we try to delete first block */\r\n editor.caret.setToNextBlock(editor.caret.inputIndex);\r\n\r\n }\r\n\r\n }\r\n\r\n editor.toolbar.move();\r\n\r\n if (!editor.toolbar.opened) {\r\n\r\n editor.toolbar.open();\r\n\r\n }\r\n\r\n /** Updating inputs state */\r\n editor.ui.saveInputs();\r\n\r\n /** Prevent default browser behaviour */\r\n event.preventDefault();\r\n\r\n };\r\n\r\n /**\r\n * used by UI module\r\n * Clicks on block settings button\r\n *\r\n * @param {Object} event\r\n * @protected\r\n * @description Opens toolbar settings\r\n */\r\n callbacks.showSettingsButtonClicked = function (event) {\r\n\r\n /**\r\n * Get type of current block\r\n * It uses to append settings from tool.settings property.\r\n * ...\r\n * Type is stored in data-type attribute on block\r\n */\r\n var currentToolType = editor.content.currentNode.dataset.tool;\r\n\r\n editor.toolbar.settings.toggle(currentToolType);\r\n\r\n /** Close toolbox when settings button is active */\r\n editor.toolbar.toolbox.close();\r\n editor.toolbar.settings.hideRemoveActions();\r\n\r\n };\r\n\r\n return callbacks;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/callbacks.js","/**\r\n * Codex Editor Draw module\r\n *\r\n * @author Codex Team\r\n * @version 1.0.\r\n */\r\n\r\nmodule.exports = (function (draw) {\r\n\r\n /**\r\n * Base editor wrapper\r\n */\r\n draw.wrapper = function () {\r\n\r\n var wrapper = document.createElement('div');\r\n\r\n wrapper.className += 'codex-editor';\r\n\r\n return wrapper;\r\n\r\n };\r\n\r\n /**\r\n * Content-editable holder\r\n */\r\n draw.redactor = function () {\r\n\r\n var redactor = document.createElement('div');\r\n\r\n redactor.className += 'ce-redactor';\r\n\r\n return redactor;\r\n\r\n };\r\n\r\n draw.ceBlock = function () {\r\n\r\n var block = document.createElement('DIV');\r\n\r\n block.className += 'ce_block';\r\n\r\n return block;\r\n\r\n };\r\n\r\n /**\r\n * Empty toolbar with toggler\r\n */\r\n draw.toolbar = function () {\r\n\r\n var bar = document.createElement('div');\r\n\r\n bar.className += 'ce-toolbar';\r\n\r\n return bar;\r\n\r\n };\r\n\r\n draw.toolbarContent = function () {\r\n\r\n var wrapper = document.createElement('DIV');\r\n\r\n wrapper.classList.add('ce-toolbar__content');\r\n\r\n return wrapper;\r\n\r\n };\r\n\r\n /**\r\n * Inline toolbar\r\n */\r\n draw.inlineToolbar = function () {\r\n\r\n var bar = document.createElement('DIV');\r\n\r\n bar.className += 'ce-toolbar-inline';\r\n\r\n return bar;\r\n\r\n };\r\n\r\n /**\r\n * Wrapper for inline toobar buttons\r\n */\r\n draw.inlineToolbarButtons = function () {\r\n\r\n var wrapper = document.createElement('DIV');\r\n\r\n wrapper.className += 'ce-toolbar-inline__buttons';\r\n\r\n return wrapper;\r\n\r\n };\r\n\r\n /**\r\n * For some actions\r\n */\r\n draw.inlineToolbarActions = function () {\r\n\r\n var wrapper = document.createElement('DIV');\r\n\r\n wrapper.className += 'ce-toolbar-inline__actions';\r\n\r\n return wrapper;\r\n\r\n };\r\n\r\n draw.inputForLink = function () {\r\n\r\n var input = document.createElement('INPUT');\r\n\r\n input.type = 'input';\r\n input.className += 'inputForLink';\r\n input.placeholder = 'Вставьте ссылку ...';\r\n input.setAttribute('form', 'defaultForm');\r\n\r\n input.setAttribute('autofocus', 'autofocus');\r\n\r\n return input;\r\n\r\n };\r\n\r\n /**\r\n * @todo Desc\r\n */\r\n draw.blockButtons = function () {\r\n\r\n var block = document.createElement('div');\r\n\r\n block.className += 'ce-toolbar__actions';\r\n\r\n return block;\r\n\r\n };\r\n\r\n /**\r\n * Block settings panel\r\n */\r\n draw.blockSettings = function () {\r\n\r\n var settings = document.createElement('div');\r\n\r\n settings.className += 'ce-settings';\r\n\r\n return settings;\r\n\r\n };\r\n\r\n draw.defaultSettings = function () {\r\n\r\n var div = document.createElement('div');\r\n\r\n div.classList.add('ce-settings_default');\r\n\r\n return div;\r\n\r\n };\r\n\r\n draw.pluginsSettings = function () {\r\n\r\n var div = document.createElement('div');\r\n\r\n div.classList.add('ce-settings_plugin');\r\n\r\n return div;\r\n\r\n };\r\n\r\n draw.plusButton = function () {\r\n\r\n var button = document.createElement('span');\r\n\r\n button.className = 'ce-toolbar__plus';\r\n // button.innerHTML = '';\r\n\r\n return button;\r\n\r\n };\r\n\r\n /**\r\n * Settings button in toolbar\r\n */\r\n draw.settingsButton = function () {\r\n\r\n var toggler = document.createElement('span');\r\n\r\n toggler.className = 'ce-toolbar__settings-btn';\r\n\r\n /** Toggler button*/\r\n toggler.innerHTML = '';\r\n\r\n return toggler;\r\n\r\n };\r\n\r\n /**\r\n * Redactor tools wrapper\r\n */\r\n\r\n draw.toolbox = function () {\r\n\r\n var wrapper = document.createElement('div');\r\n\r\n wrapper.className = 'ce-toolbar__tools';\r\n\r\n return wrapper;\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n *\r\n * Draws tool buttons for toolbox\r\n *\r\n * @param {String} type\r\n * @param {String} classname\r\n * @returns {Element}\r\n */\r\n draw.toolbarButton = function (type, classname) {\r\n\r\n var button = document.createElement('li'),\r\n toolIcon = document.createElement('i'),\r\n toolTitle = document.createElement('span');\r\n\r\n button.dataset.type = type;\r\n button.setAttribute('title', type);\r\n\r\n toolIcon.classList.add(classname);\r\n toolTitle.classList.add('ce_toolbar_tools--title');\r\n\r\n\r\n button.appendChild(toolIcon);\r\n button.appendChild(toolTitle);\r\n\r\n return button;\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n *\r\n * Draws tools for inline toolbar\r\n *\r\n * @param {String} type\r\n * @param {String} classname\r\n */\r\n draw.toolbarButtonInline = function (type, classname) {\r\n\r\n var button = document.createElement('BUTTON'),\r\n toolIcon = document.createElement('I');\r\n\r\n button.type = 'button';\r\n button.dataset.type = type;\r\n toolIcon.classList.add(classname);\r\n\r\n button.appendChild(toolIcon);\r\n\r\n return button;\r\n\r\n };\r\n\r\n /**\r\n * Redactor block\r\n */\r\n draw.block = function (tagName, content) {\r\n\r\n var node = document.createElement(tagName);\r\n\r\n node.innerHTML = content || '';\r\n\r\n return node;\r\n\r\n };\r\n\r\n /**\r\n * Creates Node with passed tagName and className\r\n * @param {string} tagName\r\n * @param {string} className\r\n * @param {object} properties - allow to assign properties\r\n */\r\n draw.node = function ( tagName, className, properties ) {\r\n\r\n var el = document.createElement( tagName );\r\n\r\n if ( className ) el.className = className;\r\n\r\n if ( properties ) {\r\n\r\n for (var name in properties) {\r\n\r\n el[name] = properties[name];\r\n\r\n }\r\n\r\n }\r\n\r\n return el;\r\n\r\n };\r\n\r\n /**\r\n * Unavailable plugin block\r\n */\r\n draw.unavailableBlock = function () {\r\n\r\n var wrapper = document.createElement('DIV');\r\n\r\n wrapper.classList.add('cdx-unavailable-block');\r\n\r\n return wrapper;\r\n\r\n };\r\n\r\n return draw;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/draw.js","/**\r\n * Codex Editor Caret Module\r\n *\r\n * @author Codex Team\r\n * @version 1.0\r\n */\r\n\r\nmodule.exports = (function (caret) {\r\n\r\n let editor = codex.editor;\r\n\r\n /**\r\n * @var {int} InputIndex - editable element in DOM\r\n */\r\n caret.inputIndex = null;\r\n\r\n /**\r\n * @var {int} offset - caret position in a text node.\r\n */\r\n caret.offset = null;\r\n\r\n /**\r\n * @var {int} focusedNodeIndex - we get index of child node from first-level block\r\n */\r\n caret.focusedNodeIndex = null;\r\n\r\n /**\r\n * Creates Document Range and sets caret to the element.\r\n * @protected\r\n * @uses caret.save — if you need to save caret position\r\n * @param {Element} el - Changed Node.\r\n */\r\n caret.set = function ( el, index, offset) {\r\n\r\n offset = offset || caret.offset || 0;\r\n index = index || caret.focusedNodeIndex || 0;\r\n\r\n var childs = el.childNodes,\r\n nodeToSet;\r\n\r\n if ( childs.length === 0 ) {\r\n\r\n nodeToSet = el;\r\n\r\n } else {\r\n\r\n nodeToSet = childs[index];\r\n\r\n }\r\n\r\n /** If Element is INPUT */\r\n if (el.contentEditable != 'true') {\r\n\r\n el.focus();\r\n return;\r\n\r\n }\r\n\r\n if (editor.core.isDomNode(nodeToSet)) {\r\n\r\n nodeToSet = editor.content.getDeepestTextNodeFromPosition(nodeToSet, nodeToSet.childNodes.length);\r\n\r\n }\r\n\r\n var range = document.createRange(),\r\n selection = window.getSelection();\r\n\r\n window.setTimeout(function () {\r\n\r\n range.setStart(nodeToSet, offset);\r\n range.setEnd(nodeToSet, offset);\r\n\r\n selection.removeAllRanges();\r\n selection.addRange(range);\r\n\r\n editor.caret.saveCurrentInputIndex();\r\n\r\n }, 20);\r\n\r\n };\r\n\r\n /**\r\n * @protected\r\n * Updates index of input and saves it in caret object\r\n */\r\n caret.saveCurrentInputIndex = function () {\r\n\r\n /** Index of Input that we paste sanitized content */\r\n var selection = window.getSelection(),\r\n inputs = editor.state.inputs,\r\n focusedNode = selection.anchorNode,\r\n focusedNodeHolder;\r\n\r\n if (!focusedNode) {\r\n\r\n return;\r\n\r\n }\r\n\r\n /** Looking for parent contentEditable block */\r\n while (focusedNode.contentEditable != 'true') {\r\n\r\n focusedNodeHolder = focusedNode.parentNode;\r\n focusedNode = focusedNodeHolder;\r\n\r\n }\r\n\r\n /** Input index in DOM level */\r\n var editableElementIndex = 0;\r\n\r\n while (focusedNode != inputs[editableElementIndex]) {\r\n\r\n editableElementIndex ++;\r\n\r\n }\r\n\r\n caret.inputIndex = editableElementIndex;\r\n\r\n };\r\n\r\n /**\r\n * Returns current input index (caret object)\r\n */\r\n caret.getCurrentInputIndex = function () {\r\n\r\n return caret.inputIndex;\r\n\r\n };\r\n\r\n /**\r\n * @param {int} index - index of first-level block after that we set caret into next input\r\n */\r\n caret.setToNextBlock = function (index) {\r\n\r\n var inputs = editor.state.inputs,\r\n nextInput = inputs[index + 1];\r\n\r\n if (!nextInput) {\r\n\r\n editor.core.log('We are reached the end');\r\n return;\r\n\r\n }\r\n\r\n /**\r\n * When new Block created or deleted content of input\r\n * We should add some text node to set caret\r\n */\r\n if (!nextInput.childNodes.length) {\r\n\r\n var emptyTextElement = document.createTextNode('');\r\n\r\n nextInput.appendChild(emptyTextElement);\r\n\r\n }\r\n\r\n editor.caret.inputIndex = index + 1;\r\n editor.caret.set(nextInput, 0, 0);\r\n editor.content.workingNodeChanged(nextInput);\r\n\r\n };\r\n\r\n /**\r\n * @param {int} index - index of target input.\r\n * Sets caret to input with this index\r\n */\r\n caret.setToBlock = function (index) {\r\n\r\n var inputs = editor.state.inputs,\r\n targetInput = inputs[index];\r\n\r\n if ( !targetInput ) {\r\n\r\n return;\r\n\r\n }\r\n\r\n /**\r\n * When new Block created or deleted content of input\r\n * We should add some text node to set caret\r\n */\r\n if (!targetInput.childNodes.length) {\r\n\r\n var emptyTextElement = document.createTextNode('');\r\n\r\n targetInput.appendChild(emptyTextElement);\r\n\r\n }\r\n\r\n editor.caret.inputIndex = index;\r\n editor.caret.set(targetInput, 0, 0);\r\n editor.content.workingNodeChanged(targetInput);\r\n\r\n };\r\n\r\n /**\r\n * @param {int} index - index of input\r\n */\r\n caret.setToPreviousBlock = function (index) {\r\n\r\n index = index || 0;\r\n\r\n var inputs = editor.state.inputs,\r\n previousInput = inputs[index - 1],\r\n lastChildNode,\r\n lengthOfLastChildNode,\r\n emptyTextElement;\r\n\r\n\r\n if (!previousInput) {\r\n\r\n editor.core.log('We are reached first node');\r\n return;\r\n\r\n }\r\n\r\n lastChildNode = editor.content.getDeepestTextNodeFromPosition(previousInput, previousInput.childNodes.length);\r\n lengthOfLastChildNode = lastChildNode.length;\r\n\r\n /**\r\n * When new Block created or deleted content of input\r\n * We should add some text node to set caret\r\n */\r\n if (!previousInput.childNodes.length) {\r\n\r\n emptyTextElement = document.createTextNode('');\r\n previousInput.appendChild(emptyTextElement);\r\n\r\n }\r\n editor.caret.inputIndex = index - 1;\r\n editor.caret.set(previousInput, previousInput.childNodes.length - 1, lengthOfLastChildNode);\r\n editor.content.workingNodeChanged(inputs[index - 1]);\r\n\r\n };\r\n\r\n caret.position = {\r\n\r\n atStart : function () {\r\n\r\n var selection = window.getSelection(),\r\n anchorOffset = selection.anchorOffset,\r\n anchorNode = selection.anchorNode,\r\n firstLevelBlock = editor.content.getFirstLevelBlock(anchorNode),\r\n pluginsRender = firstLevelBlock.childNodes[0];\r\n\r\n if (!editor.core.isDomNode(anchorNode)) {\r\n\r\n anchorNode = anchorNode.parentNode;\r\n\r\n }\r\n\r\n var isFirstNode = anchorNode === pluginsRender.childNodes[0],\r\n isOffsetZero = anchorOffset === 0;\r\n\r\n return isFirstNode && isOffsetZero;\r\n\r\n },\r\n\r\n atTheEnd : function () {\r\n\r\n var selection = window.getSelection(),\r\n anchorOffset = selection.anchorOffset,\r\n anchorNode = selection.anchorNode;\r\n\r\n /** Caret is at the end of input */\r\n return !anchorNode || !anchorNode.length || anchorOffset === anchorNode.length;\r\n\r\n }\r\n };\r\n\r\n\r\n /**\r\n * Inserts node at the caret location\r\n * @param {HTMLElement|DocumentFragment} node\r\n */\r\n caret.insertNode = function (node) {\r\n\r\n var selection, range,\r\n lastNode = node;\r\n\r\n if (node.nodeType == editor.core.nodeTypes.DOCUMENT_FRAGMENT) {\r\n\r\n lastNode = node.lastChild;\r\n\r\n }\r\n\r\n selection = window.getSelection();\r\n\r\n range = selection.getRangeAt(0);\r\n range.deleteContents();\r\n\r\n range.insertNode(node);\r\n\r\n range.setStartAfter(lastNode);\r\n range.collapse(true);\r\n\r\n selection.removeAllRanges();\r\n selection.addRange(range);\r\n\r\n\r\n };\r\n\r\n return caret;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/caret.js","/**\r\n * Codex Editor Notification Module\r\n *\r\n * @author Codex Team\r\n * @version 1.0\r\n */\r\n\r\nmodule.exports = (function (notifications) {\r\n\r\n let editor = codex.editor;\r\n\r\n var queue = [];\r\n\r\n var addToQueue = function (settings) {\r\n\r\n queue.push(settings);\r\n\r\n var index = 0;\r\n\r\n while ( index < queue.length && queue.length > 5) {\r\n\r\n if (queue[index].type == 'confirm' || queue[index].type == 'prompt') {\r\n\r\n index++;\r\n continue;\r\n\r\n }\r\n\r\n queue[index].close();\r\n queue.splice(index, 1);\r\n\r\n }\r\n\r\n };\r\n\r\n notifications.createHolder = function () {\r\n\r\n var holder = editor.draw.node('DIV', 'cdx-notifications-block');\r\n\r\n editor.nodes.notifications = document.body.appendChild(holder);\r\n\r\n return holder;\r\n\r\n };\r\n\r\n\r\n /**\r\n * Error notificator. Shows block with message\r\n * @protected\r\n */\r\n notifications.errorThrown = function (errorMsg, event) {\r\n\r\n editor.notifications.notification({message: 'This action is not available currently', type: event.type});\r\n\r\n };\r\n\r\n /**\r\n *\r\n * Appends notification\r\n *\r\n * settings = {\r\n * type - notification type (reserved types: alert, confirm, prompt). Just add class 'cdx-notification-'+type\r\n * message - notification message\r\n * okMsg - confirm button text (default - 'Ok')\r\n * cancelBtn - cancel button text (default - 'Cancel'). Only for confirm and prompt types\r\n * confirm - function-handler for ok button click\r\n * cancel - function-handler for cancel button click. Only for confirm and prompt types\r\n * time - time (in seconds) after which notification will close (default - 10s)\r\n * }\r\n *\r\n * @param settings\r\n */\r\n notifications.notification = function (constructorSettings) {\r\n\r\n /** Private vars and methods */\r\n var notification = null,\r\n cancel = null,\r\n type = null,\r\n confirm = null,\r\n inputField = null;\r\n\r\n var confirmHandler = function () {\r\n\r\n close();\r\n\r\n if (typeof confirm !== 'function' ) {\r\n\r\n return;\r\n\r\n }\r\n\r\n if (type == 'prompt') {\r\n\r\n confirm(inputField.value);\r\n return;\r\n\r\n }\r\n\r\n confirm();\r\n\r\n };\r\n\r\n var cancelHandler = function () {\r\n\r\n close();\r\n\r\n if (typeof cancel !== 'function' ) {\r\n\r\n return;\r\n\r\n }\r\n\r\n cancel();\r\n\r\n };\r\n\r\n\r\n /** Public methods */\r\n function create(settings) {\r\n\r\n if (!(settings && settings.message)) {\r\n\r\n editor.core.log('Can\\'t create notification. Message is missed');\r\n return;\r\n\r\n }\r\n\r\n settings.type = settings.type || 'alert';\r\n settings.time = settings.time*1000 || 10000;\r\n\r\n var wrapper = editor.draw.node('DIV', 'cdx-notification'),\r\n message = editor.draw.node('DIV', 'cdx-notification__message'),\r\n input = editor.draw.node('INPUT', 'cdx-notification__input'),\r\n okBtn = editor.draw.node('SPAN', 'cdx-notification__ok-btn'),\r\n cancelBtn = editor.draw.node('SPAN', 'cdx-notification__cancel-btn');\r\n\r\n message.textContent = settings.message;\r\n okBtn.textContent = settings.okMsg || 'ОК';\r\n cancelBtn.textContent = settings.cancelMsg || 'Отмена';\r\n\r\n editor.listeners.add(okBtn, 'click', confirmHandler);\r\n editor.listeners.add(cancelBtn, 'click', cancelHandler);\r\n\r\n wrapper.appendChild(message);\r\n\r\n if (settings.type == 'prompt') {\r\n\r\n wrapper.appendChild(input);\r\n\r\n }\r\n\r\n wrapper.appendChild(okBtn);\r\n\r\n if (settings.type == 'prompt' || settings.type == 'confirm') {\r\n\r\n wrapper.appendChild(cancelBtn);\r\n\r\n }\r\n\r\n wrapper.classList.add('cdx-notification-' + settings.type);\r\n wrapper.dataset.type = settings.type;\r\n\r\n notification = wrapper;\r\n type = settings.type;\r\n confirm = settings.confirm;\r\n cancel = settings.cancel;\r\n inputField = input;\r\n\r\n if (settings.type != 'prompt' && settings.type != 'confirm') {\r\n\r\n window.setTimeout(close, settings.time);\r\n\r\n }\r\n\r\n };\r\n\r\n function send() {\r\n\r\n editor.nodes.notifications.appendChild(notification);\r\n inputField.focus();\r\n\r\n editor.nodes.notifications.classList.add('cdx-notification__notification-appending');\r\n\r\n window.setTimeout(function () {\r\n\r\n editor.nodes.notifications.classList.remove('cdx-notification__notification-appending');\r\n\r\n }, 100);\r\n\r\n addToQueue({type: type, close: close});\r\n\r\n };\r\n\r\n function close() {\r\n\r\n notification.remove();\r\n\r\n };\r\n\r\n\r\n if (constructorSettings) {\r\n\r\n create(constructorSettings);\r\n send();\r\n\r\n }\r\n\r\n return {\r\n create: create,\r\n send: send,\r\n close: close\r\n };\r\n\r\n };\r\n\r\n notifications.clear = function () {\r\n\r\n editor.nodes.notifications.innerHTML = '';\r\n queue = [];\r\n\r\n };\r\n\r\n return notifications;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/notifications.js","/**\r\n * Codex Editor Parser Module\r\n *\r\n * @author Codex Team\r\n * @version 1.1\r\n */\r\n\r\nmodule.exports = (function (parser) {\r\n\r\n let editor = codex.editor;\r\n\r\n /** inserting text */\r\n parser.insertPastedContent = function (blockType, tag) {\r\n\r\n editor.content.insertBlock({\r\n type : blockType.type,\r\n block : blockType.render({\r\n text : tag.innerHTML\r\n })\r\n });\r\n\r\n };\r\n\r\n /**\r\n * Check DOM node for display style: separated block or child-view\r\n */\r\n parser.isFirstLevelBlock = function (node) {\r\n\r\n return node.nodeType == editor.core.nodeTypes.TAG &&\r\n node.classList.contains(editor.ui.className.BLOCK_CLASSNAME);\r\n\r\n };\r\n\r\n return parser;\r\n\r\n})({});\r\n\n\n\n// WEBPACK FOOTER //\n// ./modules/parser.js","/**\r\n * Codex Sanitizer\r\n */\r\n\r\nmodule.exports = (function (sanitizer) {\r\n\r\n /** HTML Janitor library */\r\n let janitor = require('html-janitor');\r\n\r\n /** Codex Editor */\r\n let editor = codex.editor;\r\n\r\n sanitizer.prepare = function () {\r\n\r\n if (editor.settings.sanitizer && !editor.core.isEmpty(editor.settings.sanitizer)) {\r\n\r\n Config.CUSTOM = editor.settings.sanitizer;\r\n\r\n }\r\n\r\n };\r\n\r\n /**\r\n * Basic config\r\n */\r\n var Config = {\r\n\r\n /** User configuration */\r\n CUSTOM : null,\r\n\r\n BASIC : {\r\n\r\n tags: {\r\n p: {},\r\n a: {\r\n href: true,\r\n target: '_blank',\r\n rel: 'nofollow'\r\n },\r\n i: {},\r\n b: {},\r\n strong: {},\r\n em: {},\r\n span: {}\r\n }\r\n }\r\n };\r\n\r\n sanitizer.Config = Config;\r\n\r\n /**\r\n *\r\n * @param userCustomConfig\r\n * @returns {*}\r\n * @private\r\n *\r\n * @description If developer uses editor's API, then he can customize sane restrictions.\r\n * Or, sane config can be defined globally in editors initialization. That config will be used everywhere\r\n * At least, if there is no config overrides, that API uses BASIC Default configation\r\n */\r\n let init_ = function (userCustomConfig) {\r\n\r\n let configuration = userCustomConfig || Config.CUSTOM || Config.BASIC;\r\n\r\n return new janitor(configuration);\r\n\r\n };\r\n\r\n /**\r\n * Cleans string from unwanted tags\r\n * @protected\r\n * @param {String} dirtyString - taint string\r\n * @param {Object} customConfig - allowed tags\r\n */\r\n sanitizer.clean = function (dirtyString, customConfig) {\r\n\r\n let janitorInstance = init_(customConfig);\r\n\r\n return janitorInstance.clean(dirtyString);\r\n\r\n };\r\n\r\n return sanitizer;\r\n\r\n})({});\n\n\n// WEBPACK FOOTER //\n// ./modules/sanitizer.js","(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n define('html-janitor', factory);\n } else if (typeof exports === 'object') {\n module.exports = factory();\n } else {\n root.HTMLJanitor = factory();\n }\n}(this, function () {\n\n /**\n * @param {Object} config.tags Dictionary of allowed tags.\n * @param {boolean} config.keepNestedBlockElements Default false.\n */\n function HTMLJanitor(config) {\n\n var tagDefinitions = config['tags'];\n var tags = Object.keys(tagDefinitions);\n\n var validConfigValues = tags\n .map(function(k) { return typeof tagDefinitions[k]; })\n .every(function(type) { return type === 'object' || type === 'boolean' || type === 'function'; });\n\n if(!validConfigValues) {\n throw new Error(\"The configuration was invalid\");\n }\n\n this.config = config;\n }\n\n // TODO: not exhaustive?\n var blockElementNames = ['P', 'LI', 'TD', 'TH', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'PRE'];\n function isBlockElement(node) {\n return blockElementNames.indexOf(node.nodeName) !== -1;\n }\n\n var inlineElementNames = ['A', 'B', 'STRONG', 'I', 'EM', 'SUB', 'SUP', 'U', 'STRIKE'];\n function isInlineElement(node) {\n return inlineElementNames.indexOf(node.nodeName) !== -1;\n }\n\n HTMLJanitor.prototype.clean = function (html) {\n var sandbox = document.createElement('div');\n sandbox.innerHTML = html;\n\n this._sanitize(sandbox);\n\n return sandbox.innerHTML;\n };\n\n HTMLJanitor.prototype._sanitize = function (parentNode) {\n var treeWalker = createTreeWalker(parentNode);\n var node = treeWalker.firstChild();\n if (!node) { return; }\n\n do {\n // Ignore nodes that have already been sanitized\n if (node._sanitized) {\n continue;\n }\n\n if (node.nodeType === Node.TEXT_NODE) {\n // If this text node is just whitespace and the previous or next element\n // sibling is a block element, remove it\n // N.B.: This heuristic could change. Very specific to a bug with\n // `contenteditable` in Firefox: http://jsbin.com/EyuKase/1/edit?js,output\n // FIXME: make this an option?\n if (node.data.trim() === ''\n && ((node.previousElementSibling && isBlockElement(node.previousElementSibling))\n || (node.nextElementSibling && isBlockElement(node.nextElementSibling)))) {\n parentNode.removeChild(node);\n this._sanitize(parentNode);\n break;\n } else {\n continue;\n }\n }\n\n // Remove all comments\n if (node.nodeType === Node.COMMENT_NODE) {\n parentNode.removeChild(node);\n this._sanitize(parentNode);\n break;\n }\n\n var isInline = isInlineElement(node);\n var containsBlockElement;\n if (isInline) {\n containsBlockElement = Array.prototype.some.call(node.childNodes, isBlockElement);\n }\n\n // Block elements should not be nested (e.g.

  • ...); if\n // they are, we want to unwrap the inner block element.\n var isNotTopContainer = !! parentNode.parentNode;\n var isNestedBlockElement =\n isBlockElement(parentNode) &&\n isBlockElement(node) &&\n isNotTopContainer;\n\n var nodeName = node.nodeName.toLowerCase();\n\n var allowedAttrs = getAllowedAttrs(this.config, nodeName, node);\n\n var isInvalid = isInline && containsBlockElement;\n\n // Drop tag entirely according to the whitelist *and* if the markup\n // is invalid.\n if (isInvalid || shouldRejectNode(node, allowedAttrs)\n || (!this.config.keepNestedBlockElements && isNestedBlockElement)) {\n // Do not keep the inner text of SCRIPT/STYLE elements.\n if (! (node.nodeName === 'SCRIPT' || node.nodeName === 'STYLE')) {\n while (node.childNodes.length > 0) {\n parentNode.insertBefore(node.childNodes[0], node);\n }\n }\n parentNode.removeChild(node);\n\n this._sanitize(parentNode);\n break;\n }\n\n // Sanitize attributes\n for (var a = 0; a < node.attributes.length; a += 1) {\n var attr = node.attributes[a];\n\n if (shouldRejectAttr(attr, allowedAttrs, node)) {\n node.removeAttribute(attr.name);\n // Shift the array to continue looping.\n a = a - 1;\n }\n }\n\n // Sanitize children\n this._sanitize(node);\n\n // Mark node as sanitized so it's ignored in future runs\n node._sanitized = true;\n } while ((node = treeWalker.nextSibling()));\n };\n\n function createTreeWalker(node) {\n return document.createTreeWalker(node,\n NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT,\n null, false);\n }\n\n function getAllowedAttrs(config, nodeName, node){\n if (typeof config.tags[nodeName] === 'function') {\n return config.tags[nodeName](node);\n } else {\n return config.tags[nodeName];\n }\n }\n\n function shouldRejectNode(node, allowedAttrs){\n if (typeof allowedAttrs === 'undefined') {\n return true;\n } else if (typeof allowedAttrs === 'boolean') {\n return !allowedAttrs;\n }\n\n return false;\n }\n\n function shouldRejectAttr(attr, allowedAttrs, node){\n var attrName = attr.name.toLowerCase();\n\n if (allowedAttrs === true){\n return false;\n } else if (typeof allowedAttrs[attrName] === 'function'){\n return !allowedAttrs[attrName](attr.value, node);\n } else if (typeof allowedAttrs[attrName] === 'undefined'){\n return true;\n } else if (allowedAttrs[attrName] === false) {\n return true;\n } else if (typeof allowedAttrs[attrName] === 'string') {\n return (allowedAttrs[attrName] !== attr.value);\n }\n\n return false;\n }\n\n return HTMLJanitor;\n\n}));\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./~/html-janitor/src/html-janitor.js\n// module id = 18\n// module chunks = 0","/**\r\n * Codex Editor Listeners module\r\n *\r\n * @author Codex Team\r\n * @version 1.0\r\n */\r\n\r\n/**\r\n * Module-decorator for event listeners assignment\r\n */\r\nmodule.exports = function (listeners) {\r\n\r\n var allListeners = [];\r\n\r\n /**\r\n * Search methods\r\n *\r\n * byElement, byType and byHandler returns array of suitable listeners\r\n * one and all takes element, eventType, and handler and returns first (all) suitable listener\r\n *\r\n */\r\n listeners.search = function () {\r\n\r\n var byElement = function (element, context) {\r\n\r\n var listenersOnElement = [];\r\n\r\n context = context || allListeners;\r\n\r\n for (var i = 0; i < context.length; i++) {\r\n\r\n var listener = context[i];\r\n\r\n if (listener.element === element) {\r\n\r\n listenersOnElement.push(listener);\r\n\r\n }\r\n\r\n }\r\n\r\n return listenersOnElement;\r\n\r\n };\r\n\r\n var byType = function (eventType, context) {\r\n\r\n var listenersWithType = [];\r\n\r\n context = context || allListeners;\r\n\r\n for (var i = 0; i < context.length; i++) {\r\n\r\n var listener = context[i];\r\n\r\n if (listener.type === eventType) {\r\n\r\n listenersWithType.push(listener);\r\n\r\n }\r\n\r\n }\r\n\r\n return listenersWithType;\r\n\r\n };\r\n\r\n var byHandler = function (handler, context) {\r\n\r\n var listenersWithHandler = [];\r\n\r\n context = context || allListeners;\r\n\r\n for (var i = 0; i < context.length; i++) {\r\n\r\n var listener = context[i];\r\n\r\n if (listener.handler === handler) {\r\n\r\n listenersWithHandler.push(listener);\r\n\r\n }\r\n\r\n }\r\n\r\n return listenersWithHandler;\r\n\r\n };\r\n\r\n var one = function (element, eventType, handler) {\r\n\r\n var result = allListeners;\r\n\r\n if (element)\r\n result = byElement(element, result);\r\n\r\n if (eventType)\r\n result = byType(eventType, result);\r\n\r\n if (handler)\r\n result = byHandler(handler, result);\r\n\r\n return result[0];\r\n\r\n };\r\n\r\n var all = function (element, eventType, handler) {\r\n\r\n var result = allListeners;\r\n\r\n if (element)\r\n result = byElement(element, result);\r\n\r\n if (eventType)\r\n result = byType(eventType, result);\r\n\r\n if (handler)\r\n result = byHandler(handler, result);\r\n\r\n return result;\r\n\r\n };\r\n\r\n return {\r\n byElement : byElement,\r\n byType : byType,\r\n byHandler : byHandler,\r\n one : one,\r\n all : all\r\n };\r\n\r\n }();\r\n\r\n listeners.add = function (element, eventType, handler, isCapture) {\r\n\r\n element.addEventListener(eventType, handler, isCapture);\r\n\r\n var data = {\r\n element: element,\r\n type: eventType,\r\n handler: handler\r\n };\r\n\r\n var alreadyAddedListener = listeners.search.one(element, eventType, handler);\r\n\r\n if (!alreadyAddedListener) {\r\n\r\n allListeners.push(data);\r\n\r\n }\r\n\r\n };\r\n\r\n listeners.remove = function (element, eventType, handler) {\r\n\r\n element.removeEventListener(eventType, handler);\r\n\r\n var existingListeners = listeners.search.all(element, eventType, handler);\r\n\r\n for (var i = 0; i < existingListeners.length; i++) {\r\n\r\n var index = allListeners.indexOf(existingListeners[i]);\r\n\r\n if (index > 0) {\r\n\r\n allListeners.splice(index, 1);\r\n\r\n }\r\n\r\n }\r\n\r\n };\r\n\r\n listeners.removeAll = function () {\r\n\r\n allListeners.map(function (current) {\r\n\r\n listeners.remove(current.element, current.type, current.handler);\r\n\r\n });\r\n\r\n };\r\n\r\n listeners.get = function (element, eventType, handler) {\r\n\r\n return listeners.search.all(element, eventType, handler);\r\n\r\n };\r\n\r\n return listeners;\r\n\r\n}({});\n\n\n// WEBPACK FOOTER //\n// ./modules/listeners.js","/**\r\n * Codex Editor Destroyer module\r\n *\r\n * @auhor Codex Team\r\n * @version 1.0\r\n */\r\n\r\nmodule.exports = function (destroyer) {\r\n\r\n let editor = codex.editor;\r\n\r\n destroyer.removeNodes = function () {\r\n\r\n editor.nodes.wrapper.remove();\r\n editor.nodes.notifications.remove();\r\n\r\n };\r\n\r\n destroyer.destroyPlugins = function () {\r\n\r\n for (var tool in editor.tools) {\r\n\r\n if (typeof editor.tools[tool].destroy === 'function') {\r\n\r\n editor.tools[tool].destroy();\r\n\r\n }\r\n\r\n }\r\n\r\n };\r\n\r\n destroyer.destroyScripts = function () {\r\n\r\n var scripts = document.getElementsByTagName('SCRIPT');\r\n\r\n for (var i = 0; i < scripts.length; i++) {\r\n\r\n if (scripts[i].id.indexOf(editor.scriptPrefix) + 1) {\r\n\r\n scripts[i].remove();\r\n i--;\r\n\r\n }\r\n\r\n }\r\n\r\n };\r\n\r\n\r\n /**\r\n * Delete editor data from webpage.\r\n * You should send settings argument with boolean flags:\r\n * @param settings.ui- remove redactor event listeners and DOM nodes\r\n * @param settings.scripts - remove redactor scripts from DOM\r\n * @param settings.plugins - remove plugin's objects\r\n * @param settings.core - remove editor core. You can remove core only if UI and scripts flags is true\r\n * }\r\n *\r\n */\r\n destroyer.destroy = function (settings) {\r\n\r\n if (!settings || typeof settings !== 'object') {\r\n\r\n return;\r\n\r\n }\r\n\r\n if (settings.ui) {\r\n\r\n destroyer.removeNodes();\r\n editor.listeners.removeAll();\r\n\r\n }\r\n\r\n if (settings.scripts) {\r\n\r\n destroyer.destroyScripts();\r\n\r\n }\r\n\r\n if (settings.plugins) {\r\n\r\n destroyer.destroyPlugins();\r\n\r\n }\r\n\r\n if (settings.ui && settings.scripts && settings.core) {\r\n\r\n delete codex.editor;\r\n\r\n }\r\n\r\n };\r\n\r\n return destroyer;\r\n\r\n}({});\n\n\n// WEBPACK FOOTER //\n// ./modules/destroyer.js","/**\r\n * Codex Editor Paste module\r\n *\r\n * @author Codex Team\r\n * @version 1.1.1\r\n */\r\n\r\nmodule.exports = function (paste) {\r\n\r\n let editor = codex.editor;\r\n\r\n var patterns = [];\r\n\r\n paste.prepare = function () {\r\n\r\n var tools = editor.tools;\r\n\r\n for (var tool in tools) {\r\n\r\n if (!tools[tool].renderOnPastePatterns || !Array.isArray(tools[tool].renderOnPastePatterns)) {\r\n\r\n continue;\r\n\r\n }\r\n\r\n tools[tool].renderOnPastePatterns.map(function (pattern) {\r\n\r\n\r\n patterns.push(pattern);\r\n\r\n });\r\n\r\n }\r\n\r\n return Promise.resolve();\r\n\r\n };\r\n\r\n /**\r\n * Saves data\r\n * @param event\r\n */\r\n paste.pasted = function (event) {\r\n\r\n var clipBoardData = event.clipboardData || window.clipboardData,\r\n content = clipBoardData.getData('Text');\r\n\r\n var result = analize(content);\r\n\r\n if (result) {\r\n\r\n event.preventDefault();\r\n event.stopImmediatePropagation();\r\n\r\n }\r\n\r\n return result;\r\n\r\n };\r\n\r\n /**\r\n * Analizes pated string and calls necessary method\r\n */\r\n\r\n var analize = function (string) {\r\n\r\n var result = false,\r\n content = editor.content.currentNode,\r\n plugin = content.dataset.tool;\r\n\r\n patterns.map( function (pattern) {\r\n\r\n var execArray = pattern.regex.exec(string),\r\n match = execArray && execArray[0];\r\n\r\n if ( match && match === string.trim()) {\r\n\r\n /** current block is not empty */\r\n if ( content.textContent.trim() && plugin == editor.settings.initialBlockPlugin ) {\r\n\r\n pasteToNewBlock_();\r\n\r\n }\r\n\r\n pattern.callback(string, pattern);\r\n result = true;\r\n\r\n }\r\n\r\n });\r\n\r\n return result;\r\n\r\n };\r\n\r\n var pasteToNewBlock_ = function () {\r\n\r\n /** Create new initial block */\r\n editor.content.insertBlock({\r\n\r\n type : editor.settings.initialBlockPlugin,\r\n block : editor.tools[editor.settings.initialBlockPlugin].render({\r\n text : ''\r\n })\r\n\r\n }, false);\r\n\r\n };\r\n\r\n /**\r\n * This method prevents default behaviour.\r\n *\r\n * @param {Object} event\r\n * @protected\r\n *\r\n * @description We get from clipboard pasted data, sanitize, make a fragment that contains of this sanitized nodes.\r\n * Firstly, we need to memorize the caret position. We can do that by getting the range of selection.\r\n * After all, we insert clear fragment into caret placed position. Then, we should move the caret to the last node\r\n */\r\n paste.blockPasteCallback = function (event) {\r\n\r\n\r\n if (!needsToHandlePasteEvent(event.target)) {\r\n\r\n return;\r\n\r\n }\r\n\r\n /** Prevent default behaviour */\r\n event.preventDefault();\r\n\r\n /** get html pasted data - dirty data */\r\n var htmlData = event.clipboardData.getData('text/html'),\r\n plainData = event.clipboardData.getData('text/plain');\r\n\r\n /** Temporary DIV that is used to work with text's paragraphs as DOM-elements*/\r\n var paragraphs = editor.draw.node('DIV', '', {}),\r\n cleanData,\r\n wrappedData;\r\n\r\n /** Create fragment, that we paste to range after proccesing */\r\n cleanData = editor.sanitizer.clean(htmlData);\r\n\r\n /**\r\n * We wrap pasted text with

    tags to split it logically\r\n * @type {string}\r\n */\r\n wrappedData = editor.content.wrapTextWithParagraphs(cleanData, plainData);\r\n paragraphs.innerHTML = wrappedData;\r\n\r\n /**\r\n * If there only one paragraph, just insert in at the caret location\r\n */\r\n if (paragraphs.childNodes.length == 1) {\r\n\r\n emulateUserAgentBehaviour(paragraphs.firstChild);\r\n return;\r\n\r\n }\r\n\r\n insertPastedParagraphs(paragraphs.childNodes);\r\n\r\n };\r\n\r\n /**\r\n * Checks if we should handle paste event on block\r\n * @param block\r\n *\r\n * @return {boolean}\r\n */\r\n var needsToHandlePasteEvent = function (block) {\r\n\r\n /** If area is input or textarea then allow default behaviour */\r\n if ( editor.core.isNativeInput(block) ) {\r\n\r\n return false;\r\n\r\n }\r\n\r\n var editableParent = editor.content.getEditableParent(block);\r\n\r\n /** Allow paste when event target placed in Editable element */\r\n if (!editableParent) {\r\n\r\n return false;\r\n\r\n }\r\n\r\n return true;\r\n\r\n };\r\n\r\n /**\r\n * Inserts new initial plugin blocks with data in paragraphs\r\n *\r\n * @param {Array} paragraphs - array of paragraphs (

    ) whit content, that should be inserted\r\n */\r\n var insertPastedParagraphs = function (paragraphs) {\r\n\r\n var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;\r\n\r\n paragraphs.forEach(function (paragraph, index) {\r\n\r\n /** Don't allow empty paragraphs */\r\n if (editor.core.isBlockEmpty(paragraph)) {\r\n\r\n return;\r\n\r\n }\r\n\r\n /**\r\n * If there was no data in working node, replace it with first paragraph of pasted text\r\n */\r\n if (index == 0 && editor.core.isBlockEmpty(editor.content.currentNode)) {\r\n\r\n editor.content.currentNode.remove();\r\n editor.caret.setToPreviousBlock(editor.caret.inputIndex);\r\n\r\n }\r\n\r\n editor.content.insertBlock({\r\n type : NEW_BLOCK_TYPE,\r\n block : editor.tools[NEW_BLOCK_TYPE].render({\r\n text : paragraph.innerHTML\r\n })\r\n });\r\n\r\n editor.caret.inputIndex++;\r\n\r\n });\r\n\r\n editor.caret.setToPreviousBlock(editor.caret.getCurrentInputIndex() + 1);\r\n\r\n\r\n };\r\n\r\n /**\r\n * Inserts node content at the caret position\r\n *\r\n * @param {Node} node - DOM node (could be DocumentFragment), that should be inserted at the caret location\r\n */\r\n var emulateUserAgentBehaviour = function (node) {\r\n\r\n var newNode;\r\n\r\n if (node.childElementCount) {\r\n\r\n newNode = document.createDocumentFragment();\r\n\r\n node.childNodes.forEach(function (current) {\r\n\r\n if (!editor.core.isDomNode(current) && current.data.trim() === '') {\r\n\r\n return;\r\n\r\n }\r\n\r\n newNode.appendChild(current.cloneNode(true));\r\n\r\n });\r\n\r\n } else {\r\n\r\n newNode = document.createTextNode(node.textContent);\r\n\r\n }\r\n\r\n editor.caret.insertNode(newNode);\r\n\r\n };\r\n\r\n\r\n return paste;\r\n\r\n}({});\n\n\n// WEBPACK FOOTER //\n// ./modules/paste.js"],"sourceRoot":""} \ No newline at end of file diff --git a/modules/callbacks.js b/modules/callbacks.js index e04024ec..443759ba 100644 --- a/modules/callbacks.js +++ b/modules/callbacks.js @@ -71,23 +71,8 @@ module.exports = (function (callbacks) { */ event.preventDefault(); - var nativeInputs = editor.content.currentNode.querySelectorAll('textarea, input'), - nativeInputsAreEmpty = true, - textContentIsEmpty = !editor.content.currentNode.textContent.trim(); - Array.prototype.map.call(nativeInputs, function (input) { - - if (input.type == 'textarea' || input.type == 'text') { - - nativeInputsAreEmpty = nativeInputsAreEmpty && !input.value.trim(); - - } - - }); - - var blockIsEmpty = textContentIsEmpty && nativeInputsAreEmpty; - - if (!blockIsEmpty) { + if (!editor.core.isBlockEmpty(editor.content.currentNode)) { return; diff --git a/modules/caret.js b/modules/caret.js index bad17753..8c151709 100644 --- a/modules/caret.js +++ b/modules/caret.js @@ -271,14 +271,14 @@ module.exports = (function (caret) { /** * Inserts node at the caret location - * @param node + * @param {HTMLElement|DocumentFragment} node */ caret.insertNode = function (node) { var selection, range, lastNode = node; - if (node instanceof window.DocumentFragment) { + if (node.nodeType == editor.core.nodeTypes.DOCUMENT_FRAGMENT) { lastNode = node.lastChild; diff --git a/modules/core.js b/modules/core.js index 83f84df9..ceedf84c 100644 --- a/modules/core.js +++ b/modules/core.js @@ -112,7 +112,8 @@ module.exports = (function (core) { core.nodeTypes = { TAG : 1, TEXT : 3, - COMMENT : 8 + COMMENT : 8, + DOCUMENT_FRAGMENT: 11 }; /** @@ -351,6 +352,35 @@ module.exports = (function (core) { }; + /** + * Check if block is empty + * We should check block textContent, child native inputs and some exceptions like IMG and IFRAME + * + * @param block + * @returns {boolean} + */ + core.isBlockEmpty = function (block) { + + const EXCEPTION_TAGS = ['IMG', 'IFRAME']; + + var nativeInputs = block.querySelectorAll('textarea, input'), + nativeInputsAreEmpty = true, + textContentIsEmpty = !block.textContent.trim(); + + Array.prototype.forEach.call(nativeInputs, function (input) { + + if (input.type == 'textarea' || input.type == 'text') { + + nativeInputsAreEmpty = nativeInputsAreEmpty && !input.value.trim(); + + } + + }); + + return textContentIsEmpty && nativeInputsAreEmpty && !(EXCEPTION_TAGS.indexOf(block.tagName) + 1); + + }; + return core; diff --git a/modules/paste.js b/modules/paste.js index 87a733d1..f0213ea5 100644 --- a/modules/paste.js +++ b/modules/paste.js @@ -197,13 +197,12 @@ module.exports = function (paste) { */ var insertPastedParagraphs = function (paragraphs) { - var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin, - currentBlockContent = editor.content.currentNode.firstChild.firstChild; + var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin; paragraphs.forEach(function (paragraph, index) { /** Don't allow empty paragraphs */ - if (paragraph.innerHTML.trim() === '') { + if (editor.core.isBlockEmpty(paragraph)) { return; @@ -212,13 +211,10 @@ module.exports = function (paste) { /** * If there was no data in working node, replace it with first paragraph of pasted text */ - if (index == 0 && currentBlockContent.innerHTML.trim() === '') { + if (index == 0 && editor.core.isBlockEmpty(editor.content.currentNode)) { - editor.content.switchBlock(editor.content.currentNode, editor.tools[NEW_BLOCK_TYPE].render({ - text : paragraph.innerHTML - }), NEW_BLOCK_TYPE); - - return; + editor.content.currentNode.remove(); + editor.caret.setToPreviousBlock(editor.caret.inputIndex); }