diff --git a/lib/DOM.js b/lib/DOM.js
index 0f3e2447..2749fa44 100644
--- a/lib/DOM.js
+++ b/lib/DOM.js
@@ -222,31 +222,48 @@ angular.module('textAngular.DOM', ['textAngular.factories'])
}catch(e){}
};
};
-}]).service('taSelection', ['$window', '$document',
+}]).service('taSelection', ['$window', '$document', 'taDOM',
/* istanbul ignore next: all browser specifics and PhantomJS dosen't seem to support half of it */
-function($window, $document){
+function($window, $document, taDOM){
// need to dereference the document else the calls don't work correctly
var _document = $document[0];
var rangy = $window.rangy;
+ var brException = function (element, offset) {
+ /* check if selection is a BR element at the beginning of a container. If so, get
+ * the parentNode instead.
+ * offset should be zero in this case. Otherwise, return the original
+ * element.
+ */
+ if (element.tagName && element.tagName.match(/^br$/i) && offset === 0 && !element.previousSibling) {
+ return {
+ element: element.parentNode,
+ offset: 0
+ };
+ } else {
+ return {
+ element: element,
+ offset: offset
+ };
+ }
+ };
var api = {
getSelection: function(){
var range = rangy.getSelection().getRangeAt(0);
var container = range.commonAncestorContainer;
- // Check if the container is a text node and return its parent if so
- container = container.nodeType === 3 ? container.parentNode : container;
- return {
- start: {
- element: range.startContainer,
- offset: range.startOffset
- },
- end: {
- element: range.endContainer,
- offset: range.endOffset
- },
- container: container,
+ var selection = {
+ start: brException(range.startContainer, range.startOffset),
+ end: brException(range.endContainer, range.endOffset),
collapsed: range.collapsed
-
};
+ // Check if the container is a text node and return its parent if so
+ container = container.nodeType === 3 ? container.parentNode : container;
+ if (container.parentNode === selection.start.element ||
+ container.parentNode === selection.end.element) {
+ selection.container = container.parentNode;
+ } else {
+ selection.container = container;
+ }
+ return selection;
},
getOnlySelectedElements: function(){
var range = rangy.getSelection().getRangeAt(0);
@@ -304,12 +321,13 @@ function($window, $document){
// from http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
// topNode is the contenteditable normally, all manipulation MUST be inside this.
insertHtml: function(html, topNode){
- var parent, secondParent, _childI, nodes, startIndex, startNodes, endNodes, i, lastNode;
+ var parent, secondParent, _childI, nodes, startIndex, startNodes, endNodes, i, lastNode, _tempFrag;
var element = angular.element("
" + html + "
");
var range = rangy.getSelection().getRangeAt(0);
var frag = _document.createDocumentFragment();
var children = element[0].childNodes;
var isInline = true;
+
if(children.length > 0){
// NOTE!! We need to do the following:
// check for blockelements - if they exist then we have to split the current element in half (and all others up to the closest block element) and insert all children in-between.
@@ -336,41 +354,68 @@ function($window, $document){
if(isInline){
range.deleteContents();
}else{ // not inline insert
- if(range.collapsed && range.startContainer !== topNode && range.startContainer.parentNode !== topNode){
- // split element into 2 and insert block element in middle
- if(range.startContainer.nodeType === 3){ // if text node
- parent = range.startContainer.parentNode;
- nodes = parent.childNodes;
- // split the nodes into two lists - before and after, splitting the node with the selection into 2 text nodes.
- startNodes = [];
- endNodes = [];
- for(startIndex = 0; startIndex < nodes.length; startIndex++){
- startNodes.push(nodes[startIndex]);
- if(nodes[startIndex] === range.startContainer) break;
+ if(range.collapsed && range.startContainer !== topNode){
+ if(range.startContainer.innerHTML && range.startContainer.innerHTML.match(/^<[^>]*>$/i)){
+ // this log is to catch when innerHTML is something like ``
+ parent = range.startContainer;
+ if(range.startOffset === 1){
+ // before single tag
+ range.setStartAfter(parent);
+ range.setEndAfter(parent);
+ }else{
+ // after single tag
+ range.setStartBefore(parent);
+ range.setEndBefore(parent);
}
- endNodes.push(_document.createTextNode(range.startContainer.nodeValue.substring(range.startOffset)));
- range.startContainer.nodeValue = range.startContainer.nodeValue.substring(0, range.startOffset);
- for(i = startIndex + 1; i < nodes.length; i++) endNodes.push(nodes[i]);
-
- secondParent = parent.cloneNode();
- parent.childNodes = startNodes;
- secondParent.childNodes = endNodes;
}else{
- parent = range.startContainer;
- secondParent = parent.cloneNode();
- secondParent.innerHTML = parent.innerHTML.substring(range.startOffset);
- parent.innerHTML = parent.innerHTML.substring(0, range.startOffset);
- }
- angular.element(parent).after(secondParent);
- // put cursor to end of inserted content
- range.setStartAfter(parent);
- range.setEndAfter(parent);
- if(/^(|
)$/i.test(parent.innerHTML.trim())){
- range.setStartBefore(parent);
- range.setEndBefore(parent);
- angular.element(parent).remove();
+ // split element into 2 and insert block element in middle
+ if(range.startContainer.nodeType === 3 && range.startContainer.parentNode !== topNode){ // if text node
+ parent = range.startContainer.parentNode;
+ secondParent = parent.cloneNode();
+ // split the nodes into two lists - before and after, splitting the node with the selection into 2 text nodes.
+ taDOM.splitNodes(parent.childNodes, parent, secondParent, range.startContainer, range.startOffset);
+
+ // Escape out of the inline tags like b
+ while(!VALIDELEMENTS.test(parent.nodeName)){
+ angular.element(parent).after(secondParent);
+ parent = parent.parentNode;
+ var _lastSecondParent = secondParent;
+ secondParent = parent.cloneNode();
+ // split the nodes into two lists - before and after, splitting the node with the selection into 2 text nodes.
+ taDOM.splitNodes(parent.childNodes, parent, secondParent, _lastSecondParent);
+ }
+ }else{
+ parent = range.startContainer;
+ secondParent = parent.cloneNode();
+ taDOM.splitNodes(parent.childNodes, parent, secondParent, undefined, undefined, range.startOffset);
+ }
+
+ angular.element(parent).after(secondParent);
+ // put cursor to end of inserted content
+ range.setStartAfter(parent);
+ range.setEndAfter(parent);
+
+ if(/^(|
)$/i.test(parent.innerHTML.trim())){
+ range.setStartBefore(parent);
+ range.setEndBefore(parent);
+ angular.element(parent).remove();
+ }
+ if(/^(|
)$/i.test(secondParent.innerHTML.trim())) angular.element(secondParent).remove();
+ if(parent.nodeName.toLowerCase() === 'li'){
+ _tempFrag = _document.createDocumentFragment();
+ for(i = 0; i < frag.childNodes.length; i++){
+ element = angular.element('');
+ taDOM.transferChildNodes(frag.childNodes[i], element[0]);
+ taDOM.transferNodeAttributes(frag.childNodes[i], element[0]);
+ _tempFrag.appendChild(element[0]);
+ }
+ frag = _tempFrag;
+ if(lastNode){
+ lastNode = frag.childNodes[frag.childNodes.length - 1];
+ lastNode = lastNode.childNodes[lastNode.childNodes.length - 1];
+ }
+ }
}
- if(/^(|
)$/i.test(secondParent.innerHTML.trim())) angular.element(secondParent).remove();
}else{
range.deleteContents();
}
@@ -382,4 +427,53 @@ function($window, $document){
}
};
return api;
-}]);
\ No newline at end of file
+}]).service('taDOM', function(){
+ var taDOM = {
+ // recursive function that returns an array of angular.elements that have the passed attribute set on them
+ getByAttribute: function(element, attribute){
+ var resultingElements = [];
+ var childNodes = element.children();
+ if(childNodes.length){
+ angular.forEach(childNodes, function(child){
+ resultingElements = resultingElements.concat(taDOM.getByAttribute(angular.element(child), attribute));
+ });
+ }
+ if(element.attr(attribute) !== undefined) resultingElements.push(element);
+ return resultingElements;
+ },
+
+ transferChildNodes: function(source, target){
+ // clear out target
+ target.innerHTML = '';
+ while(source.childNodes.length > 0) target.appendChild(source.childNodes[0]);
+ return target;
+ },
+
+ splitNodes: function(nodes, target1, target2, splitNode, subSplitIndex, splitIndex){
+ if(!splitNode && isNaN(splitIndex)) throw new Error('taDOM.splitNodes requires a splitNode or splitIndex');
+ var startNodes = document.createDocumentFragment();
+ var endNodes = document.createDocumentFragment();
+ var index = 0;
+
+ while(nodes.length > 0 && (isNaN(splitIndex) || splitIndex !== index) && nodes[0] !== splitNode){
+ startNodes.appendChild(nodes[0]); // this removes from the nodes array (if proper childNodes object.
+ index++;
+ }
+
+ if(!isNaN(subSplitIndex) && subSplitIndex >= 0 && nodes[0]){
+ startNodes.appendChild(document.createTextNode(nodes[0].nodeValue.substring(0, subSplitIndex)));
+ nodes[0].nodeValue = nodes[0].nodeValue.substring(subSplitIndex);
+ }
+ while(nodes.length > 0) endNodes.appendChild(nodes[0]);
+
+ taDOM.transferChildNodes(startNodes, target1);
+ taDOM.transferChildNodes(endNodes, target2);
+ },
+
+ transferNodeAttributes: function(source, target){
+ for(var i = 0; i < source.attributes.length; i++) target.setAttribute(source.attributes[i].name, source.attributes[i].value);
+ return target;
+ }
+ };
+ return taDOM;
+});
\ No newline at end of file