mirror of
https://github.com/codex-team/editor.js
synced 2024-05-26 10:32:18 +02:00
Parser and UI modules
This commit is contained in:
parent
32da685e84
commit
f523879cda
355
codex-editor.js
355
codex-editor.js
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
122
editor.css
122
editor.css
|
@ -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 {
|
||||
}
|
55
example.html
55
example.html
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue