var codex = /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) /******/ return installedModules[moduleId].exports; /******/ /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ exports: {}, /******/ id: moduleId, /******/ loaded: false /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.loaded = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ function(module, exports, __webpack_require__) { /** * */ 'use strict'; var editor = __webpack_require__(1); module.exports = editor; /***/ }, /* 1 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = function (codex) { var init = function init() { __webpack_require__(2); __webpack_require__(3); __webpack_require__(4); __webpack_require__(5); __webpack_require__(6); __webpack_require__(7); __webpack_require__(8); __webpack_require__(12); __webpack_require__(13); __webpack_require__(14); __webpack_require__(15); __webpack_require__(16); __webpack_require__(17); }; /** * @public * * holds initial settings */ codex.settings = { tools: ['paragraph', 'header', 'picture', 'list', 'quote', 'code', 'twitter', 'instagram', 'smile'], textareaId: 'codex-editor', uploadImagesUrl: '/editor/transport/', // Type of block showing on empty editor initialBlockPlugin: "paragraph" }; /** * public * * Static nodes */ codex.nodes = { textarea: 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: {}, // { type : DomEl, ... } redactor: null }; /** * @public * * Output state */ codex.state = { jsonOutput: [], blocks: [], inputs: [] }; /** * Initialization * @uses Promise cEditor.core.prepare * @param {} userSettings are : * - tools [], * - textareaId String * ... * * Load user defined tools * Tools must contain this important objects : * @param {String} type - this is a type of plugin. It can be used as plugin name * @param {String} iconClassname - this a icon in toolbar * @param {Object} make - what should plugin do, when it is clicked * @param {Object} appendCallback - callback after clicking * @param {Element} settings - what settings does it have * @param {Object} render - plugin get JSON, and should return HTML * @param {Object} save - plugin gets HTML content, returns JSON * @param {Boolean} displayInToolbox - will be displayed in toolbox. Default value is TRUE * @param {Boolean} enableLineBreaks - inserts new block or break lines. Default value is FALSE * * @example * - type : 'header', * - iconClassname : 'ce-icon-header', * - make : headerTool.make, * - appendCallback : headerTool.appendCallback, * - settings : headerTool.makeSettings(), * - render : headerTool.render, * - save : headerTool.save, * - displayInToolbox : true, * - enableLineBreaks : false */ codex.start = function (userSettings) { init(); this.core.prepare(userSettings) // If all ok, make UI, bind events and parse initial-content .then(this.ui.make).then(this.ui.addTools).then(this.ui.bindEvents).then(this.ui.preparePlugins).then(this.transport.prepare).then(this.renderer.makeBlocksFromData).then(this.ui.saveInputs).catch(function (error) { codex.core.log('Initialization failed with error: %o', 'warn', error); }); }; return codex; }({}); module.exports = codex; /***/ }, /* 2 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var _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; }; var codex = __webpack_require__(1); var core = function (core) { /** * @public * * Editor preparing method * @return Promise */ core.prepare = function (userSettings) { return new Promise(function (resolve, reject) { if (userSettings) { codex.settings.tools = userSettings.tools || codex.settings.tools; } if (userSettings.data) { codex.state.blocks = userSettings.data; } codex.nodes.textarea = document.getElementById(userSettings.textareaId || codex.settings.textareaId); if (_typeof(codex.nodes.textarea) === undefined || codex.nodes.textarea === null) { reject(Error("Textarea wasn't found by ID: #" + userSettings.textareaId)); } else { resolve(); } }); }; /** * Logging method * @param type = ['log', 'info', 'warn'] */ core.log = function (msg, type, arg) { type = type || 'log'; if (!arg) { arg = msg || 'undefined'; msg = '[codex-editor]: %o'; } else { msg = '[codex-editor]: ' + msg; } try { if ('console' in window && console[type]) { if (arg) console[type](msg, arg);else console[type](msg); } } catch (e) {} }; /** * @protected * * Helper for insert one element after another */ core.insertAfter = function (target, element) { target.parentNode.insertBefore(element, target.nextSibling); }; /** * @const * * Readable DOM-node types map */ core.nodeTypes = { TAG: 1, TEXT: 3, COMMENT: 8 }; /** * @const * Readable keys map */ 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 }; /** * @protected * * Check object for DOM node */ core.isDomNode = function (el) { return el && (typeof el === 'undefined' ? 'undefined' : _typeof(el)) === 'object' && el.nodeType && el.nodeType == this.nodeTypes.TAG; }; /** * Native Ajax */ core.ajax = function (data) { if (!data || !data.url) { return; } var XMLHTTP = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"), success_function = function success_function() {}, params = '', obj; data.async = true; data.type = data.type || 'GET'; data.data = data.data || ''; data['content-type'] = data['content-type'] || 'application/json; charset=utf-8'; success_function = data.success || success_function; if (data.type == 'GET' && data.data) { data.url = /\?/.test(data.url) ? data.url + '&' + data.data : data.url + '?' + data.data; } else { for (obj in data.data) { params += obj + '=' + encodeURIComponent(data.data[obj]) + '&'; } } if (data.withCredentials) { XMLHTTP.withCredentials = true; } if (data.beforeSend && typeof data.beforeSend == 'function') { data.beforeSend.call(); } XMLHTTP.open(data.type, data.url, data.async); XMLHTTP.setRequestHeader("X-Requested-With", "XMLHttpRequest"); XMLHTTP.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); XMLHTTP.onreadystatechange = function () { if (XMLHTTP.readyState == 4 && XMLHTTP.status == 200) { success_function(XMLHTTP.responseText); } }; XMLHTTP.send(params); }; /** Appends script to head of document */ core.importScript = function (scriptPath, instanceName) { /** Script is already loaded */ if (!instanceName || instanceName && document.getElementById('ce-script-' + instanceName)) { codex.core.log("Instance name of script is missed or script is already loaded", "warn"); return; } var script = document.createElement('SCRIPT'); script.type = "text/javascript"; script.src = scriptPath; script.async = true; script.defer = true; if (instanceName) { script.id = "ce-script-" + instanceName; } document.head.appendChild(script); return script; }; return core; }({}); codex.core = core; module.exports = core; /***/ }, /* 3 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = __webpack_require__(1); var ui = function (ui) { /** * Basic editor classnames */ ui.className = { /** * @const {string} BLOCK_CLASSNAME - redactor blocks name */ BLOCK_CLASSNAME: 'ce-block', /** * @const {String} wrapper for plugins content */ BLOCK_CONTENT: 'ce-block__content', /** * @const {String} BLOCK_STRETCHED - makes block stretched */ BLOCK_STRETCHED: 'ce-block--stretched', /** * @const {String} BLOCK_HIGHLIGHTED - adds background */ BLOCK_HIGHLIGHTED: 'ce-block--focused', /** * @const {String} - highlights covered blocks */ BLOCK_IN_FEED_MODE: 'ce-block--feed-mode', /** * @const {String} - for all default settings */ SETTINGS_ITEM: 'ce-settings__item' }; /** * @protected * * Making main interface */ ui.make = function () { var wrapper, toolbar, toolbarContent, inlineToolbar, redactor, ceBlock, notifications, blockButtons, blockSettings, showSettingsButton, showTrashButton, toolbox, plusButton; /** Make editor wrapper */ wrapper = codex.draw.wrapper(); /** Append editor wrapper after initial textarea */ codex.core.insertAfter(codex.nodes.textarea, wrapper); /** Append block with notifications to the document */ notifications = codex.draw.alertsHolder(); codex.nodes.notifications = document.body.appendChild(notifications); /** Make toolbar and content-editable redactor */ toolbar = codex.draw.toolbar(); toolbarContent = codex.draw.toolbarContent(); inlineToolbar = codex.draw.inlineToolbar(); plusButton = codex.draw.plusButton(); showSettingsButton = codex.draw.settingsButton(); showTrashButton = codex.toolbar.settings.makeRemoveBlockButton(); blockSettings = codex.draw.blockSettings(); blockButtons = codex.draw.blockButtons(); toolbox = codex.draw.toolbox(); redactor = codex.draw.redactor(); /** settings */ var defaultSettings = codex.draw.defaultSettings(), pluginSettings = codex.draw.pluginsSettings(); /** Add default and plugins settings */ blockSettings.appendChild(pluginSettings); blockSettings.appendChild(defaultSettings); /** Make blocks buttons * This block contains settings button and remove block button */ blockButtons.appendChild(showSettingsButton); blockButtons.appendChild(showTrashButton); blockButtons.appendChild(blockSettings); /** Append plus button */ toolbarContent.appendChild(plusButton); /** Appending toolbar tools */ toolbarContent.appendChild(toolbox); /** Appending first-level block buttons */ toolbar.appendChild(blockButtons); /** Append toolbarContent to toolbar */ toolbar.appendChild(toolbarContent); wrapper.appendChild(toolbar); wrapper.appendChild(redactor); /** Save created ui-elements to static nodes state */ codex.nodes.wrapper = wrapper; codex.nodes.toolbar = toolbar; codex.nodes.plusButton = plusButton; codex.nodes.toolbox = toolbox; codex.nodes.blockSettings = blockSettings; codex.nodes.pluginSettings = pluginSettings; codex.nodes.defaultSettings = defaultSettings; codex.nodes.showSettingsButton = showSettingsButton; codex.nodes.showTrashButton = showTrashButton; codex.nodes.redactor = redactor; codex.ui.makeInlineToolbar(inlineToolbar); /** fill in default settings */ codex.toolbar.settings.addDefaultSettings(); }; ui.makeInlineToolbar = function (container) { /** Append to redactor new inline block */ codex.nodes.inlineToolbar.wrapper = container; /** Draw toolbar buttons */ codex.nodes.inlineToolbar.buttons = codex.draw.inlineToolbarButtons(); /** Buttons action or settings */ codex.nodes.inlineToolbar.actions = codex.draw.inlineToolbarActions(); /** Append to inline toolbar buttons as part of it */ codex.nodes.inlineToolbar.wrapper.appendChild(codex.nodes.inlineToolbar.buttons); codex.nodes.inlineToolbar.wrapper.appendChild(codex.nodes.inlineToolbar.actions); codex.nodes.wrapper.appendChild(codex.nodes.inlineToolbar.wrapper); }; /** * @private * Append tools passed in codex.tools */ ui.addTools = function () { var tool, tool_button; for (var name in codex.settings.tools) { tool = codex.settings.tools[name]; codex.tools[name] = tool;; } /** Make toolbar buttons */ for (var name in codex.tools) { tool = codex.tools[name]; if (tool.displayInToolbox == false) { continue; } if (!tool.iconClassname) { codex.core.log('Toolbar icon classname missed. Tool %o skipped', 'warn', name); continue; } if (typeof tool.make != 'function') { codex.core.log('make method missed. Tool %o skipped', 'warn', name); continue; } /** * if tools is for toolbox */ tool_button = codex.draw.toolbarButton(name, tool.iconClassname); codex.nodes.toolbox.appendChild(tool_button); /** Save tools to static nodes */ codex.nodes.toolbarButtons[name] = tool_button; } /** * Add inline toolbar tools */ codex.ui.addInlineToolbarTools(); }; ui.addInlineToolbarTools = function () { var tools = { 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' } }; var toolButton, tool; for (var name in tools) { tool = tools[name]; toolButton = codex.draw.toolbarButtonInline(name, tool.icon); codex.nodes.inlineToolbar.buttons.appendChild(toolButton); /** * Add callbacks to this buttons */ codex.ui.setInlineToolbarButtonBehaviour(toolButton, tool.command); } }; /** * @private * Bind editor UI events */ ui.bindEvents = function () { codex.core.log('ui.bindEvents fired', 'info'); window.addEventListener('error', function (errorMsg, url, lineNumber) { codex.notifications.errorThrown(errorMsg, event); }, false); /** All keydowns on Document */ codex.nodes.redactor.addEventListener('keydown', codex.callback.globalKeydown, false); /** All keydowns on Document */ document.addEventListener('keyup', codex.callback.globalKeyup, false); /** * Mouse click to radactor */ codex.nodes.redactor.addEventListener('click', codex.callback.redactorClicked, false); /** * Clicks to the Plus button */ codex.nodes.plusButton.addEventListener('click', codex.callback.plusButtonClicked, false); /** * Clicks to SETTINGS button in toolbar */ codex.nodes.showSettingsButton.addEventListener('click', codex.callback.showSettingsButtonClicked, false); /** * @deprecated ( but now in use for syncronization ); * Any redactor changes: keyboard input, mouse cut/paste, drag-n-drop text */ codex.nodes.redactor.addEventListener('input', codex.callback.redactorInputEvent, false); /** Bind click listeners on toolbar buttons */ for (var button in codex.nodes.toolbarButtons) { codex.nodes.toolbarButtons[button].addEventListener('click', codex.callback.toolbarButtonClicked, false); } }; /** * Initialize plugins before using * Ex. Load scripts or call some internal methods */ ui.preparePlugins = function () { for (var tool in codex.tools) { if (typeof codex.tools[tool].prepare != 'function') continue; codex.tools[tool].prepare(); } }, ui.addBlockHandlers = function (block) { if (!block) return; /** * Block keydowns */ block.addEventListener('keydown', function (event) { codex.callback.blockKeydown(event, block); }, false); /** * Pasting content from another source */ block.addEventListener('paste', function (event) { codex.callback.blockPaste(event); }, false); block.addEventListener('mouseup', function () { codex.toolbar.inline.show(); }, false); }; /** getting all contenteditable elements */ ui.saveInputs = function () { var redactor = codex.nodes.redactor, elements = []; /** Save all inputs in global variable state */ codex.state.inputs = redactor.querySelectorAll('[contenteditable], input'); }; /** * Adds first initial block on empty redactor */ ui.addInitialBlock = function () { var initialBlockType = codex.settings.initialBlockPlugin, initialBlock; if (!codex.tools[initialBlockType]) { codex.core.log('Plugin %o was not implemented and can\'t be used as initial block', 'warn', initialBlockType); return; } initialBlock = codex.tools[initialBlockType].render(); initialBlock.setAttribute('data-placeholder', 'Write your story...'); codex.content.insertBlock({ type: initialBlockType, block: initialBlock }); codex.content.workingNodeChanged(initialBlock); }; ui.setInlineToolbarButtonBehaviour = function (button, type) { button.addEventListener('mousedown', function (event) { codex.toolbar.inline.toolClicked(event, type); }, false); }; return ui; }({}); codex.ui = ui; module.exports = codex; /***/ }, /* 4 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = __webpack_require__(1); var transport = function (transport) { transport.input = null; /** * @property {Object} arguments - keep plugin settings and defined callbacks */ transport.arguments = null; transport.prepare = function () { var input = document.createElement('INPUT'); input.type = 'file'; input.addEventListener('change', codex.transport.fileSelected); codex.transport.input = input; }; /** Clear input when files is uploaded */ transport.clearInput = function () { /** Remove old input */ this.input = null; /** Prepare new one */ this.prepare(); }; /** * Callback for file selection */ transport.fileSelected = function (event) { var input = this, files = input.files, filesLength = files.length, formdData = new FormData(), file, i; formdData.append('files', files[0], files[0].name); codex.transport.ajax({ data: formdData, beforeSend: codex.transport.arguments.beforeSend, success: codex.transport.arguments.success, error: codex.transport.arguments.error }); }; /** * Use plugin callbacks * @protected */ transport.selectAndUpload = function (args) { this.arguments = args; this.input.click(); }; /** * Ajax requests module */ transport.ajax = function (params) { var xhr = new XMLHttpRequest(), beforeSend = typeof params.beforeSend == 'function' ? params.beforeSend : function () {}, success = typeof params.success == 'function' ? params.success : function () {}, error = typeof params.error == 'function' ? params.error : function () {}; beforeSend(); xhr.open('POST', codex.settings.uploadImagesUrl, true); xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); xhr.onload = function () { if (xhr.status === 200) { success(xhr.responseText); } else { console.log("request error: %o", xhr); error(); } }; xhr.send(params.data); this.clearInput(); }; return transport; }({}); codex.transport = transport; module.exports = transport; /***/ }, /* 5 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = __webpack_require__(1); var renderer = function (renderer) { /** * Asyncronously parses input JSON to redactor blocks */ renderer.makeBlocksFromData = function () { /** * If redactor is empty, add first paragraph to start writing */ if (!codex.state.blocks.items.length) { codex.ui.addInitialBlock(); return; } Promise.resolve() /** First, get JSON from state */ .then(function () { return codex.state.blocks; }) /** Then, start to iterate they */ .then(codex.renderer.appendBlocks) /** Write log if something goes wrong */ .catch(function (error) { codex.core.log('Error while parsing JSON: %o', 'error', error); }); }; /** * Parses JSON to blocks * @param {object} data * @return Primise -> nodeList */ renderer.appendBlocks = function (data) { var blocks = data.items; /** * Sequence of one-by-one blocks appending * Uses to save blocks order after async-handler */ var nodeSequence = Promise.resolve(); for (var index = 0; index < blocks.length; index++) { /** Add node to sequence at specified index */ codex.renderer.appendNodeAtIndex(nodeSequence, blocks, index); } }; /** * Append node at specified index */ renderer.appendNodeAtIndex = function (nodeSequence, blocks, index) { /** We need to append node to sequence */ nodeSequence /** first, get node async-aware */ .then(function () { return codex.renderer.getNodeAsync(blocks, index); }) /** * second, compose editor-block from JSON object */ .then(codex.renderer.createBlockFromData) /** * now insert block to redactor */ .then(function (blockData) { /** * blockData has 'block', 'type' and 'stretched' information */ codex.content.insertBlock(blockData); /** Pass created block to next step */ return blockData.block; }) /** Log if something wrong with node */ .catch(function (error) { codex.core.log('Node skipped while parsing because %o', 'error', error); }); }; /** * Asynchronously returns block data from blocksList by index * @return Promise to node */ renderer.getNodeAsync = function (blocksList, index) { return Promise.resolve().then(function () { return blocksList[index]; }); }; /** * Creates editor block by JSON-data * * @uses render method of each plugin * * @param {object} blockData looks like * { header : { * text: '', * type: 'H3', ... * } * } * @return {object} with type and Element */ renderer.createBlockFromData = function (blockData) { /** New parser */ var pluginName = blockData.type; /** Get first key of object that stores plugin name */ // for (var pluginName in blockData) break; /** Check for plugin existance */ if (!codex.tools[pluginName]) { throw Error('Plugin \xAB' + pluginName + '\xBB not found'); } /** Check for plugin having render method */ if (typeof codex.tools[pluginName].render != 'function') { throw Error('Plugin \xAB' + pluginName + '\xBB must have \xABrender\xBB method'); } /** New Parser */ var block = codex.tools[pluginName].render(blockData.data); /** Fire the render method with data */ // var block = codex.tools[pluginName].render(blockData[pluginName]); /** is first-level block stretched */ var stretched = codex.tools[pluginName].isStretched || false; /** Retrun type and block */ return { type: pluginName, block: block, stretched: stretched }; }; return renderer; }({}); codex.renderer = renderer; module.exports = renderer; /***/ }, /* 6 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = __webpack_require__(1); var saver = function (saver) { /** * Saves blocks * @private */ saver.saveBlocks = function () { /** Save html content of redactor to memory */ codex.state.html = codex.nodes.redactor.innerHTML; /** Empty jsonOutput state */ codex.state.jsonOutput = []; Promise.resolve().then(function () { return codex.nodes.redactor.childNodes; }) /** Making a sequence from separate blocks */ .then(codex.saver.makeQueue).then(function () { // codex.nodes.textarea.innerHTML = codex.state.html; }).catch(function (error) { console.log('Something happend'); }); }; saver.makeQueue = function (blocks) { var queue = Promise.resolve(); for (var index = 0; index < blocks.length; index++) { /** Add node to sequence at specified index */ codex.saver.getBlockData(queue, blocks, index); } }; /** Gets every block and makes From Data */ saver.getBlockData = function (queue, blocks, index) { queue.then(function () { return codex.saver.getNodeAsync(blocks, index); }).then(codex.saver.makeFormDataFromBlocks); }; /** * Asynchronously returns block data from blocksList by index * @return Promise to node */ saver.getNodeAsync = function (blocksList, index) { return Promise.resolve().then(function () { return blocksList[index]; }); }; saver.makeFormDataFromBlocks = function (block) { var pluginName = block.dataset.tool; /** Check for plugin existance */ if (!codex.tools[pluginName]) { throw Error('Plugin \xAB' + pluginName + '\xBB not found'); } /** Check for plugin having render method */ if (typeof codex.tools[pluginName].save != 'function') { throw Error('Plugin \xAB' + pluginName + '\xBB must have save method'); } /** Result saver */ var blockContent = block.childNodes[0], pluginsContent = blockContent.childNodes[0], savedData = codex.tools[pluginName].save(pluginsContent), output; output = { type: pluginName, data: savedData }; /** Marks Blocks that will be in main page */ output.cover = block.classList.contains(codex.ui.className.BLOCK_IN_FEED_MODE); codex.state.jsonOutput.push(output); }; return saver; }({}); codex.saver = saver; module.exports = saver; /***/ }, /* 7 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = __webpack_require__(1); var content = function (content) { content.currentNode = null; /** * Synchronizes redactor with original textarea */ content.sync = function () { codex.core.log('syncing...'); /** * Save redactor content to codex.state */ codex.state.html = codex.nodes.redactor.innerHTML; }; /** * @deprecated */ content.getNodeFocused = function () { var selection = window.getSelection(), focused; if (selection.anchorNode === null) { return null; } if (selection.anchorNode.nodeType == codex.core.nodeTypes.TAG) { focused = selection.anchorNode; } else { focused = selection.focusNode.parentElement; } if (!codex.parser.isFirstLevelBlock(focused)) { /** Iterate with parent nodes to find first-level*/ var parent = focused.parentNode; while (parent && !codex.parser.isFirstLevelBlock(parent)) { parent = parent.parentNode; } focused = parent; } if (focused != codex.nodes.redactor) { return focused; } return null; }; /** * Appends background to the block */ content.markBlock = function () { codex.content.currentNode.classList.add(codex.ui.className.BLOCK_HIGHLIGHTED); }; /** * Clear background */ content.clearMark = function () { if (codex.content.currentNode) { codex.content.currentNode.classList.remove(codex.ui.className.BLOCK_HIGHLIGHTED); } }; /** * @private * * Finds first-level block * @param {Element} node - selected or clicked in redactors area node */ content.getFirstLevelBlock = function (node) { if (!codex.core.isDomNode(node)) { node = node.parentNode; } if (node === codex.nodes.redactor || node === document.body) { return null; } else { while (!node.classList.contains(codex.ui.className.BLOCK_CLASSNAME)) { node = node.parentNode; } return node; } }; /** * Trigger this event when working node changed * @param {Element} targetNode - first-level of this node will be current * If targetNode is first-level then we set it as current else we look for parents to find first-level */ content.workingNodeChanged = function (targetNode) { /** Clear background from previous marked block before we change */ codex.content.clearMark(); if (!targetNode) { return; } this.currentNode = this.getFirstLevelBlock(targetNode); }; /** * Replaces one redactor block with another * @protected * @param {Element} targetBlock - block to replace. Mostly currentNode. * @param {Element} newBlock * @param {string} newBlockType - type of new block; we need to store it to data-attribute * * [!] Function does not saves old block content. * You can get it manually and pass with newBlock.innerHTML */ content.replaceBlock = function function_name(targetBlock, newBlock) { if (!targetBlock || !newBlock) { codex.core.log('replaceBlock: missed params'); return; } /** If target-block is not a frist-level block, then we iterate parents to find it */ while (!targetBlock.classList.contains(codex.ui.className.BLOCK_CLASSNAME)) { targetBlock = targetBlock.parentNode; } /** Replacing */ codex.nodes.redactor.replaceChild(newBlock, targetBlock); /** * Set new node as current */ codex.content.workingNodeChanged(newBlock); /** * Add block handlers */ codex.ui.addBlockHandlers(newBlock); /** * Save changes */ codex.ui.saveInputs(); }; /** * @private * * Inserts new block to redactor * Wrapps block into a DIV with BLOCK_CLASSNAME class * * @param blockData {object} * @param blockData.block {Element} element with block content * @param blockData.type {string} block plugin * @param needPlaceCaret {bool} pass true to set caret in new block * */ content.insertBlock = function (blockData, needPlaceCaret) { var workingBlock = codex.content.currentNode, newBlockContent = blockData.block, blockType = blockData.type, isStretched = blockData.stretched; var newBlock = codex.content.composeNewBlock(newBlockContent, blockType, isStretched); if (workingBlock) { codex.core.insertAfter(workingBlock, newBlock); } else { /** * If redactor is empty, append as first child */ codex.nodes.redactor.appendChild(newBlock); } /** * Block handler */ codex.ui.addBlockHandlers(newBlock); /** * Set new node as current */ codex.content.workingNodeChanged(newBlock); /** * Save changes */ codex.ui.saveInputs(); if (needPlaceCaret) { /** * If we don't know input index then we set default value -1 */ var currentInputIndex = codex.caret.getCurrentInputIndex() || -1; if (currentInputIndex == -1) { var editableElement = newBlock.querySelector('[contenteditable]'), emptyText = document.createTextNode(''); editableElement.appendChild(emptyText); codex.caret.set(editableElement, 0, 0); codex.toolbar.move(); codex.toolbar.showPlusButton(); } else { /** Timeout for browsers execution */ setTimeout(function () { /** Setting to the new input */ codex.caret.setToNextBlock(currentInputIndex); codex.toolbar.move(); codex.toolbar.open(); }, 10); } } }; /** * Replaces blocks with saving content * @protected * @param {Element} noteToReplace * @param {Element} newNode * @param {Element} blockType */ content.switchBlock = function (blockToReplace, newBlock, tool) { var newBlockComposed = codex.content.composeNewBlock(newBlock, tool); /** Replacing */ codex.content.replaceBlock(blockToReplace, newBlockComposed); /** Save new Inputs when block is changed */ codex.ui.saveInputs(); }; /** * Iterates between child noted and looking for #text node on deepest level * @private * @param {Element} block - node where find * @param {int} postiton - starting postion * Example: childNodex.length to find from the end * or 0 to find from the start * @return {Text} block * @uses DFS */ content.getDeepestTextNodeFromPosition = function (block, position) { /** * Clear Block from empty and useless spaces with trim. * Such nodes we should remove */ var blockChilds = block.childNodes, index, node, text; for (index = 0; index < blockChilds.length; index++) { node = blockChilds[index]; if (node.nodeType == codex.core.nodeTypes.TEXT) { text = node.textContent.trim(); /** Text is empty. We should remove this child from node before we start DFS * decrease the quantity of childs. */ if (text === '') { block.removeChild(node); position--; } } } if (block.childNodes.length === 0) { return document.createTextNode(''); } /** Setting default position when we deleted all empty nodes */ if (position < 0) position = 1; var looking_from_start = false; /** For looking from START */ if (position === 0) { looking_from_start = true; position = 1; } while (position) { /** initial verticle of node. */ if (looking_from_start) { block = block.childNodes[0]; } else { block = block.childNodes[position - 1]; } if (block.nodeType == codex.core.nodeTypes.TAG) { position = block.childNodes.length; } else if (block.nodeType == codex.core.nodeTypes.TEXT) { position = 0; } } return block; }; /** * @private */ content.composeNewBlock = function (block, tool, isStretched) { var newBlock = codex.draw.node('DIV', codex.ui.className.BLOCK_CLASSNAME, {}), blockContent = codex.draw.node('DIV', codex.ui.className.BLOCK_CONTENT, {}); blockContent.appendChild(block); newBlock.appendChild(blockContent); if (isStretched) { blockContent.classList.add(codex.ui.className.BLOCK_STRETCHED); } newBlock.dataset.tool = tool; return newBlock; }; /** * Returns Range object of current selection */ content.getRange = function () { var selection = window.getSelection().getRangeAt(0); return selection; }; /** * Divides block in two blocks (after and before caret) * @private * @param {Int} inputIndex - target input index */ content.splitBlock = function (inputIndex) { var selection = window.getSelection(), anchorNode = selection.anchorNode, anchorNodeText = anchorNode.textContent, caretOffset = selection.anchorOffset, textBeforeCaret, textNodeBeforeCaret, textAfterCaret, textNodeAfterCaret; var currentBlock = codex.content.currentNode.querySelector('[contentEditable]'); textBeforeCaret = anchorNodeText.substring(0, caretOffset); textAfterCaret = anchorNodeText.substring(caretOffset); textNodeBeforeCaret = document.createTextNode(textBeforeCaret); if (textAfterCaret) { textNodeAfterCaret = document.createTextNode(textAfterCaret); } var previousChilds = [], nextChilds = [], reachedCurrent = false; if (textNodeAfterCaret) { nextChilds.push(textNodeAfterCaret); } for (var i = 0, child; !!(child = currentBlock.childNodes[i]); i++) { if (child != anchorNode) { if (!reachedCurrent) { previousChilds.push(child); } else { nextChilds.push(child); } } else { reachedCurrent = true; } } /** Clear current input */ codex.state.inputs[inputIndex].innerHTML = ''; /** * Append all childs founded before anchorNode */ var previousChildsLength = previousChilds.length; for (i = 0; i < previousChildsLength; i++) { codex.state.inputs[inputIndex].appendChild(previousChilds[i]); } codex.state.inputs[inputIndex].appendChild(textNodeBeforeCaret); /** * Append text node which is after caret */ var nextChildsLength = nextChilds.length, newNode = document.createElement('div'); for (i = 0; i < nextChildsLength; i++) { newNode.appendChild(nextChilds[i]); } newNode = newNode.innerHTML; /** This type of block creates when enter is pressed */ var NEW_BLOCK_TYPE = 'paragraph'; /** * Make new paragraph with text after caret */ codex.content.insertBlock({ type: NEW_BLOCK_TYPE, block: codex.tools[NEW_BLOCK_TYPE].render({ text: newNode }) }, true); }; /** * Merges two blocks — current and target * If target index is not exist, then previous will be as target */ content.mergeBlocks = function (currentInputIndex, targetInputIndex) { /** If current input index is zero, then prevent method execution */ if (currentInputIndex === 0) { return; } var targetInput, currentInputContent = codex.state.inputs[currentInputIndex].innerHTML; if (!targetInputIndex) { targetInput = codex.state.inputs[currentInputIndex - 1]; } else { targetInput = codex.state.inputs[targetInputIndex]; } targetInput.innerHTML += currentInputContent; }; /** * @private * * Callback for HTML Mutations * @param {Array} mutation - Mutation Record */ content.paste = function (mutation) { var workingNode = codex.content.currentNode, tool = workingNode.dataset.tool; if (codex.tools[tool].allowedToPaste) { codex.content.sanitize(mutation.addedNodes); } else { codex.content.pasteTextContent(mutation.addedNodes); } }; /** * @private * * gets only text/plain content of node * @param {Element} target - HTML node */ content.pasteTextContent = function (nodes) { var node = nodes[0], textNode = document.createTextNode(node.textContent); if (codex.core.isDomNode(node)) { node.parentNode.replaceChild(textNode, node); } }; /** * @private * * Sanitizes HTML content * @param {Element} target - inserted element * @uses DFS function for deep searching */ content.sanitize = function (target) { if (!target) { return; } for (var i = 0; i < target.childNodes.length; i++) { this.dfs(target.childNodes[i]); } }; /** * Clears styles * @param {Element|Text} */ content.clearStyles = function (target) { var href, newNode = null, blockTags = ['P', 'BLOCKQUOTE', 'UL', 'CODE', 'OL', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DIV', 'PRE', 'HEADER', 'SECTION'], allowedTags = ['P', 'B', 'I', 'A', 'U', 'BR'], needReplace = !allowedTags.includes(target.tagName), isDisplayedAsBlock = blockTags.includes(target.tagName); if (!codex.core.isDomNode(target)) { return target; } if (!target.parentNode) { return target; } if (needReplace) { if (isDisplayedAsBlock) { newNode = document.createElement('P'); newNode.innerHTML = target.innerHTML; target.parentNode.replaceChild(newNode, target); target = newNode; } else { newNode = document.createTextNode(' ' + target.textContent + ' '); newNode.textContent = newNode.textContent.replace(/\s{2,}/g, ' '); target.parentNode.replaceChild(newNode, target); } } /** keep href attributes of tag A */ if (target.tagName == 'A') { href = target.getAttribute('href'); } /** Remove all tags */ while (target.attributes.length > 0) { target.removeAttribute(target.attributes[0].name); } /** return href */ if (href) { target.setAttribute('href', href); } return target; }; /** * Depth-first search Algorithm * returns all childs * @param {Element} */ content.dfs = function (el) { if (!codex.core.isDomNode(el)) return; var sanitized = this.clearStyles(el); for (var i = 0; i < sanitized.childNodes.length; i++) { this.dfs(sanitized.childNodes[i]); } }; return content; }({}); codex.content = content; module.exports = content; /***/ }, /* 8 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = __webpack_require__(1); var toolbar = function (toolbar) { toolbar.init = function () { toolbar.settings = __webpack_require__(9); toolbar.inline = __webpack_require__(10); toolbar.toolbox = __webpack_require__(11); }; /** * Margin between focused node and toolbar */ toolbar.defaultToolbarHeight = 49; toolbar.defaultOffset = 34; toolbar.opened = false; toolbar.current = null; /** * @protected */ toolbar.open = function () { codex.nodes.toolbar.classList.add('opened'); this.opened = true; }; /** * @protected */ toolbar.close = function () { codex.nodes.toolbar.classList.remove('opened'); this.opened = false; this.current = null; for (var button in codex.nodes.toolbarButtons) { codex.nodes.toolbarButtons[button].classList.remove('selected'); } /** Close toolbox when toolbar is not displayed */ codex.toolbar.toolbox.close(); codex.toolbar.settings.close(); }; toolbar.toggle = function () { if (!this.opened) { this.open(); } else { this.close(); } }; toolbar.hidePlusButton = function () { codex.nodes.plusButton.classList.add('hide'); }; toolbar.showPlusButton = function () { codex.nodes.plusButton.classList.remove('hide'); }; /** * Moving toolbar to the specified node */ toolbar.move = function () { /** Close Toolbox when we move toolbar */ codex.toolbar.toolbox.close(); if (!codex.content.currentNode) { return; } var toolbarHeight = codex.nodes.toolbar.clientHeight || codex.toolbar.defaultToolbarHeight, newYCoordinate = codex.content.currentNode.offsetTop - codex.toolbar.defaultToolbarHeight / 2 + codex.toolbar.defaultOffset; codex.nodes.toolbar.style.transform = 'translate3D(0, ' + Math.floor(newYCoordinate) + 'px, 0)'; /** Close trash actions */ codex.toolbar.settings.hideRemoveActions(); }; return toolbar; }({}); toolbar.init(); codex.toolbar = toolbar; module.exports = toolbar; /***/ }, /* 9 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = __webpack_require__(1); var settings = function (settings) { settings.init = function () { __webpack_require__(7); }; settings.opened = false; settings.setting = null; settings.actions = null; settings.cover = null; /** * Append and open settings */ settings.open = function (toolType) { /** * Append settings content * It's stored in tool.settings */ if (!codex.tools[toolType] || !codex.core.isDomNode(codex.tools[toolType].settings)) { codex.core.log('Plugin \xAB' + toolType + '\xBB has no settings', 'warn'); // codex.nodes.pluginSettings.innerHTML = `Плагин «${toolType}» не имеет настроек`; } else { codex.nodes.pluginSettings.appendChild(codex.tools[toolType].settings); } var currentBlock = codex.content.currentNode; /** Open settings block */ codex.nodes.blockSettings.classList.add('opened'); codex.toolbar.settings.addDefaultSettings(); this.opened = true; }; /** * Close and clear settings */ settings.close = function () { codex.nodes.blockSettings.classList.remove('opened'); codex.nodes.pluginSettings.innerHTML = ''; this.opened = false; }; /** * @param {string} toolType - plugin type */ settings.toggle = function (toolType) { if (!this.opened) { this.open(toolType); } else { this.close(); } }; /** * This function adds default core settings */ settings.addDefaultSettings = function () { /** list of default settings */ var feedModeToggler; /** Clear block and append initialized settings */ codex.nodes.defaultSettings.innerHTML = ''; /** Init all default setting buttons */ feedModeToggler = codex.toolbar.settings.makeFeedModeToggler(); /** * Fill defaultSettings */ /** * Button that enables/disables Feed-mode * Feed-mode means that block will be showed in articles-feed like cover */ codex.nodes.defaultSettings.appendChild(feedModeToggler); }; /** * Cover setting. * This tune highlights block, so that it may be used for showing target block on main page * Draw different setting when block is marked for main page * If TRUE, then we show button that removes this selection * Also defined setting "Click" events will be listened and have separate callbacks * * @return {Element} node/button that we place in default settings block */ settings.makeFeedModeToggler = function () { var isFeedModeActivated = codex.toolbar.settings.isFeedModeActivated(), setting, data; if (!isFeedModeActivated) { data = { innerHTML: 'Вывести в ленте' }; } else { data = { innerHTML: 'Не выводить в ленте' }; } setting = codex.draw.node('DIV', codex.ui.className.SETTINGS_ITEM, data); setting.addEventListener('click', codex.toolbar.settings.updateFeedMode, false); return setting; }; /** * Updates Feed-mode */ settings.updateFeedMode = function () { var currentNode = codex.content.currentNode; currentNode.classList.toggle(codex.ui.className.BLOCK_IN_FEED_MODE); codex.toolbar.settings.close(); }; settings.isFeedModeActivated = function () { var currentBlock = codex.content.currentNode; if (currentBlock) { return currentBlock.classList.contains(codex.ui.className.BLOCK_IN_FEED_MODE); } else { return false; } }; /** * Here we will draw buttons and add listeners to components */ settings.makeRemoveBlockButton = function () { var removeBlockWrapper = codex.draw.node('SPAN', 'ce-toolbar__remove-btn', {}), settingButton = codex.draw.node('SPAN', 'ce-toolbar__remove-setting', { innerHTML: '' }), actionWrapper = codex.draw.node('DIV', 'ce-toolbar__remove-confirmation', {}), confirmAction = codex.draw.node('DIV', 'ce-toolbar__remove-confirm', { textContent: 'Удалить блок' }), cancelAction = codex.draw.node('DIV', 'ce-toolbar__remove-cancel', { textContent: 'Отменить удаление' }); settingButton.addEventListener('click', codex.toolbar.settings.removeButtonClicked, false); confirmAction.addEventListener('click', codex.toolbar.settings.confirmRemovingRequest, false); cancelAction.addEventListener('click', codex.toolbar.settings.cancelRemovingRequest, false); actionWrapper.appendChild(confirmAction); actionWrapper.appendChild(cancelAction); removeBlockWrapper.appendChild(settingButton); removeBlockWrapper.appendChild(actionWrapper); /** Save setting */ codex.toolbar.settings.setting = settingButton; codex.toolbar.settings.actions = actionWrapper; return removeBlockWrapper; }; settings.removeButtonClicked = function () { var action = codex.toolbar.settings.actions; if (action.classList.contains('opened')) { codex.toolbar.settings.hideRemoveActions(); } else { codex.toolbar.settings.showRemoveActions(); } codex.toolbar.toolbox.close(); codex.toolbar.settings.close(); }; settings.cancelRemovingRequest = function () { codex.toolbar.settings.actions.classList.remove('opened'); }; settings.confirmRemovingRequest = function () { var currentBlock = codex.content.currentNode, firstLevelBlocksCount; currentBlock.remove(); firstLevelBlocksCount = codex.nodes.redactor.childNodes.length; /** * If all blocks are removed */ if (firstLevelBlocksCount === 0) { /** update currentNode variable */ codex.content.currentNode = null; /** Inserting new empty initial block */ codex.ui.addInitialBlock(); } codex.ui.saveInputs(); codex.toolbar.close(); }; settings.showRemoveActions = function () { codex.toolbar.settings.actions.classList.add('opened'); }; settings.hideRemoveActions = function () { codex.toolbar.settings.actions.classList.remove('opened'); }; return settings; }({}); settings.init(); module.exports = settings; /***/ }, /* 10 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = __webpack_require__(1); var inline = function (inline) { inline.init = function () {}; inline.buttonsOpened = null; inline.actionsOpened = null; inline.wrappersOffset = null; /** * saving selection that need for execCommand for styling * */ inline.storedSelection = null, /** * @protected * * Open inline toobar */ inline.show = function () { var selectedText = this.getSelectionText(), toolbar = codex.nodes.inlineToolbar.wrapper, buttons = codex.nodes.inlineToolbar.buttons; if (selectedText.length > 0) { /** Move toolbar and open */ codex.toolbar.inline.move(); /** Open inline toolbar */ toolbar.classList.add('opened'); /** show buttons of inline toolbar */ codex.toolbar.inline.showButtons(); } }; /** * @protected * * Closes inline toolbar */ inline.close = function () { var toolbar = codex.nodes.inlineToolbar.wrapper; toolbar.classList.remove('opened'); }; /** * @private * * Moving toolbar */ inline.move = function () { if (!this.wrappersOffset) { this.wrappersOffset = this.getWrappersOffset(); } var coords = this.getSelectionCoords(), defaultOffset = 0, toolbar = codex.nodes.inlineToolbar.wrapper, newCoordinateX, newCoordinateY; if (toolbar.offsetHeight === 0) { defaultOffset = 40; } newCoordinateX = coords.x - this.wrappersOffset.left; newCoordinateY = coords.y + window.scrollY - this.wrappersOffset.top - defaultOffset - toolbar.offsetHeight; toolbar.style.transform = 'translate3D(' + Math.floor(newCoordinateX) + 'px, ' + Math.floor(newCoordinateY) + 'px, 0)'; /** Close everything */ codex.toolbar.inline.closeButtons(); codex.toolbar.inline.closeAction(); }; /** * @private * * Tool Clicked */ inline.toolClicked = function (event, type) { /** * For simple tools we use default browser function * For more complicated tools, we should write our own behavior */ switch (type) { case 'createLink': codex.toolbar.inline.createLinkAction(event, type);break; default: codex.toolbar.inline.defaultToolAction(type);break; } /** * highlight buttons * after making some action */ codex.nodes.inlineToolbar.buttons.childNodes.forEach(codex.toolbar.inline.hightlight); }; /** * @private * * Saving wrappers offset in DOM */ inline.getWrappersOffset = function () { var wrapper = codex.nodes.wrapper, offset = this.getOffset(wrapper); this.wrappersOffset = offset; return offset; }; /** * @private * * Calculates offset of DOM element * * @param el * @returns {{top: number, left: number}} */ inline.getOffset = function (el) { var _x = 0; var _y = 0; while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) { _x += el.offsetLeft + el.clientLeft; _y += el.offsetTop + el.clientTop; el = el.offsetParent; } return { top: _y, left: _x }; }; /** * @private * * Calculates position of selected text * @returns {{x: number, y: number}} */ inline.getSelectionCoords = function () { var sel = document.selection, range; var x = 0, y = 0; if (sel) { if (sel.type != "Control") { range = sel.createRange(); range.collapse(true); x = range.boundingLeft; y = range.boundingTop; } } else if (window.getSelection) { sel = window.getSelection(); if (sel.rangeCount) { range = sel.getRangeAt(0).cloneRange(); if (range.getClientRects) { range.collapse(true); var rect = range.getClientRects()[0]; x = rect.left; y = rect.top; } } } return { x: x, y: y }; }; /** * @private * * Returns selected text as String * @returns {string} */ inline.getSelectionText = function getSelectionText() { var selectedText = ""; if (window.getSelection) { // all modern browsers and IE9+ selectedText = window.getSelection().toString(); } return selectedText; }; /** Opens buttons block */ inline.showButtons = function () { var buttons = codex.nodes.inlineToolbar.buttons; buttons.classList.add('opened'); codex.toolbar.inline.buttonsOpened = true; /** highlight buttons */ codex.nodes.inlineToolbar.buttons.childNodes.forEach(codex.toolbar.inline.hightlight); }; /** Makes buttons disappear */ inline.closeButtons = function () { var buttons = codex.nodes.inlineToolbar.buttons; buttons.classList.remove('opened'); codex.toolbar.inline.buttonsOpened = false; }; /** Open buttons defined action if exist */ inline.showActions = function () { var action = codex.nodes.inlineToolbar.actions; action.classList.add('opened'); codex.toolbar.inline.actionsOpened = true; }; /** Close actions block */ inline.closeAction = function () { var action = codex.nodes.inlineToolbar.actions; action.innerHTML = ''; action.classList.remove('opened'); codex.toolbar.inline.actionsOpened = false; }; /** Action for link creation or for setting anchor */ inline.createLinkAction = function (event, type) { var isActive = this.isLinkActive(); var editable = codex.content.currentNode, storedSelection = codex.toolbar.inline.storedSelection; if (isActive) { var selection = window.getSelection(), anchorNode = selection.anchorNode; storedSelection = codex.toolbar.inline.saveSelection(editable); /** * Changing stored selection. if we want to remove anchor from word * we should remove anchor from whole word, not only selected part. * The solution is than we get the length of current link * Change start position to - end of selection minus length of anchor */ codex.toolbar.inline.restoreSelection(editable, storedSelection); codex.toolbar.inline.defaultToolAction('unlink'); } else { /** Create input and close buttons */ var action = codex.draw.inputForLink(); codex.nodes.inlineToolbar.actions.appendChild(action); codex.toolbar.inline.closeButtons(); codex.toolbar.inline.showActions(); storedSelection = codex.toolbar.inline.saveSelection(editable); /** * focus to input * Solution: https://developer.mozilla.org/ru/docs/Web/API/HTMLElement/focus * Prevents event after showing input and when we need to focus an input which is in unexisted form */ action.focus(); event.preventDefault(); /** Callback to link action */ action.addEventListener('keydown', function (event) { if (event.keyCode == codex.core.keys.ENTER) { codex.toolbar.inline.restoreSelection(editable, storedSelection); codex.toolbar.inline.setAnchor(action.value); /** * Preventing events that will be able to happen */ event.preventDefault(); event.stopImmediatePropagation(); codex.toolbar.inline.clearRange(); } }, false); } }; inline.isLinkActive = function () { var isActive = false; codex.nodes.inlineToolbar.buttons.childNodes.forEach(function (tool) { var dataType = tool.dataset.type; if (dataType == 'link' && tool.classList.contains('hightlighted')) { isActive = true; } }); return isActive; }; /** default action behavior of tool */ inline.defaultToolAction = function (type) { document.execCommand(type, false, null); }; /** * @private * * Sets URL * * @param {String} url - URL */ inline.setAnchor = function (url) { document.execCommand('createLink', false, url); /** Close after URL inserting */ codex.toolbar.inline.closeAction(); }; /** * @private * * Saves selection */ inline.saveSelection = function (containerEl) { var range = window.getSelection().getRangeAt(0), preSelectionRange = range.cloneRange(), start; preSelectionRange.selectNodeContents(containerEl); preSelectionRange.setEnd(range.startContainer, range.startOffset); start = preSelectionRange.toString().length; return { start: start, end: start + range.toString().length }; }; /** * @private * * Sets to previous selection (Range) * * @param {Element} containerEl - editable element where we restore range * @param {Object} savedSel - range basic information to restore */ inline.restoreSelection = function (containerEl, savedSel) { var range = document.createRange(), charIndex = 0; range.setStart(containerEl, 0); range.collapse(true); var nodeStack = [containerEl], node, foundStart = false, stop = false, nextCharIndex; while (!stop && (node = nodeStack.pop())) { if (node.nodeType == 3) { nextCharIndex = charIndex + node.length; if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) { range.setStart(node, savedSel.start - charIndex); foundStart = true; } if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) { range.setEnd(node, savedSel.end - charIndex); stop = true; } charIndex = nextCharIndex; } else { var i = node.childNodes.length; while (i--) { nodeStack.push(node.childNodes[i]); } } } var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); }; /** * @private * * Removes all ranges from window selection */ inline.clearRange = function () { var selection = window.getSelection(); selection.removeAllRanges(); }; /** * @private * * sets or removes hightlight */ inline.hightlight = function (tool) { var dataType = tool.dataset.type; if (document.queryCommandState(dataType)) { codex.toolbar.inline.setButtonHighlighted(tool); } else { codex.toolbar.inline.removeButtonsHighLight(tool); } /** * * hightlight for anchors */ var selection = window.getSelection(), tag = selection.anchorNode.parentNode; if (tag.tagName == 'A' && dataType == 'link') { codex.toolbar.inline.setButtonHighlighted(tool); } }; /** * @private * * Mark button if text is already executed */ inline.setButtonHighlighted = function (button) { button.classList.add('hightlighted'); /** At link tool we also change icon */ if (button.dataset.type == 'link') { var icon = button.childNodes[0]; icon.classList.remove('ce-icon-link'); icon.classList.add('ce-icon-unlink'); } }; /** * @private * * Removes hightlight */ inline.removeButtonsHighLight = function (button) { button.classList.remove('hightlighted'); /** At link tool we also change icon */ if (button.dataset.type == 'link') { var icon = button.childNodes[0]; icon.classList.remove('ce-icon-unlink'); icon.classList.add('ce-icon-link'); } }; return inline; }({}); inline.init(); module.exports = inline; /***/ }, /* 11 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = __webpack_require__(1); var toolbox = function (toolbox) { toolbox.init = function () { __webpack_require__(8); }; toolbox.opened = false; /** Shows toolbox */ toolbox.open = function () { /** Close setting if toolbox is opened */ if (codex.toolbar.settings.opened) { codex.toolbar.settings.close(); } /** display toolbox */ codex.nodes.toolbox.classList.add('opened'); /** Animate plus button */ codex.nodes.plusButton.classList.add('clicked'); /** toolbox state */ codex.toolbar.toolbox.opened = true; }; /** Closes toolbox */ toolbox.close = function () { /** Makes toolbox disapear */ codex.nodes.toolbox.classList.remove('opened'); /** Rotate plus button */ codex.nodes.plusButton.classList.remove('clicked'); /** toolbox state */ codex.toolbar.toolbox.opened = false; }; toolbox.leaf = function () { var currentTool = codex.toolbar.current, tools = Object.keys(codex.tools), barButtons = codex.nodes.toolbarButtons, nextToolIndex, toolToSelect; if (!currentTool) { /** Get first tool from object*/ for (toolToSelect in barButtons) { break; } } else { nextToolIndex = tools.indexOf(currentTool) + 1; if (nextToolIndex == tools.length) nextToolIndex = 0; toolToSelect = tools[nextToolIndex]; } for (var button in barButtons) { barButtons[button].classList.remove('selected'); }barButtons[toolToSelect].classList.add('selected'); codex.toolbar.current = toolToSelect; }; /** * Transforming selected node type into selected toolbar element type * @param {event} event */ toolbox.toolClicked = function () { /** * UNREPLACEBLE_TOOLS this types of tools are forbidden to replace even they are empty */ var UNREPLACEBLE_TOOLS = ['image', 'link', 'list', 'instagram', 'twitter'], tool = codex.tools[codex.toolbar.current], workingNode = codex.content.currentNode, currentInputIndex = codex.caret.inputIndex, newBlockContent, appendCallback, blockData; /** Make block from plugin */ newBlockContent = tool.make(); /** information about block */ blockData = { block: newBlockContent, type: tool.type, stretched: false }; if (workingNode && UNREPLACEBLE_TOOLS.indexOf(workingNode.dataset.tool) === -1 && workingNode.textContent.trim() === '') { /** Replace current block */ codex.content.switchBlock(workingNode, newBlockContent, tool.type); } else { /** Insert new Block from plugin */ codex.content.insertBlock(blockData); /** increase input index */ currentInputIndex++; } /** Fire tool append callback */ appendCallback = tool.appendCallback; if (appendCallback && typeof appendCallback == 'function') { appendCallback.call(event); } setTimeout(function () { /** Set caret to current block */ codex.caret.setToBlock(currentInputIndex); }, 10); /** * Changing current Node */ codex.content.workingNodeChanged(); /** * Move toolbar when node is changed */ codex.toolbar.move(); }; return toolbox; }({}); toolbox.init(); module.exports = toolbox; /***/ }, /* 12 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = __webpack_require__(1); var tools = function (tools) { return tools; }({}); codex.tools = tools; module.exports = tools; /***/ }, /* 13 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = __webpack_require__(1); var callbacks = function (callbacks) { callbacks.redactorSyncTimeout = null; callbacks.globalKeydown = function (event) { switch (event.keyCode) { case codex.core.keys.TAB: codex.callback.tabKeyPressed(event);break; case codex.core.keys.ENTER: codex.callback.enterKeyPressed(event);break; case codex.core.keys.ESC: codex.callback.escapeKeyPressed(event);break; default: codex.callback.defaultKeyPressed(event);break; } }; callbacks.globalKeyup = function (event) { switch (event.keyCode) { case codex.core.keys.UP: case codex.core.keys.LEFT: case codex.core.keys.RIGHT: case codex.core.keys.DOWN: codex.callback.arrowKeyPressed(event);break; } }; callbacks.tabKeyPressed = function (event) { if (!codex.toolbar.opened) { codex.toolbar.open(); } if (codex.toolbar.opened && !codex.toolbar.toolbox.opened) { codex.toolbar.toolbox.open(); } else { codex.toolbar.toolbox.leaf(); } event.preventDefault(); }; /** * ENTER key handler * Makes new paragraph block */ callbacks.enterKeyPressed = function (event) { /** Set current node */ var firstLevelBlocksArea = codex.callback.clickedOnFirstLevelBlockArea(); if (firstLevelBlocksArea) { event.preventDefault(); /** * it means that we lose input index, saved index before is not correct * therefore we need to set caret when we insert new block */ codex.caret.inputIndex = -1; codex.callback.enterPressedOnBlock(); return; } if (event.target.contentEditable == 'true') { /** Update input index */ codex.caret.saveCurrentInputIndex(); } if (!codex.content.currentNode) { /** * Enter key pressed in first-level block area */ codex.callback.enterPressedOnBlock(event); return; } var currentInputIndex = codex.caret.getCurrentInputIndex() || 0, workingNode = codex.content.currentNode, tool = workingNode.dataset.tool, isEnterPressedOnToolbar = codex.toolbar.opened && codex.toolbar.current && event.target == codex.state.inputs[currentInputIndex]; /** The list of tools which needs the default browser behaviour */ var enableLineBreaks = codex.tools[tool].enableLineBreaks; /** This type of block creates when enter is pressed */ var NEW_BLOCK_TYPE = 'paragraph'; /** * When toolbar is opened, select tool instead of making new paragraph */ if (isEnterPressedOnToolbar) { event.preventDefault(); codex.toolbar.toolbox.toolClicked(event); codex.toolbar.close(); return; } /** * Allow making new

in same block by SHIFT+ENTER and forbids to prevent default browser behaviour */ if (event.shiftKey && !enableLineBreaks) { codex.callback.enterPressedOnBlock(codex.content.currentBlock, event); event.preventDefault(); } else if (event.shiftKey && !enableLineBreaks || !event.shiftKey && enableLineBreaks) { /** XOR */ return; } var isLastTextNode = false, currentSelection = window.getSelection(), currentSelectedNode = currentSelection.anchorNode, caretAtTheEndOfText = codex.caret.position.atTheEnd(), isTextNodeHasParentBetweenContenteditable = false; /** * Workaround situation when caret at the Text node that has some wrapper Elements * Split block cant handle this. * We need to save default behavior */ isTextNodeHasParentBetweenContenteditable = currentSelectedNode && currentSelectedNode.parentNode.contentEditable != "true"; /** * Split blocks when input has several nodes and caret placed in textNode */ if (currentSelectedNode.nodeType == codex.core.nodeTypes.TEXT && !isTextNodeHasParentBetweenContenteditable && !caretAtTheEndOfText) { event.preventDefault(); codex.core.log('Splitting Text node...'); codex.content.splitBlock(currentInputIndex); /** Show plus button when next input after split is empty*/ if (!codex.state.inputs[currentInputIndex + 1].textContent.trim()) { codex.toolbar.showPlusButton(); } } else { if (currentSelectedNode && currentSelectedNode.parentNode) { isLastTextNode = !currentSelectedNode.parentNode.nextSibling; } if (isLastTextNode && caretAtTheEndOfText) { event.preventDefault(); codex.core.log('ENTER clicked in last textNode. Create new BLOCK'); codex.content.insertBlock({ type: NEW_BLOCK_TYPE, block: codex.tools[NEW_BLOCK_TYPE].render() }, true); codex.toolbar.move(); codex.toolbar.open(); /** Show plus button with empty block */ codex.toolbar.showPlusButton(); } else { codex.core.log('Default ENTER behavior.'); } } /** get all inputs after new appending block */ codex.ui.saveInputs(); }; callbacks.escapeKeyPressed = function (event) { /** Close all toolbar */ codex.toolbar.close(); /** Close toolbox */ codex.toolbar.toolbox.close(); event.preventDefault(); }; callbacks.arrowKeyPressed = function (event) { codex.content.workingNodeChanged(); /* Closing toolbar */ codex.toolbar.close(); codex.toolbar.move(); }; callbacks.defaultKeyPressed = function (event) { codex.toolbar.close(); if (!codex.toolbar.inline.actionsOpened) { codex.toolbar.inline.close(); codex.content.clearMark(); } }; callbacks.redactorClicked = function (event) { codex.content.workingNodeChanged(event.target); codex.ui.saveInputs(); var selectedText = codex.toolbar.inline.getSelectionText(); /** * If selection range took off, then we hide inline toolbar */ if (selectedText.length === 0) { codex.toolbar.inline.close(); } /** Update current input index in memory when caret focused into existed input */ if (event.target.contentEditable == 'true') { codex.caret.saveCurrentInputIndex(); } if (codex.content.currentNode === null) { /** * If inputs in redactor does not exits, then we put input index 0 not -1 */ var indexOfLastInput = codex.state.inputs.length > 0 ? codex.state.inputs.length - 1 : 0; /** If we have any inputs */ if (codex.state.inputs.length) { /** getting firstlevel parent of input */ var firstLevelBlock = codex.content.getFirstLevelBlock(codex.state.inputs[indexOfLastInput]); } /** If input is empty, then we set caret to the last input */ if (codex.state.inputs.length && codex.state.inputs[indexOfLastInput].textContent === '' && firstLevelBlock.dataset.tool == 'paragraph') { codex.caret.setToBlock(indexOfLastInput); } else { /** Create new input when caret clicked in redactors area */ var NEW_BLOCK_TYPE = 'paragraph'; codex.content.insertBlock({ type: NEW_BLOCK_TYPE, block: codex.tools[NEW_BLOCK_TYPE].render() }); /** If there is no inputs except inserted */ if (codex.state.inputs.length === 1) { codex.caret.setToBlock(indexOfLastInput); } else { /** Set caret to this appended input */ codex.caret.setToNextBlock(indexOfLastInput); } } /** * Move toolbar to the right position and open */ codex.toolbar.move(); codex.toolbar.open(); } else { /** * Move toolbar to the new position and open */ codex.toolbar.move(); codex.toolbar.open(); /** Close all panels */ codex.toolbar.settings.close(); codex.toolbar.toolbox.close(); } var inputIsEmpty = !codex.content.currentNode.textContent.trim(); if (inputIsEmpty) { /** Show plus button */ codex.toolbar.showPlusButton(); } else { /** Hide plus buttons */ codex.toolbar.hidePlusButton(); } var currentNodeType = codex.content.currentNode.dataset.tool; /** Mark current block*/ if (currentNodeType != 'paragraph' || !inputIsEmpty) { codex.content.markBlock(); } }; /** * This method allows to define, is caret in contenteditable element or not. * Otherwise, if we get TEXT node from range container, that will means we have input index. * In this case we use default browsers behaviour (if plugin allows that) or overwritten action. * Therefore, to be sure that we've clicked first-level block area, we should have currentNode, which always * specifies to the first-level block. Other cases we just ignore. */ callbacks.clickedOnFirstLevelBlockArea = function () { var selection = window.getSelection(), anchorNode = selection.anchorNode, flag = false; if (selection.rangeCount == 0) { return true; } else { if (!codex.core.isDomNode(anchorNode)) { anchorNode = anchorNode.parentNode; } /** Already founded, without loop */ if (anchorNode.contentEditable == 'true') { flag = true; } while (anchorNode.contentEditable != 'true') { anchorNode = anchorNode.parentNode; if (anchorNode.contentEditable == 'true') { flag = true; } if (anchorNode == document.body) { break; } } /** If editable element founded, flag is "TRUE", Therefore we return "FALSE" */ return flag ? false : true; } }; /** * Toolbar button click handler * @param this - cursor to the button */ callbacks.toolbarButtonClicked = function (event) { var button = this; codex.toolbar.current = button.dataset.type; codex.toolbar.toolbox.toolClicked(event); codex.toolbar.close(); }; callbacks.redactorInputEvent = function (event) { /** * Clear previous sync-timeout */ if (this.redactorSyncTimeout) { clearTimeout(this.redactorSyncTimeout); } /** * Start waiting to input finish and sync redactor */ this.redactorSyncTimeout = setTimeout(function () { codex.content.sync(); }, 500); }; /** Show or Hide toolbox when plus button is clicked */ callbacks.plusButtonClicked = function () { if (!codex.nodes.toolbox.classList.contains('opened')) { codex.toolbar.toolbox.open(); } else { codex.toolbar.toolbox.close(); } }; /** * Block handlers for KeyDown events */ callbacks.blockKeydown = function (event, block) { switch (event.keyCode) { case codex.core.keys.DOWN: case codex.core.keys.RIGHT: codex.callback.blockRightOrDownArrowPressed(block); break; case codex.core.keys.BACKSPACE: codex.callback.backspacePressed(block); break; case codex.core.keys.UP: case codex.core.keys.LEFT: codex.callback.blockLeftOrUpArrowPressed(block); break; } }; /** * RIGHT or DOWN keydowns on block */ callbacks.blockRightOrDownArrowPressed = function (block) { var selection = window.getSelection(), inputs = codex.state.inputs, focusedNode = selection.anchorNode, focusedNodeHolder; /** Check for caret existance */ if (!focusedNode) { return false; } /** Looking for closest (parent) contentEditable element of focused node */ while (focusedNode.contentEditable != 'true') { focusedNodeHolder = focusedNode.parentNode; focusedNode = focusedNodeHolder; } /** Input index in DOM level */ var editableElementIndex = 0; while (focusedNode != inputs[editableElementIndex]) { editableElementIndex++; } /** * Founded contentEditable element doesn't have childs * Or maybe New created block */ if (!focusedNode.textContent) { codex.caret.setToNextBlock(editableElementIndex); return; } /** * Do nothing when caret doesn not reaches the end of last child */ var caretInLastChild = false, caretAtTheEndOfText = false; var lastChild, deepestTextnode; lastChild = focusedNode.childNodes[focusedNode.childNodes.length - 1]; if (codex.core.isDomNode(lastChild)) { deepestTextnode = codex.content.getDeepestTextNodeFromPosition(lastChild, lastChild.childNodes.length); } else { deepestTextnode = lastChild; } caretInLastChild = selection.anchorNode == deepestTextnode; caretAtTheEndOfText = deepestTextnode.length == selection.anchorOffset; if (!caretInLastChild || !caretAtTheEndOfText) { codex.core.log('arrow [down|right] : caret does not reached the end'); return false; } codex.caret.setToNextBlock(editableElementIndex); }; /** * LEFT or UP keydowns on block */ callbacks.blockLeftOrUpArrowPressed = function (block) { var selection = window.getSelection(), inputs = codex.state.inputs, focusedNode = selection.anchorNode, focusedNodeHolder; /** Check for caret existance */ if (!focusedNode) { return false; } /** * LEFT or UP not at the beginning */ if (selection.anchorOffset !== 0) { return false; } /** Looking for parent contentEditable block */ while (focusedNode.contentEditable != 'true') { focusedNodeHolder = focusedNode.parentNode; focusedNode = focusedNodeHolder; } /** Input index in DOM level */ var editableElementIndex = 0; while (focusedNode != inputs[editableElementIndex]) { editableElementIndex++; } /** * Do nothing if caret is not at the beginning of first child */ var caretInFirstChild = false, caretAtTheBeginning = false; var firstChild, deepestTextnode; /** * Founded contentEditable element doesn't have childs * Or maybe New created block */ if (!focusedNode.textContent) { codex.caret.setToPreviousBlock(editableElementIndex); return; } firstChild = focusedNode.childNodes[0]; if (codex.core.isDomNode(firstChild)) { deepestTextnode = codex.content.getDeepestTextNodeFromPosition(firstChild, 0); } else { deepestTextnode = firstChild; } caretInFirstChild = selection.anchorNode == deepestTextnode; caretAtTheBeginning = selection.anchorOffset === 0; if (caretInFirstChild && caretAtTheBeginning) { codex.caret.setToPreviousBlock(editableElementIndex); } }; /** * Callback for enter key pressing in first-level block area */ callbacks.enterPressedOnBlock = function (event) { var NEW_BLOCK_TYPE = 'paragraph'; codex.content.insertBlock({ type: NEW_BLOCK_TYPE, block: codex.tools[NEW_BLOCK_TYPE].render() }, true); codex.toolbar.move(); codex.toolbar.open(); }; callbacks.backspacePressed = function (block) { var currentInputIndex = codex.caret.getCurrentInputIndex(), range, selectionLength, firstLevelBlocksCount; if (block.textContent.trim()) { range = codex.content.getRange(); selectionLength = range.endOffset - range.startOffset; if (codex.caret.position.atStart() && !selectionLength) { codex.content.mergeBlocks(currentInputIndex); } else { return; } } if (!selectionLength) { block.remove(); } firstLevelBlocksCount = codex.nodes.redactor.childNodes.length; /** * If all blocks are removed */ if (firstLevelBlocksCount === 0) { /** update currentNode variable */ codex.content.currentNode = null; /** Inserting new empty initial block */ codex.ui.addInitialBlock(); /** Updating inputs state after deleting last block */ codex.ui.saveInputs(); /** Set to current appended block */ setTimeout(function () { codex.caret.setToPreviousBlock(1); }, 10); } else { if (codex.caret.inputIndex !== 0) { /** Target block is not first */ codex.caret.setToPreviousBlock(codex.caret.inputIndex); } else { /** If we try to delete first block */ codex.caret.setToNextBlock(codex.caret.inputIndex); } } codex.toolbar.move(); if (!codex.toolbar.opened) { codex.toolbar.open(); } /** Updating inputs state */ codex.ui.saveInputs(); /** Prevent default browser behaviour */ event.preventDefault(); }; callbacks.blockPaste = function (event) { var currentInputIndex = codex.caret.getCurrentInputIndex(), node = codex.state.inputs[currentInputIndex]; setTimeout(function () { codex.content.sanitize(node); }, 10); }; callbacks._blockPaste = function (event) { var currentInputIndex = codex.caret.getCurrentInputIndex(); /** * create an observer instance */ var observer = new MutationObserver(codex.callback.handlePasteEvents); /** * configuration of the observer: */ var config = { attributes: true, childList: true, characterData: false }; // pass in the target node, as well as the observer options observer.observe(codex.state.inputs[currentInputIndex], config); }; /** * Sends all mutations to paste handler */ callbacks.handlePasteEvents = function (mutations) { mutations.forEach(codex.content.paste); }; /** * Clicks on block settings button */ callbacks.showSettingsButtonClicked = function () { /** * Get type of current block * It uses to append settings from tool.settings property. * ... * Type is stored in data-type attribute on block */ var currentToolType = codex.content.currentNode.dataset.tool; codex.toolbar.settings.toggle(currentToolType); /** Close toolbox when settings button is active */ codex.toolbar.toolbox.close(); codex.toolbar.settings.hideRemoveActions(); }; return callbacks; }({}); codex.callback = callbacks; module.exports = callbacks; /***/ }, /* 14 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = __webpack_require__(1); var draw = function (draw) { /** * Base editor wrapper */ draw.wrapper = function () { var wrapper = document.createElement('div'); wrapper.className += 'codex-editor'; return wrapper; }; /** * Content-editable holder */ draw.redactor = function () { var redactor = document.createElement('div'); redactor.className += 'ce-redactor'; return redactor; }; draw.ceBlock = function () { var block = document.createElement('DIV'); block.className += 'ce_block'; return block; }; /** * Empty toolbar with toggler */ draw.toolbar = function () { var bar = document.createElement('div'); bar.className += 'ce-toolbar'; return bar; }; draw.toolbarContent = function () { var wrapper = document.createElement('DIV'); wrapper.classList.add('ce-toolbar__content'); return wrapper; }; /** * Inline toolbar */ draw.inlineToolbar = function () { var bar = document.createElement('DIV'); bar.className += 'ce-toolbar-inline'; return bar; }; /** * Wrapper for inline toobar buttons */ draw.inlineToolbarButtons = function () { var wrapper = document.createElement('DIV'); wrapper.className += 'ce-toolbar-inline__buttons'; return wrapper; }; /** * For some actions */ draw.inlineToolbarActions = function () { var wrapper = document.createElement('DIV'); wrapper.className += 'ce-toolbar-inline__actions'; return wrapper; }; draw.inputForLink = function () { var input = document.createElement('INPUT'); input.type = 'input'; input.className += 'inputForLink'; input.placeholder = 'Type URL ...'; input.setAttribute('form', 'defaultForm'); input.setAttribute('autofocus', 'autofocus'); return input; }; /** * Block with notifications */ draw.alertsHolder = function () { var block = document.createElement('div'); block.classList.add('ce_notifications-block'); return block; }; /** * @todo Desc */ draw.blockButtons = function () { var block = document.createElement('div'); block.className += 'ce-toolbar__actions'; return block; }; /** * Block settings panel */ draw.blockSettings = function () { var settings = document.createElement('div'); settings.className += 'ce-settings'; return settings; }; draw.defaultSettings = function () { var div = document.createElement('div'); div.classList.add('ce-settings_default'); return div; }, draw.pluginsSettings = function () { var div = document.createElement('div'); div.classList.add('ce-settings_plugin'); return div; }; draw.plusButton = function () { var button = document.createElement('span'); button.className = 'ce-toolbar__plus'; // button.innerHTML = ''; return button; }; /** * Settings button in toolbar */ draw.settingsButton = function () { var toggler = document.createElement('span'); toggler.className = 'ce-toolbar__settings-btn'; /** Toggler button*/ toggler.innerHTML = ''; return toggler; }; /** * Redactor tools wrapper */ draw.toolbox = function () { var wrapper = document.createElement('div'); wrapper.className = 'ce-toolbar__tools'; return wrapper; }; /** * @protected * * Draws tool buttons for toolbox * * @param {String} type * @param {String} classname * @returns {Element} */ draw.toolbarButton = function (type, classname) { var button = document.createElement("li"), tool_icon = document.createElement("i"), tool_title = document.createElement("span"); button.dataset.type = type; button.setAttribute('title', type); tool_icon.classList.add(classname); tool_title.classList.add('ce_toolbar_tools--title'); button.appendChild(tool_icon); button.appendChild(tool_title); return button; }; /** * @protected * * Draws tools for inline toolbar * * @param {String} type * @param {String} classname */ draw.toolbarButtonInline = function (type, classname) { var button = document.createElement("BUTTON"), tool_icon = document.createElement("I"); button.type = "button"; button.dataset.type = type; tool_icon.classList.add(classname); button.appendChild(tool_icon); return button; }; /** * Redactor block */ draw.block = function (tagName, content) { var node = document.createElement(tagName); node.innerHTML = content || ''; return node; }; /** * Creates Node with passed tagName and className * @param {string} tagName * @param {string} className * @param {object} properties - allow to assign properties */ draw.node = function (tagName, className, properties) { var el = document.createElement(tagName); if (className) el.className = className; if (properties) { for (var name in properties) { el[name] = properties[name]; } } return el; }; draw.pluginsRender = function (type, content) { return { type: type, block: cEditor.tools[type].render({ text: content }) }; }; return draw; }({}); codex.draw = draw; module.exports = draw; /***/ }, /* 15 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = __webpack_require__(1); var caret = function (caret) { /** * @var {int} InputIndex - editable element in DOM */ caret.inputIndex = null; /** * @var {int} offset - caret position in a text node. */ caret.offset = null; /** * @var {int} focusedNodeIndex - we get index of child node from first-level block */ caret.focusedNodeIndex = null; /** * Creates Document Range and sets caret to the element. * @protected * @uses caret.save — if you need to save caret position * @param {Element} el - Changed Node. */ caret.set = function (el, index, offset) { offset = offset || this.offset || 0; index = index || this.focusedNodeIndex || 0; var childs = el.childNodes, nodeToSet; if (childs.length === 0) { nodeToSet = el; } else { nodeToSet = childs[index]; } /** If Element is INPUT */ if (el.tagName == 'INPUT') { el.focus(); return; } if (codex.core.isDomNode(nodeToSet)) { nodeToSet = codex.content.getDeepestTextNodeFromPosition(nodeToSet, nodeToSet.childNodes.length); } var range = document.createRange(), selection = window.getSelection(); setTimeout(function () { range.setStart(nodeToSet, offset); range.setEnd(nodeToSet, offset); selection.removeAllRanges(); selection.addRange(range); codex.caret.saveCurrentInputIndex(); }, 20); }; /** * @protected * Updates index of input and saves it in caret object */ caret.saveCurrentInputIndex = function () { /** Index of Input that we paste sanitized content */ var selection = window.getSelection(), inputs = codex.state.inputs, focusedNode = selection.anchorNode, focusedNodeHolder; if (!focusedNode) { return; } /** Looking for parent contentEditable block */ while (focusedNode.contentEditable != 'true') { focusedNodeHolder = focusedNode.parentNode; focusedNode = focusedNodeHolder; } /** Input index in DOM level */ var editableElementIndex = 0; while (focusedNode != inputs[editableElementIndex]) { editableElementIndex++; } this.inputIndex = editableElementIndex; }; /** * Returns current input index (caret object) */ caret.getCurrentInputIndex = function () { return this.inputIndex; }; /** * @param {int} index - index of first-level block after that we set caret into next input */ caret.setToNextBlock = function (index) { var inputs = codex.state.inputs, nextInput = inputs[index + 1]; if (!nextInput) { codex.core.log('We are reached the end'); return; } /** * When new Block created or deleted content of input * We should add some text node to set caret */ if (!nextInput.childNodes.length) { var emptyTextElement = document.createTextNode(''); nextInput.appendChild(emptyTextElement); } codex.caret.inputIndex = index + 1; codex.caret.set(nextInput, 0, 0); codex.content.workingNodeChanged(nextInput); }; /** * @param {int} index - index of target input. * Sets caret to input with this index */ caret.setToBlock = function (index) { var inputs = codex.state.inputs, targetInput = inputs[index]; console.assert(targetInput, 'caret.setToBlock: target input does not exists'); if (!targetInput) { return; } /** * When new Block created or deleted content of input * We should add some text node to set caret */ if (!targetInput.childNodes.length) { var emptyTextElement = document.createTextNode(''); targetInput.appendChild(emptyTextElement); } codex.caret.inputIndex = index; codex.caret.set(targetInput, 0, 0); codex.content.workingNodeChanged(targetInput); }; /** * @param {int} index - index of input */ caret.setToPreviousBlock = function (index) { index = index || 0; var inputs = codex.state.inputs, previousInput = inputs[index - 1], lastChildNode, lengthOfLastChildNode, emptyTextElement; if (!previousInput) { codex.core.log('We are reached first node'); return; } lastChildNode = codex.content.getDeepestTextNodeFromPosition(previousInput, previousInput.childNodes.length); lengthOfLastChildNode = lastChildNode.length; /** * When new Block created or deleted content of input * We should add some text node to set caret */ if (!previousInput.childNodes.length) { emptyTextElement = document.createTextNode(''); previousInput.appendChild(emptyTextElement); } codex.caret.inputIndex = index - 1; codex.caret.set(previousInput, previousInput.childNodes.length - 1, lengthOfLastChildNode); codex.content.workingNodeChanged(inputs[index - 1]); }; caret.position = { atStart: function atStart() { var selection = window.getSelection(), anchorOffset = selection.anchorOffset, anchorNode = selection.anchorNode, firstLevelBlock = codex.content.getFirstLevelBlock(anchorNode), pluginsRender = firstLevelBlock.childNodes[0]; if (!codex.core.isDomNode(anchorNode)) { anchorNode = anchorNode.parentNode; } var isFirstNode = anchorNode === pluginsRender.childNodes[0], isOffsetZero = anchorOffset === 0; return isFirstNode && isOffsetZero; }, atTheEnd: function atTheEnd() { var selection = window.getSelection(), anchorOffset = selection.anchorOffset, anchorNode = selection.anchorNode; /** Caret is at the end of input */ return !anchorNode || !anchorNode.length || anchorOffset === anchorNode.length; } }; return caret; }({}); codex.caret = caret; module.exports = caret; /***/ }, /* 16 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = __webpack_require__(1); var notifications = function (notifications) { /** * Error notificator. Shows block with message * @protected */ notifications.errorThrown = function (errorMsg, event) { codex.notifications.send('This action is not available currently', event.type, false); }, /** * Appends notification with different types * @param message {string} - Error or alert message * @param type {string} - Type of message notification. Ex: Error, Warning, Danger ... * @param append {boolean} - can be True or False when notification should be inserted after */ notifications.send = function (message, type, append) { var notification = codex.draw.block('div'); notification.textContent = message; notification.classList.add('ce_notification-item', 'ce_notification-' + type, 'flipInX'); if (!append) { codex.nodes.notifications.innerHTML = ''; } codex.nodes.notifications.appendChild(notification); setTimeout(function () { notification.remove(); }, 3000); }; return notifications; }({}); codex.notifications = notifications; module.exports = notifications; /***/ }, /* 17 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var codex = __webpack_require__(1); var parser = function (parser) { parser.init = function () {}; /** * Splits content by `\n` and returns blocks */ parser.getSeparatedTexttSeparatedTextFromContent = function (content) { return content.split('\n'); }; /** inserting text */ parser.insertPastedContent = function (content) { var blocks = this.getSeparatedTextFromContent(content), i, inputIndex = cEditor.caret.getCurrentInputIndex(), textNode, parsedTextContent; for (i = 0; i < blocks.length; i++) { blocks[i].trim(); if (blocks[i]) { var data = cEditor.draw.pluginsRender('paragraph', blocks[i]); cEditor.content.insertBlock(data); } } }; /** * Asynchronously parses textarea input string to HTML editor blocks */ parser.parseTextareaContent = function () { var initialContent = cEditor.nodes.textarea.value; if (initialContent.trim().length === 0) return true; cEditor.parser /** Get child nodes async-aware */ .getNodesFromString(initialContent) /** Then append nodes to the redactor */ .then(cEditor.parser.appendNodesToRedactor) /** Write log if something goes wrong */ .catch(function (error) { cEditor.core.log('Error while parsing content: %o', 'warn', error); }); }; /** * Parses string to nodeList * @param string inputString * @return Primise -> nodeList */ parser.getNodesFromString = function (inputString) { return Promise.resolve().then(function () { var contentHolder = document.createElement('div'); contentHolder.innerHTML = inputString; /** * Returning childNodes will include: * - Elements (html-tags), * - Texts (empty-spaces or non-wrapped strings ) * - Comments and other */ return contentHolder.childNodes; }); }; /** * Appends nodes to the redactor * @param nodeList nodes - list for nodes to append */ parser.appendNodesToRedactor = function (nodes) { /** * Sequence of one-by-one nodes appending * Uses to save blocks order after async-handler */ var nodeSequence = Promise.resolve(); for (var index = 0; index < nodes.length; index++) { /** Add node to sequence at specified index */ cEditor.parser.appendNodeAtIndex(nodeSequence, nodes, index); } }; /** * Append node at specified index */ parser.appendNodeAtIndex = function (nodeSequence, nodes, index) { /** We need to append node to sequence */ nodeSequence /** first, get node async-aware */ .then(function () { return cEditor.parser.getNodeAsync(nodes, index); }) /** * second, compose editor-block from node * and append it to redactor */ .then(function (node) { var block = cEditor.parser.createBlockByDomNode(node); if (cEditor.core.isDomNode(block)) { block.contentEditable = "true"; /** Mark node as redactor block*/ block.classList.add('ce-block'); /** Append block to the redactor */ cEditor.nodes.redactor.appendChild(block); /** Save block to the cEditor.state array */ cEditor.state.blocks.push(block); return block; } return null; }).then(cEditor.ui.addBlockHandlers) /** Log if something wrong with node */ .catch(function (error) { cEditor.core.log('Node skipped while parsing because %o', 'warn', error); }); }; /** * Asynchronously returns node from nodeList by index * @return Promise to node */ parser.getNodeAsync = function (nodeList, index) { return Promise.resolve().then(function () { return nodeList.item(index); }); }; /** * Creates editor block by DOM node * * First-level blocks (see cEditor.settings.blockTags) saves as-is, * other wrapps with

-tag * * @param DOMnode node * @return First-level node (paragraph) */ parser.createBlockByDomNode = function (node) { /** First level nodes already appears as blocks */ if (cEditor.parser.isFirstLevelBlock(node)) { /** Save plugin type in data-type */ node = this.storeBlockType(node); return node; } /** Other nodes wraps into parent block (paragraph-tag) */ var parentBlock, nodeContent = node.textContent.trim(), isPlainTextNode = node.nodeType != cEditor.core.nodeTypes.TAG; /** Skip empty textNodes with space-symbols */ if (isPlainTextNode && !nodeContent.length) return null; /** Make

tag */ parentBlock = cEditor.draw.block('P'); if (isPlainTextNode) { parentBlock.textContent = nodeContent.replace(/(\s){2,}/, '$1'); // remove double spaces } else { parentBlock.appendChild(node); } /** Save plugin type in data-type */ parentBlock = this.storeBlockType(parentBlock); return parentBlock; }; /** * It's a crutch * - - - - - - - * We need block type stored as data-attr * Now supports only simple blocks : P, HEADER, QUOTE, CODE * Remove it after updating parser module for the block-oriented structure: * - each block must have stored type * @param {Element} node */ parser.storeBlockType = function (node) { switch (node.tagName) { case 'P': node.dataset.tool = 'paragraph';break; case 'H1': case 'H2': case 'H3': case 'H4': case 'H5': case 'H6': node.dataset.tool = 'header';break; case 'BLOCKQUOTE': node.dataset.tool = 'quote';break; case 'CODE': node.dataset.tool = 'code';break; } return node; }; /** * Check DOM node for display style: separated block or child-view */ parser.isFirstLevelBlock = function (node) { return node.nodeType == cEditor.core.nodeTypes.TAG && node.classList.contains(cEditor.ui.className.BLOCK_CLASSNAME); }; return parser; }({}); parser.init(); codex.parser = parser; module.exports = parser; /***/ } /******/ ]); //# sourceMappingURL=codex-editor.js.map