Parser and UI modules

This commit is contained in:
Peter S 2016-02-05 11:25:52 +03:00
parent 32da685e84
commit f523879cda
3 changed files with 386 additions and 146 deletions

View file

@ -7,15 +7,26 @@ var cEditor = (function (cEditor) {
// Default settings
cEditor.settings = {
tools : ['header', 'picture', 'list', 'quote', 'code', 'twitter', 'instagram', 'smile'],
textareaId : 'codex-editor'
tools : ['header', 'picture', 'list', 'quote', 'code', 'twitter', 'instagram', 'smile'],
textareaId : 'codex-editor',
// First-level tags viewing as separated blocks. Other'll be inserted as child
blockTags : ['P','BLOCKQUOTE','UL','CODE','OL','H1','H2','H3','H4','H5','H6']
};
// Static nodes
cEditor.nodes = {
textarea : null,
editor : null,
toolbar : null
wrapper : null,
toolbar : null,
toolbarButtons : {}, // {type : DomEl, ... }
redactor : null
}
// Current editor state
cEditor.state = {
html : '',
blocks : []
}
/**
@ -31,9 +42,10 @@ var cEditor = (function (cEditor) {
// Prepare editor settings
this.core.prepare(userSettings)
// If all ok, make UI, parse content and bind events
// If all ok, make UI, bind events and parse initial-content
.then(this.ui.make)
.then(this.ui.bindEvents)
.then(this.parser.parseTextareaContent)
.catch(function (error) {
cEditor.core.log('Initialization failed with error: %o', 'warn', error);
})
@ -50,7 +62,8 @@ var cEditor = (function (cEditor) {
* Methods:
* - init
* - log
* - el
* - insertAfter
* - isDomNode
*/
cEditor.core = {
@ -104,26 +117,26 @@ cEditor.core = {
},
/**
* Returns element by selector
* @todo Not using now. Check for necessity
* Helper for insert one element after another
*/
el : function (selector, parent) {
insertAfter : function (target, element) {
target.parentNode.insertBefore(element, target.nextSibling);
},
var el = null;
/**
* DOM node types map
*/
nodeTypes : {
TAG : 1,
TEXT : 3,
COMMENT : 8
},
parent = parent || document;
if ( selector.substring(0,1) == '#' ){
el = parent.getElementById(selector.substring(1));
if ( typeof el != undefined )
return el;
} else {
el = parent.querySelectorAll(selector);
if ( el.length !== 0 )
return el;
}
return el;
/**
* Check object for DOM node
*/
isDomNode : function (el) {
return el && typeof el === 'object' && el.nodeType && el.nodeType == this.nodeTypes.TAG;
}
}
@ -135,20 +148,40 @@ cEditor.ui = {
*/
make : function () {
cEditor.core.log('ui.make fired', 'info');
var wrapper,
toolbar,
tool,
redactor;
// Making toolbar ...
/** Make editor wrapper */
wrapper = cEditor.draw.wrapper();
// Making 'plus' button ...
/** Append editor wrapper after initial textarea */
cEditor.core.insertAfter(cEditor.nodes.textarea, wrapper);
},
/**
* Parses input string to HTML editor content
*/
parseContent : function () {
/** Make toolbar and content-editable redactor */
toolbar = cEditor.draw.toolbar();
redactor = cEditor.draw.redactor();
cEditor.core.log('ui.parseContent fired', 'info');
wrapper.appendChild(toolbar);
wrapper.appendChild(redactor);
/** Make toolbar buttons */
cEditor.settings.tools.forEach(function(type) {
tool = cEditor.draw.toolbarButton(type);
toolbar.appendChild(tool);
/** Save tools to static nodes */
cEditor.nodes.toolbarButtons[type] = tool;
});
/** Save created ui-elements to static nodes state */
cEditor.nodes.wrapper = wrapper;
cEditor.nodes.toolbar = toolbar;
cEditor.nodes.redactor = redactor;
},
@ -161,4 +194,262 @@ cEditor.ui = {
}
}
/**
* Content parsing module
*/
cEditor.parser = {
/**
* Asynchronously parses textarea input string to HTML editor blocks
*/
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
*/
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
*/
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
*/
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) ) {
/** Append block to the redactor */
cEditor.nodes.redactor.appendChild(block);
/** Save block to the cEditor.state array */
cEditor.state.blocks.push(block);
};
})
/** 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
*/
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 <p>-tag
*
* @param DOMnode node
* @return First-level node (paragraph)
*/
createBlockByDomNode : function (node) {
/** First level nodes already appears as blocks */
if ( cEditor.parser.isFirstLevelBlock(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 <p> tag */
parentBlock = cEditor.draw.block('P');
if (isPlainTextNode){
parentBlock.textContent = nodeContent.replace(/(\s){2,}/, '$1'); // remove double spaces
} else {
parentBlock.appendChild(node);
}
return parentBlock;
},
/**
* Check DOM node for display style: separated block or child-view
*/
isFirstLevelBlock : function (node) {
return node.nodeType == cEditor.core.nodeTypes.TAG &&
cEditor.settings.blockTags.indexOf(node.tagName) !== -1;
}
};
/**
* Creates HTML elements
*/
cEditor.draw = {
/**
* Base editor wrapper
*/
wrapper : function () {
var wrapper = document.createElement('div');
wrapper.className += 'ce_wrapper';
return wrapper;
},
/**
* Content-editable holder
*/
redactor : function () {
var redactor = document.createElement('div');
redactor.className += 'ce_redactor';
redactor.contentEditable = true;
return redactor;
},
/**
* Empty toolbar with toggler
*/
toolbar : function () {
var bar = document.createElement('div');
bar.className += 'ce_toolbar';
/** Toggler button*/
bar.innerHTML = '<span class="toggler">' +
'<i class="plus_btn ce_icon-plus-circled-1"></i>'+
'</span>';
return bar;
},
/**
* Toolbar button
*/
toolbarButton : function (type) {
var button = document.createElement("li");
button.dataset.type = type;
button.innerHTML = '<i class="ce_icon-' + type + '"></i>';
return button;
},
/**
* Redactor block
*/
block : function (tagName, content) {
var node = document.createElement(tagName);
node.innerHTML = content || '';
return node;
}
}

View file

@ -70,108 +70,48 @@
/* EDITOR */
.hidden {display: none !important;}
.hidden_file {position: absolute; opacity: 0; z-index: -1; left:-99999px; }
.codex_editor button{
border: 0;
outline: none;
background: transparent;
}
.codex_editor [contenteditable]{
.ce_redactor {
position: relative;
outline: none;
}
.codex_editor .node{
position: relative;
z-index: 5;
min-height: 20px;
}
.codex_editor .add_buttons{
color: #3b4352;
font-size: 16px;
/* margin-left: -42px; */
/* margin-top: -50px; */
/* margin-bottom: -25px; */
.ce_toolbar{
position: absolute;
/* visibility: hidden; */
opacity: 0;
transition: opacity .15s ease-in-out;
top: 0;
left: -35px;
white-space: nowrap;
}
z-index: 2;
margin-left: -45px;
transform: translateY(100px);
overflow: hidden;
.add_buttons.show {
background: #fff;
z-index: 10;
opacity: 1;
border: 1px solid #e3e7ee;
border-radius: 2px;
box-shadow: 0 2px 11px rgba(27,39,54,.11);
color: #2e394b;
}
.add_buttons.show .buttons {display:none;}
.codex_editor .node.selected + .add_buttons{visibility:visible;}
.add_buttons .buttons {
position: absolute;
top: 1px;
background: wheat;
padding: 2px;
border-radius: 3px;
display: none;
}
.codex_editor .add_buttons button:hover,
.codex_editor .add_buttons .focused{
color: #3770ef;
}
.codex_editor .add_buttons button{
transition: all 150ms ease-in;
transform: translate3d(-50px, 0 , 0);
opacity: 0;
font-size: 14px;
}
.codex_editor .buttons_toggled{
background: #fff;
z-index: 10;
opacity: 1;
}
.codex_editor .buttons_toggled button{
opacity: 1;
transform: translate3d(0,0,0);
}
.codex_editor .toggler{
display: inline-block;
font-size: 23px;
color: #387ff5;
transition: transform 100ms ease-in;
cursor: pointer;
}
.codex_editor .toggler .buttons {
position: relative;
top: -2px;
}
.codex_editor .buttons_toggled .toggler{
transform: rotate(45deg);
}
.codex_editor .buttons_toggled .buttons{
display: inline;
}
.ce_toolbar .toggler{
color: #3e6dd6
}
.ce_toolbar .toggler,
.ce_toolbar li
{
display: inline-block;
padding: 12px;
cursor: pointer;
font-size: 14px;
}
.ce_toolbar .selected,
.ce_toolbar li:hover
{
background: #3e6dd6;
color: #e2edff;
}
/** Typography styles */
.codex_editor p{
.ce_redactor p{
padding: 5px 0;
font-size: 1em;
margin: 0;
}
.codex_editor {position: relative;}
.codex_editor .ce_content {
}

View file

@ -22,8 +22,10 @@
<br>
<form action="">
<textarea name="" id="codex_editor" cols="30" rows="10" style="width: 100%;height: 300px;">
<h2>Введение</h2>
<textarea hidden name="" id="codex_editor" cols="30" rows="10" style="width: 100%;height: 300px;">
<h2>Введение</h2> 1111
2222222 <a href="//link.ru">Link</a>444444
<p>На днях я получил очередной проект по разработке личного кабинета.<br> Как обычно, я открыл консоль, чтобы посмотреть историю проекта, ветки и все ли правки закомичены (от слова commit - фиксировать). Однако ничего из этого я не узнал — проект не содержал .git репозитория.<br> Эта ситуация в очередной раз заставила задуматься о том, как много разработчиков до сих пор не понимают необходимость контролировать изменения в файлах с исходным кодом. А многие и вовсе не знают что это такое, и как этим пользоваться.</p>
<h2>Почему нужно использовать систему контроля версий</h2>
@ -42,33 +44,40 @@
</form>
<!-- <div class="codex_editor editor_wrapper">
<div class="editor_content">
<div class="node">
<!--
<div class="ce_wrapper">
<div class="add_buttons example">
<div class="ce_toolbar">
<span class="toggler"><i class="ce_icon-plus-circled-1"></i></span>
<span class="toggler"><i class="ce_icon-plus-circled-1"></i></span>
<button data-type="header"><i class="ce_icon-header"></i></button>
<button data-type="picture"><i class="ce_icon-picture"></i></button>
<button data-type="list"><i class="ce_icon-list"></i></button>
<button data-type="quote"><i class="ce_icon-quote"></i></button>
<button data-type="code"><i class="ce_icon-code"></i></button>
<button data-type="twitter"><i class="ce_icon-twitter"></i></button>
<button data-type="instagram"><i class="ce_icon-instagram"></i></button>
<button data-type="smile"><i class="ce_icon-smile"></i></button>
<li data-type="header"><i class="ce_icon-header"></i></li>
<li data-type="picture"><i class="ce_icon-picture"></i></li>
<li data-type="list"><i class="ce_icon-list"></i></li>
<li data-type="quote"><i class="ce_icon-quote"></i></li>
<li data-type="code"><i class="ce_icon-code"></i></li>
<li data-type="twitter"><i class="ce_icon-twitter"></i></li>
<li data-type="instagram"><i class="ce_icon-instagram"></i></li>
<li data-type="smile"><i class="ce_icon-smile"></i></li>
</div>
<p class="content" contenteditable="true">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Et, eligendi cum amet unde nobis est asperiores similique odio quisquam nostrum quidem, vero esse culpa reprehenderit delectus debitis. Odio, optio, sed.
</p>
</div>
</div>
</div> -->
<div class="ce_redactor" contenteditable="true">
<h2>Введение</h2>
<p>На днях я получил очередной проект по разработке личного кабинета.<br> Как обычно, я открыл консоль, чтобы посмотреть историю проекта, ветки и все ли правки закомичены (от слова commit - фиксировать). Однако ничего из этого я не узнал — проект не содержал .git репозитория.<br> Эта ситуация в очередной раз заставила задуматься о том, как много разработчиков до сих пор не понимают необходимость контролировать изменения в файлах с исходным кодом. А многие и вовсе не знают что это такое, и как этим пользоваться.</p>
<h2>Почему нужно использовать систему контроля версий</h2>
<p>Основные преимущества:</p>
<ul>
<li>одновременная работа нескольких человек над проектом</li>
<li>возможность быстро обнаружить и откатить, все не зафиксированные изменения</li>
<li>возможность быстро откатить ошибочные, уже зафиксированные, изменения</li>
<li>история всех изменений в проекте, с указанием даты и авторов</li>
<li>возможность изучить процесс развития проекта</li>
</ul>
</div>
</div>
-->
</body>