Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Caret module: initial #242

Merged
merged 14 commits into from
Jan 4, 2018
1,326 changes: 920 additions & 406 deletions build/codex-editor.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build/codex-editor.js.map

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions docs/caret.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# CodeX Editor Caret Module

The `Caret` module contains methods working with caret. Uses [Range](https://developer.mozilla.org/en-US/docs/Web/API/Range) methods to navigate caret
between blocks.

Caret class implements basic Module class that holds User configuration
and default CodeXEditor instances

You can read more about Module class [here]()

## Properties

## Methods


### setToBlock

```javascript
Caret.setToBlock(block, offset, atEnd)
```


> Method gets Block instance and puts caret to the text node with offset

#### params

| Param | Type | Description|
| -------------|------ |:-------------:|
| block | Object | Block instance that BlockManager created|
| offset | Number | caret offset regarding to the text node (Default: 0)|
| atEnd | Boolean | puts caret at the end of last text node|


### setToTheLastBlock

```javascript
Caret.setToTheLastBlock()
```

> sets Caret at the end of last Block
If last block is not empty, inserts another empty Block which is passed as initial
4 changes: 2 additions & 2 deletions example/example.html
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@
{
type : 'text',
data : {
text : 'Пишите нам на team@ifmo.su'
text : '<p><b>CodeX</b> Привет!!!</p>'
}
},
}
]
}
});
Expand Down
2 changes: 1 addition & 1 deletion src/components/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export default class Block {

}

let emptyText = this._html.textContent.trim().length === 0,
let emptyText = $.isEmpty(this.pluginsContent),
emptyMedia = !this.hasMedia;

return emptyText && emptyMedia;
Expand Down
173 changes: 171 additions & 2 deletions src/components/dom.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* DOM manupulations helper
* DOM manipulations helper
*/
export default class Dom {

Expand All @@ -13,7 +13,7 @@ export default class Dom {
*/
static make(tagName, classNames = null, attributes = {}) {

var el = document.createElement(tagName);
let el = document.createElement(tagName);

if ( Array.isArray(classNames) ) {

Expand All @@ -35,6 +35,17 @@ export default class Dom {

}

/**
* Creates Text Node with the passed content
* @param {String} content - text content
* @return {Text}
*/
static text(content) {

return document.createTextNode(content);

}

/**
* Append one or several elements to the parent
*
Expand Down Expand Up @@ -86,6 +97,51 @@ export default class Dom {

}

/**
* Search for deepest node which is Leaf.
* Leaf is the vertex that doesn't have any child nodes
*
* @description Method recursively goes throw the all Node until it finds the Leaf
*
* @param {Element} node - root Node. From this vertex we start Deep-first search {@link https://en.wikipedia.org/wiki/Depth-first_search}
* @param {Boolean} atLast - find last text node
* @return {Node} - it can be text Node or Element Node, so that caret will able to work with it
*/
static getDeepestNode(node, atLast = false) {

if (node.childNodes.length === 0) {

/**
* We need to return an empty text node
* But caret will not be placed in empty textNode, so we need textNode with zero-width char
*/
if (this.isElement(node) && !this.isNativeInput(node)) {

let emptyTextNode = this.text('\u200B');

node.appendChild(emptyTextNode);

}

return node;

}

let childsLength = node.childNodes.length,
last = childsLength - 1;

if (atLast) {

return this.getDeepestNode(node.childNodes[last], atLast);

} else {

return this.getDeepestNode(node.childNodes[0], false);

}

}

/**
* Check if object is DOM node
*
Expand All @@ -98,4 +154,117 @@ export default class Dom {

}

/**
* Checks target if it is native input
* @param {Element|String} target - HTML element or string
* @return {Boolean}
*/
static isNativeInput(target) {

let nativeInputs = [
'INPUT',
'TEXTAREA'
];

return target ? nativeInputs.includes(target.tagName) : false;

}

/**
* Checks node if it is empty
*
* @description Method checks simple Node without any childs for emptiness
* If you have Node with 2 or more children id depth, you better use {@link Dom#isEmpty} method
*
* @param {Node} node
* @return {Boolean} true if it is empty
*/
static isNodeEmpty(node) {

let nodeText;

if ( this.isElement(node) && this.isNativeInput(node) ) {

nodeText = node.value;

} else {

nodeText = node.textContent.replace('\u200B', '');

}

return nodeText.trim().length === 0;

}

/**
* checks node if it is doesn't have any child nodes
* @param {Node} node
* @return {boolean}
*/
static isLeaf(node) {

if (!node) {

return false;

}

return node.childNodes.length === 0;

}

/**
* breadth-first search (BFS)
* {@link https://en.wikipedia.org/wiki/Breadth-first_search}
*
* @description Pushes to stack all DOM leafs and checks for emptiness
*
* @param {Node} node
* @return {boolean}
*/
static isEmpty(node) {

let treeWalker = [],
leafs = [];

if (!node) {

return false;

}

treeWalker.push(node);

while ( treeWalker.length > 0 ) {

if ( this.isLeaf(node) ) {

leafs.push(node);

}

while ( node && node.nextSibling ) {

node = node.nextSibling;

if (!node) continue;

treeWalker.push(node);

}

node = treeWalker.shift();

if (!node) continue;

node = node.firstChild;
treeWalker.push(node);

}

return leafs.every( leaf => this.isNodeEmpty(leaf)) ;

}

};
Loading