
META-INF.resources.bower_components.textAngular.src.DOM.js Maven / Gradle / Ivy
angular.module('textAngular.DOM', ['textAngular.factories'])
.factory('taExecCommand', ['taSelection', 'taBrowserTag', '$document', function (taSelection, taBrowserTag, $document) {
var listToDefault = function (listElement, defaultWrap) {
var $target, i;
// if all selected then we should remove the list
// grab all li elements and convert to taDefaultWrap tags
var children = listElement.find('li');
for (i = children.length - 1; i >= 0; i--) {
$target = angular.element('<' + defaultWrap + '>' + children[i].innerHTML + '' + defaultWrap + '>');
listElement.after($target);
}
listElement.remove();
taSelection.setSelectionToElementEnd($target[0]);
};
var listElementToSelfTag = function (list, listElement, selfTag, bDefault, defaultWrap) {
var $target, i;
// if all selected then we should remove the list
// grab all li elements
var priorElement;
var nextElement;
var children = list.find('li');
var foundIndex;
for (i = 0; i < children.length; i++) {
if (children[i].outerHTML === listElement[0].outerHTML) {
// found it...
foundIndex = i;
if (i > 0) {
priorElement = children[i - 1];
}
if (i + 1 < children.length) {
nextElement = children[i + 1];
}
break;
}
}
//console.log('listElementToSelfTag', list, listElement, selfTag, bDefault, priorElement, nextElement);
// un-list the listElement
var html = '';
if (bDefault) {
html += '<' + defaultWrap + '>' + listElement[0].innerHTML + '' + defaultWrap + '>';
} else {
html += '<' + taBrowserTag(selfTag) + '>';
html += '' + listElement[0].innerHTML + ' ';
html += '' + taBrowserTag(selfTag) + '>';
}
$target = angular.element(html);
//console.log('$target', $target[0]);
if (!priorElement) {
// this is the first the list, so we just remove it...
listElement.remove();
list.after(angular.element(list[0].outerHTML));
list.after($target);
list.remove();
taSelection.setSelectionToElementEnd($target[0]);
return;
} else if (!nextElement) {
// this is the last in the list, so we just remove it..
listElement.remove();
list.after($target);
taSelection.setSelectionToElementEnd($target[0]);
} else {
var p = list.parent();
// okay it was some where in the middle... so we need to break apart the list...
var html1 = '';
var listTag = list[0].nodeName.toLowerCase();
html1 += '<' + listTag + '>';
for (i = 0; i < foundIndex; i++) {
html1 += '' + children[i].innerHTML + ' ';
}
html1 += '' + listTag + '>';
var html2 = '';
html2 += '<' + listTag + '>';
for (i = foundIndex + 1; i < children.length; i++) {
html2 += '' + children[i].innerHTML + ' ';
}
html2 += '' + listTag + '>';
//console.log(html1, $target[0], html2);
list.after(angular.element(html2));
list.after($target);
list.after(angular.element(html1));
list.remove();
//console.log('parent ******XXX*****', p[0]);
taSelection.setSelectionToElementEnd($target[0]);
}
};
var listElementsToSelfTag = function (list, listElements, selfTag, bDefault, defaultWrap) {
var $target, i, j, p;
// grab all li elements
var priorElement;
var afterElement;
//console.log('list:', list, 'listElements:', listElements, 'selfTag:', selfTag, 'bDefault:', bDefault);
var children = list.find('li');
var foundIndexes = [];
for (i = 0; i < children.length; i++) {
for (j = 0; j < listElements.length; j++) {
if (children[i].isEqualNode(listElements[j])) {
// found it...
foundIndexes[j] = i;
}
}
}
if (foundIndexes[0] > 0) {
priorElement = children[foundIndexes[0] - 1];
}
if (foundIndexes[listElements.length - 1] + 1 < children.length) {
afterElement = children[foundIndexes[listElements.length - 1] + 1];
}
//console.log('listElementsToSelfTag', list, listElements, selfTag, bDefault, !priorElement, !afterElement, foundIndexes[listElements.length-1], children.length);
// un-list the listElements
var html = '';
if (bDefault) {
for (j = 0; j < listElements.length; j++) {
html += '<' + defaultWrap + '>' + listElements[j].innerHTML + '' + defaultWrap + '>';
listElements[j].remove();
}
} else {
html += '<' + taBrowserTag(selfTag) + '>';
for (j = 0; j < listElements.length; j++) {
html += listElements[j].outerHTML;
listElements[j].remove();
}
html += '' + taBrowserTag(selfTag) + '>';
}
$target = angular.element(html);
if (!priorElement) {
// this is the first the list, so we just remove it...
list.after(angular.element(list[0].outerHTML));
list.after($target);
list.remove();
taSelection.setSelectionToElementEnd($target[0]);
return;
} else if (!afterElement) {
// this is the last in the list, so we just remove it..
list.after($target);
taSelection.setSelectionToElementEnd($target[0]);
return;
} else {
// okay it was some where in the middle... so we need to break apart the list...
var html1 = '';
var listTag = list[0].nodeName.toLowerCase();
html1 += '<' + listTag + '>';
for (i = 0; i < foundIndexes[0]; i++) {
html1 += '' + children[i].innerHTML + ' ';
}
html1 += '' + listTag + '>';
var html2 = '';
html2 += '<' + listTag + '>';
for (i = foundIndexes[listElements.length - 1] + 1; i < children.length; i++) {
html2 += '' + children[i].innerHTML + ' ';
}
html2 += '' + listTag + '>';
list.after(angular.element(html2));
list.after($target);
list.after(angular.element(html1));
list.remove();
//console.log('parent ******YYY*****', list.parent()[0]);
taSelection.setSelectionToElementEnd($target[0]);
}
};
var selectLi = function (liElement) {
if (/(
)$/i.test(liElement.innerHTML.trim())) taSelection.setSelectionBeforeElement(angular.element(liElement).find("br")[0]);
else taSelection.setSelectionToElementEnd(liElement);
};
var listToList = function (listElement, newListTag) {
var $target = angular.element('<' + newListTag + '>' + listElement[0].innerHTML + '' + newListTag + '>');
listElement.after($target);
listElement.remove();
selectLi($target.find('li')[0]);
};
var childElementsToList = function (elements, listElement, newListTag) {
var html = '';
for (var i = 0; i < elements.length; i++) {
html += '<' + taBrowserTag('li') + '>' + elements[i].innerHTML + '' + taBrowserTag('li') + '>';
}
var $target = angular.element('<' + newListTag + '>' + html + '' + newListTag + '>');
listElement.after($target);
listElement.remove();
selectLi($target.find('li')[0]);
};
var turnBlockIntoBlocks = function (element, options) {
for (var i = 0; i < element.childNodes.length; i++) {
var _n = element.childNodes[i];
/* istanbul ignore next - more complex testing*/
if (_n.tagName && _n.tagName.match(BLOCKELEMENTS)) {
turnBlockIntoBlocks(_n, options);
}
}
/* istanbul ignore next - very rare condition that we do not test*/
if (element.parentNode === null) {
// nothing left to do..
return element;
}
/* istanbul ignore next - not sure have to test this */
if (options === '
') {
return element;
}
else {
var $target = angular.element(options);
$target[0].innerHTML = element.innerHTML;
element.parentNode.insertBefore($target[0], element);
element.parentNode.removeChild(element);
return $target;
}
};
return function (taDefaultWrap, topNode) {
// NOTE: here we are dealing with the html directly from the browser and not the html the user sees.
// IF you want to modify the html the user sees, do it when the user does a switchView
taDefaultWrap = taBrowserTag(taDefaultWrap);
return function (command, showUI, options, defaultTagAttributes) {
var i, $target, html, _nodes, next, optionsTagName, selectedElement, ourSelection;
var defaultWrapper = angular.element('<' + taDefaultWrap + '>');
try {
if (taSelection.getSelection) {
ourSelection = taSelection.getSelection();
}
selectedElement = taSelection.getSelectionElement();
// special checks and fixes when we are selecting the whole container
var __h, _innerNode;
/* istanbul ignore next */
if (selectedElement.tagName !== undefined) {
if (selectedElement.tagName.toLowerCase() === 'div' &&
/taTextElement.+/.test(selectedElement.id) &&
ourSelection && ourSelection.start &&
ourSelection.start.offset === 1 &&
ourSelection.end.offset === 1) {
// opps we are actually selecting the whole container!
//console.log('selecting whole container!');
__h = selectedElement.innerHTML;
if (/
/i.test(__h)) {
// Firefox adds
's and so we remove the
__h = __h.replace(/
/i, ''); // no space-space
}
if (/
/i.test(__h)) {
// Firefox adds
's and so we remove the
__h = __h.replace(/
/i, ''); // no space-space
}
// remove stacked up 's
if (/()+/i.test(__h)) {
__h = __.replace(/()+/i, '');
}
// remove stacked up 's
if (/<\/span>(<\/span>)+/i.test(__h)) {
__h = __.replace(/<\/span>(<\/span>)+/i, '<\/span>');
}
if (/<\/span>/i.test(__h)) {
// if we end up with a here we remove it...
__h = __h.replace(/<\/span>/i, '');
}
//console.log('inner whole container', selectedElement.childNodes);
_innerNode = '' + __h + '';
selectedElement.innerHTML = _innerNode;
taSelection.setSelectionToElementEnd(selectedElement.childNodes[0]);
selectedElement = taSelection.getSelectionElement();
} else if (selectedElement.tagName.toLowerCase() === 'span' &&
ourSelection && ourSelection.start &&
ourSelection.start.offset === 1 &&
ourSelection.end.offset === 1) {
// just a span -- this is a problem...
//console.log('selecting span!');
__h = selectedElement.innerHTML;
if (/
/i.test(__h)) {
// Firefox adds
's and so we remove the
__h = __h.replace(/
/i, ''); // no space-space
}
if (/
/i.test(__h)) {
// Firefox adds
's and so we remove the
__h = __h.replace(/
/i, ''); // no space-space
}
// remove stacked up 's
if (/()+/i.test(__h)) {
__h = __.replace(/()+/i, '');
}
// remove stacked up 's
if (/<\/span>(<\/span>)+/i.test(__h)) {
__h = __.replace(/<\/span>(<\/span>)+/i, '<\/span>');
}
if (/<\/span>/i.test(__h)) {
// if we end up with a here we remove it...
__h = __h.replace(/<\/span>/i, '');
}
//console.log('inner span', selectedElement.childNodes);
// we wrap this in a because otherwise the browser get confused when we attempt to select the whole node
// and the focus is not set correctly no matter what we do
_innerNode = '' + __h + '';
selectedElement.innerHTML = _innerNode;
taSelection.setSelectionToElementEnd(selectedElement.childNodes[0]);
selectedElement = taSelection.getSelectionElement();
//console.log(selectedElement.innerHTML);
} else if (selectedElement.tagName.toLowerCase() === 'p' &&
ourSelection && ourSelection.start &&
ourSelection.start.offset === 1 &&
ourSelection.end.offset === 1) {
//console.log('p special');
// we need to remove the that firefox adds!
__h = selectedElement.innerHTML;
if (/
/i.test(__h)) {
// Firefox adds
's and so we remove the
__h = __h.replace(/
/i, ''); // no space-space
selectedElement.innerHTML = __h;
}
} else if (selectedElement.tagName.toLowerCase() === 'li' &&
ourSelection && ourSelection.start &&
ourSelection.start.offset === ourSelection.end.offset) {
// we need to remove the that firefox adds!
__h = selectedElement.innerHTML;
if (/
/i.test(__h)) {
// Firefox adds
's and so we remove the
__h = __h.replace(/
/i, ''); // nothing
selectedElement.innerHTML = __h;
}
}
}
} catch (e) {
}
//console.log('************** selectedElement:', selectedElement);
/* istanbul ignore if: */
if (!selectedElement) {
return;
}
var $selected = angular.element(selectedElement);
var tagName = (selectedElement && selectedElement.tagName && selectedElement.tagName.toLowerCase()) ||
/* istanbul ignore next: */ "";
if (command.toLowerCase() === 'insertorderedlist' || command.toLowerCase() === 'insertunorderedlist') {
var selfTag = taBrowserTag((command.toLowerCase() === 'insertorderedlist') ? 'ol' : 'ul');
var selectedElements = taSelection.getOnlySelectedElements();
//console.log('PPPPPPPPPPPPP', tagName, selfTag, selectedElements, tagName.match(BLOCKELEMENTS), $selected.hasClass('ta-bind'), $selected.parent()[0].tagName);
if (selectedElements.length > 1 && (tagName === 'ol' || tagName === 'ul' )) {
return listElementsToSelfTag($selected, selectedElements, selfTag, selfTag === tagName, taDefaultWrap);
}
if (tagName === selfTag) {
// if all selected then we should remove the list
// grab all li elements and convert to taDefaultWrap tags
//console.log('tagName===selfTag');
if ($selected[0].childNodes.length !== selectedElements.length && selectedElements.length === 1) {
$selected = angular.element(selectedElements[0]);
return listElementToSelfTag($selected.parent(), $selected, selfTag, true, taDefaultWrap);
} else {
return listToDefault($selected, taDefaultWrap);
}
} else if (tagName === 'li' &&
$selected.parent()[0].tagName.toLowerCase() === selfTag &&
$selected.parent().children().length === 1) {
// catch for the previous statement if only one li exists
return listToDefault($selected.parent(), taDefaultWrap);
} else if (tagName === 'li' &&
$selected.parent()[0].tagName.toLowerCase() !== selfTag &&
$selected.parent().children().length === 1) {
// catch for the previous statement if only one li exists
return listToList($selected.parent(), selfTag);
} else if (tagName.match(BLOCKELEMENTS) && !$selected.hasClass('ta-bind')) {
// if it's one of those block elements we have to change the contents
// if it's a ol/ul we are changing from one to the other
if (selectedElements.length) {
if ($selected[0].childNodes.length !== selectedElements.length && selectedElements.length === 1) {
//console.log('&&&&&&&&&&&&&&& --------- &&&&&&&&&&&&&&&&', selectedElements[0], $selected[0].childNodes);
$selected = angular.element(selectedElements[0]);
return listElementToSelfTag($selected.parent(), $selected, selfTag, selfTag === tagName, taDefaultWrap);
}
}
if (tagName === 'ol' || tagName === 'ul') {
// now if this is a set of selected elements... behave diferently
return listToList($selected, selfTag);
} else {
var childBlockElements = false;
angular.forEach($selected.children(), function (elem) {
if (elem.tagName.match(BLOCKELEMENTS)) {
childBlockElements = true;
}
});
if (childBlockElements) {
return childElementsToList($selected.children(), $selected, selfTag);
} else {
return childElementsToList([angular.element('' + selectedElement.innerHTML + '')[0]], $selected, selfTag);
}
}
} else if (tagName.match(BLOCKELEMENTS)) {
// if we get here then the contents of the ta-bind are selected
_nodes = taSelection.getOnlySelectedElements();
//console.log('_nodes', _nodes, tagName);
if (_nodes.length === 0) {
// here is if there is only text in ta-bind ie test content
$target = angular.element('<' + selfTag + '>' + selectedElement.innerHTML + ' ' + selfTag + '>');
$selected.html('');
$selected.append($target);
} else if (_nodes.length === 1 && (_nodes[0].tagName.toLowerCase() === 'ol' || _nodes[0].tagName.toLowerCase() === 'ul')) {
if (_nodes[0].tagName.toLowerCase() === selfTag) {
// remove
return listToDefault(angular.element(_nodes[0]), taDefaultWrap);
} else {
return listToList(angular.element(_nodes[0]), selfTag);
}
} else {
html = '';
var $nodes = [];
for (i = 0; i < _nodes.length; i++) {
/* istanbul ignore else: catch for real-world can't make it occur in testing */
if (_nodes[i].nodeType !== 3) {
var $n = angular.element(_nodes[i]);
/* istanbul ignore if: browser check only, phantomjs doesn't return children nodes but chrome at least does */
if (_nodes[i].tagName.toLowerCase() === 'li') continue;
else if (_nodes[i].tagName.toLowerCase() === 'ol' || _nodes[i].tagName.toLowerCase() === 'ul') {
html += $n[0].innerHTML; // if it's a list, add all it's children
} else if (_nodes[i].tagName.toLowerCase() === 'span' && (_nodes[i].childNodes[0].tagName.toLowerCase() === 'ol' || _nodes[i].childNodes[0].tagName.toLowerCase() === 'ul')) {
html += $n[0].childNodes[0].innerHTML; // if it's a list, add all it's children
} else {
html += '<' + taBrowserTag('li') + '>' + $n[0].innerHTML + '' + taBrowserTag('li') + '>';
}
$nodes.unshift($n);
}
}
//console.log('$nodes', $nodes);
$target = angular.element('<' + selfTag + '>' + html + '' + selfTag + '>');
$nodes.pop().replaceWith($target);
angular.forEach($nodes, function ($node) {
$node.remove();
});
}
taSelection.setSelectionToElementEnd($target[0]);
return;
}
} else if (command.toLowerCase() === 'formatblock') {
optionsTagName = options.toLowerCase().replace(/[<>]/ig, '');
if (optionsTagName.trim() === 'default') {
optionsTagName = taDefaultWrap;
options = '<' + taDefaultWrap + '>';
}
if (tagName === 'li') {
$target = $selected.parent();
}
else {
$target = $selected;
}
// find the first blockElement
while (!$target[0].tagName || !$target[0].tagName.match(BLOCKELEMENTS) && !$target.parent().attr('contenteditable')) {
$target = $target.parent();
/* istanbul ignore next */
tagName = ($target[0].tagName || '').toLowerCase();
}
if (tagName === optionsTagName) {
// $target is wrap element
_nodes = $target.children();
var hasBlock = false;
for (i = 0; i < _nodes.length; i++) {
hasBlock = hasBlock || _nodes[i].tagName.match(BLOCKELEMENTS);
}
if (hasBlock) {
$target.after(_nodes);
next = $target.next();
$target.remove();
$target = next;
} else {
defaultWrapper.append($target[0].childNodes);
$target.after(defaultWrapper);
$target.remove();
$target = defaultWrapper;
}
} else if ($target.parent()[0].tagName.toLowerCase() === optionsTagName &&
!$target.parent().hasClass('ta-bind')) {
//unwrap logic for parent
var blockElement = $target.parent();
var contents = blockElement.contents();
for (i = 0; i < contents.length; i++) {
/* istanbul ignore next: can't test - some wierd thing with how phantomjs works */
if (blockElement.parent().hasClass('ta-bind') && contents[i].nodeType === 3) {
defaultWrapper = angular.element('<' + taDefaultWrap + '>');
defaultWrapper[0].innerHTML = contents[i].outerHTML;
contents[i] = defaultWrapper[0];
}
blockElement.parent()[0].insertBefore(contents[i], blockElement[0]);
}
blockElement.remove();
} else if (tagName.match(LISTELEMENTS)) {
// wrapping a list element
$target.wrap(options);
} else {
// default wrap behaviour
_nodes = taSelection.getOnlySelectedElements();
if (_nodes.length === 0) {
// no nodes at all....
_nodes = [$target[0]];
}
// find the parent block element if any of the nodes are inline or text
for (i = 0; i < _nodes.length; i++) {
if (_nodes[i].nodeType === 3 || !_nodes[i].tagName.match(BLOCKELEMENTS)) {
while (_nodes[i].nodeType === 3 || !_nodes[i].tagName || !_nodes[i].tagName.match(BLOCKELEMENTS)) {
_nodes[i] = _nodes[i].parentNode;
}
}
}
// remove any duplicates from the array of _nodes!
_nodes = _nodes.filter(function (value, index, self) {
return self.indexOf(value) === index;
});
// remove all whole taTextElement if it is here... unless it is the only element!
if (_nodes.length > 1) {
_nodes = _nodes.filter(function (value, index, self) {
return !(value.nodeName.toLowerCase() === 'div' && /^taTextElement/.test(value.id));
});
}
if (angular.element(_nodes[0]).hasClass('ta-bind')) {
$target = angular.element(options);
$target[0].innerHTML = _nodes[0].innerHTML;
_nodes[0].innerHTML = $target[0].outerHTML;
} else if (optionsTagName === 'blockquote') {
// blockquotes wrap other block elements
html = '';
for (i = 0; i < _nodes.length; i++) {
html += _nodes[i].outerHTML;
}
$target = angular.element(options);
$target[0].innerHTML = html;
_nodes[0].parentNode.insertBefore($target[0], _nodes[0]);
for (i = _nodes.length - 1; i >= 0; i--) {
/* istanbul ignore else: */
if (_nodes[i].parentNode) _nodes[i].parentNode.removeChild(_nodes[i]);
}
} else /* istanbul ignore next: not tested since identical to blockquote */
if (optionsTagName === 'pre' && taSelection.getStateShiftKey()) {
//console.log('shift pre', _nodes);
// pre wrap other block elements
html = '';
for (i = 0; i < _nodes.length; i++) {
html += _nodes[i].outerHTML;
}
$target = angular.element(options);
$target[0].innerHTML = html;
_nodes[0].parentNode.insertBefore($target[0], _nodes[0]);
for (i = _nodes.length - 1; i >= 0; i--) {
/* istanbul ignore else: */
if (_nodes[i].parentNode) _nodes[i].parentNode.removeChild(_nodes[i]);
}
}
else {
//console.log(optionsTagName, _nodes);
// regular block elements replace other block elements
for (i = 0; i < _nodes.length; i++) {
var newBlock = turnBlockIntoBlocks(_nodes[i], options);
if (_nodes[i] === $target[0]) {
$target = angular.element(newBlock);
}
}
}
}
taSelection.setSelectionToElementEnd($target[0]);
// looses focus when we have the whole container selected and no text!
// refocus on the shown display element, this fixes a bug when using firefox
$target[0].focus();
return;
} else if (command.toLowerCase() === 'createlink') {
/* istanbul ignore next: firefox specific fix */
if (tagName === 'a') {
// already a link!!! we are just replacing it...
taSelection.getSelectionElement().href = options;
return;
}
var tagBegin = '',
tagEnd = '',
_selection = taSelection.getSelection();
if (_selection.collapsed) {
//console.log('collapsed');
// insert text at selection, then select then just let normal exec-command run
taSelection.insertHtml(tagBegin + options + tagEnd, topNode);
} else if (rangy.getSelection().getRangeAt(0).canSurroundContents()) {
var node = angular.element(tagBegin + tagEnd)[0];
rangy.getSelection().getRangeAt(0).surroundContents(node);
}
return;
} else if (command.toLowerCase() === 'inserthtml') {
//console.log('inserthtml');
taSelection.insertHtml(options, topNode);
return;
}
try {
$document[0].execCommand(command, showUI, options);
} catch (e) {
}
};
};
}]).service('taSelection', ['$document', 'taDOM', '$log',
/* istanbul ignore next: all browser specifics and PhantomJS dosen't seem to support half of it */
function ($document, taDOM, $log) {
// need to dereference the document else the calls don't work correctly
var _document = $document[0];
var bShiftState;
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;
try {
// catch any errors from rangy and ignore the issue
range = rangy.getSelection().getRangeAt(0);
} catch (e) {
//console.info(e);
return undefined;
}
var container = range.commonAncestorContainer;
var selection = {
start: brException(range.startContainer, range.startOffset),
end: brException(range.endContainer, range.endOffset),
collapsed: range.collapsed
};
// This has problems under Firefox.
// On Firefox with
// Try me !
//
// - line 1
// - line 2
//
// line 3
//
// - line 4
// - line 5
//
// Hello textAngular
// WITH the cursor after the 3 on line 3, it gets the commonAncestorContainer as:
//
// AND Chrome gets the commonAncestorContainer as:
// line 3
//
// Check if the container is a text node and return its parent if so
// unless this is the whole taTextElement. If so we return the textNode
if (container.nodeType === 3) {
if (container.parentNode.nodeName.toLowerCase() === 'div' &&
/^taTextElement/.test(container.parentNode.id)) {
// textNode where the parent is the whole !!!
//console.log('textNode ***************** container:', container);
} else {
container = container.parentNode;
}
}
if (container.nodeName.toLowerCase() === 'div' &&
/^taTextElement/.test(container.id)) {
//console.log('*********taTextElement************');
//console.log('commonAncestorContainer:', container);
selection.start.element = container.childNodes[selection.start.offset];
selection.end.element = container.childNodes[selection.end.offset];
selection.container = container;
} else {
if (container.parentNode === selection.start.element ||
container.parentNode === selection.end.element) {
selection.container = container.parentNode;
} else {
selection.container = container;
}
}
//console.log('***selection container:', selection.container.nodeName, selection.start.offset, selection.container);
return selection;
},
// if we use the LEFT_ARROW and we are at the special place we move the cursor over by one...
// Chrome and Firefox behave differently so so fix this for Firefox here. No adjustment needed for Chrome.
updateLeftArrowKey: function (element) {
var range = rangy.getSelection().getRangeAt(0);
if (range && range.collapsed) {
var _nodes = api.getFlattenedDom(range);
if (!_nodes.findIndex) return;
var _node = range.startContainer;
var indexStartContainer = _nodes.findIndex(function (element, index) {
if (element.node === _node) return true;
var _indexp = element.parents.indexOf(_node);
return (_indexp !== -1);
});
var m;
var nextNodeToRight;
//console.log('indexStartContainer', indexStartContainer, _nodes.length, 'startContainer:', _node, _node === _nodes[indexStartContainer].node);
_nodes.forEach(function (n, i) {
//console.log(i, n.node);
n.parents.forEach(function (nn, j) {
//console.log(i, j, nn);
});
});
if (indexStartContainer + 1 < _nodes.length) {
// we need the node just after this startContainer
// so we can check and see it this is a special place
nextNodeToRight = _nodes[indexStartContainer + 1].node;
//console.log(nextNodeToRight, range.startContainer);
}
//console.log('updateLeftArrowKey', range.startOffset, range.startContainer.textContent);
// this first section handles the case for Chrome browser
// if the first character of the nextNode is a \ufeff we know that we are just before the special span...
// and so we most left by one character
if (nextNodeToRight && nextNodeToRight.textContent) {
m = /^\ufeff([^\ufeff]*)$/.exec(nextNodeToRight.textContent);
if (m) {
// we are before the special node with begins with a \ufeff character
//console.log('LEFT ...found it...', 'startOffset:', range.startOffset, m[0].length, m[1].length);
// no need to change anything in this case
return;
}
}
var nextNodeToLeft;
if (indexStartContainer > 0) {
// we need the node just after this startContainer
// so we can check and see it this is a special place
nextNodeToLeft = _nodes[indexStartContainer - 1].node;
//console.log(nextNodeToLeft, nextNodeToLeft);
}
if (range.startOffset === 0 && nextNodeToLeft) {
//console.log(nextNodeToLeft, range.startOffset, nextNodeToLeft.textContent);
m = /^\ufeff([^\ufeff]*)$/.exec(nextNodeToLeft.textContent);
if (m) {
//console.log('LEFT &&&&&&&&&&&&&&&&&&&...found it...&&&&&&&&&&&', nextNodeToLeft, m[0].length, m[1].length);
// move over to the left my one -- Firefox triggers this case
api.setSelectionToElementEnd(nextNodeToLeft);
return;
}
}
}
return;
},
// if we use the RIGHT_ARROW and we are at the special place we move the cursor over by one...
updateRightArrowKey: function (element) {
// we do not need to make any adjustments here, so we ignore all this code
if (false) {
var range = rangy.getSelection().getRangeAt(0);
if (range && range.collapsed) {
var _nodes = api.getFlattenedDom(range);
if (!_nodes.findIndex) return;
var _node = range.startContainer;
var indexStartContainer = _nodes.findIndex(function (element, index) {
if (element.node === _node) return true;
var _indexp = element.parents.indexOf(_node);
return (_indexp !== -1);
});
var _sel;
var i;
var m;
// if the last character is a \ufeff we know that we are just before the special span...
// and so we most right by one character
var indexFound = _nodes.findIndex(function (n, index) {
if (n.textContent) {
var m = /^\ufeff([^\ufeff]*)$/.exec(n.textContent);
if (m) {
return true;
} else {
return false;
}
} else {
return false;
}
});
if (indexFound === -1) {
return;
}
//console.log(indexFound, range.startContainer, range.startOffset);
_node = _nodes[indexStartContainer];
//console.log('indexStartContainer', indexStartContainer);
if (_node && _node.textContent) {
m = /^\ufeff([^\ufeff]*)$/.exec(_node.textContent);
if (m && range.startOffset - 1 === m[1].length) {
//console.log('RIGHT found it...&&&&&&&&&&&', range.startOffset);
// no need to make any adjustment
return;
}
}
//console.log(range.startOffset);
if (_nodes && range.startOffset === 0) {
indexStartContainer = _nodes.indexOf(range.startContainer);
if (indexStartContainer !== -1 && indexStartContainer > 0) {
_node = _nodes[indexStartContainer - 1];
if (_node.textContent) {
m = /\ufeff([^\ufeff]*)$/.exec(_node.textContent);
if (m && true || range.startOffset === m[1].length + 1) {
//console.log('RIGHT &&&&&&&&&&&&&&&&&&&...found it...&&&&&&&&&&&', range.startOffset, m[1].length);
// no need to make any adjustment
return;
}
}
}
}
}
}
},
getFlattenedDom: function (range) {
var parent = range.commonAncestorContainer.parentNode;
if (!parent) {
return range.commonAncestorContainer.childNodes;
}
var nodes = Array.prototype.slice.call(parent.childNodes); // converts NodeList to Array
var indexStartContainer = nodes.indexOf(range.startContainer);
// make sure that we have a big enough set of nodes
if (indexStartContainer + 1 < nodes.length && indexStartContainer > 0) {
// we are good
// we can go down one node or up one node
} else {
if (parent.parentNode) {
parent = parent.parentNode;
}
}
// now walk the parent
nodes = [];
function addNodes(_set) {
if (_set.node.childNodes.length) {
var childNodes = Array.prototype.slice.call(_set.node.childNodes); // converts NodeList to Array
childNodes.forEach(function (n) {
var _t = _set.parents.slice();
if (_t.slice(-1)[0] !== _set.node) {
_t.push(_set.node);
}
addNodes({parents: _t, node: n});
});
} else {
nodes.push({parents: _set.parents, node: _set.node});
}
}
addNodes({parents: [parent], node: parent});
return nodes;
},
getOnlySelectedElements: function () {
var range = rangy.getSelection().getRangeAt(0);
var container = range.commonAncestorContainer;
// Node.TEXT_NODE === 3
// Node.ELEMENT_NODE === 1
// Node.COMMENT_NODE === 8
// Check if the container is a text node and return its parent if so
container = container.nodeType === 3 ? container.parentNode : container;
// get the nodes in the range that are ELEMENT_NODE and are children of the container
// in this range...
return range.getNodes([1], function (node) {
return node.parentNode === container;
});
},
// this includes the container element if all children are selected
getAllSelectedElements: function () {
var range = rangy.getSelection().getRangeAt(0);
var container = range.commonAncestorContainer;
// Node.TEXT_NODE === 3
// Node.ELEMENT_NODE === 1
// Node.COMMENT_NODE === 8
// Check if the container is a text node and return its parent if so
container = container.nodeType === 3 ? container.parentNode : container;
// get the nodes in the range that are ELEMENT_NODE and are children of the container
// in this range...
var selectedNodes = range.getNodes([1], function (node) {
return node.parentNode === container;
});
var innerHtml = container.innerHTML;
// remove the junk that rangy has put down
innerHtml = innerHtml.replace(/]+>\ufeff?<\/span>/ig, '');
//console.log(innerHtml);
//console.log(range.toHtml());
//console.log(innerHtml === range.toHtml());
if (innerHtml === range.toHtml() &&
// not the whole taTextElement
(!(container.nodeName.toLowerCase() === 'div' && /^taTextElement/.test(container.id)))
) {
var arr = [];
for (var i = selectedNodes.length; i--; arr.unshift(selectedNodes[i])) ;
selectedNodes = arr;
selectedNodes.push(container);
//$log.debug(selectedNodes);
}
return selectedNodes;
},
// Some basic selection functions
getSelectionElement: function () {
var s = api.getSelection();
if (s) {
return api.getSelection().container;
} else {
return undefined;
}
},
setSelection: function (elStart, elEnd, start, end) {
var range = rangy.createRange();
range.setStart(elStart, start);
range.setEnd(elEnd, end);
rangy.getSelection().setSingleRange(range);
},
setSelectionBeforeElement: function (el) {
var range = rangy.createRange();
range.selectNode(el);
range.collapse(true);
rangy.getSelection().setSingleRange(range);
},
setSelectionAfterElement: function (el) {
var range = rangy.createRange();
range.selectNode(el);
range.collapse(false);
rangy.getSelection().setSingleRange(range);
},
setSelectionToElementStart: function (el) {
var range = rangy.createRange();
range.selectNodeContents(el);
range.collapse(true);
rangy.getSelection().setSingleRange(range);
},
setSelectionToElementEnd: function (el) {
var range = rangy.createRange();
range.selectNodeContents(el);
range.collapse(false);
if (el.childNodes && el.childNodes[el.childNodes.length - 1] && el.childNodes[el.childNodes.length - 1].nodeName === 'br') {
range.startOffset = range.endOffset = range.startOffset - 1;
}
rangy.getSelection().setSingleRange(range);
},
setStateShiftKey: function (bS) {
bShiftState = bS;
},
getStateShiftKey: function () {
return bShiftState;
},
// 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, 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.
// If there are no block elements, or there is a mixture we need to create textNodes for the non wrapped text (we don't want them spans messing up the picture).
nodes = [];
for (_childI = 0; _childI < children.length; _childI++) {
var _cnode = children[_childI];
if (_cnode.nodeName.toLowerCase() === 'p' &&
_cnode.innerHTML.trim() === '') { // empty p element
continue;
}
/****************
* allow any text to be inserted...
if(( _cnode.nodeType === 3 &&
_cnode.nodeValue === '\ufeff'[0] &&
_cnode.nodeValue.trim() === '') // empty no-space space element
) {
// no change to isInline
nodes.push(_cnode);
continue;
}
if(_cnode.nodeType === 3 &&
_cnode.nodeValue.trim() === '') { // empty text node
continue;
}
*****************/
isInline = isInline && !BLOCKELEMENTS.test(_cnode.nodeName);
nodes.push(_cnode);
}
for (var _n = 0; _n < nodes.length; _n++) {
lastNode = frag.appendChild(nodes[_n]);
}
if (!isInline &&
range.collapsed &&
/^(|
)$/i.test(range.startContainer.innerHTML)) {
range.selectNode(range.startContainer);
}
} else {
isInline = true;
// paste text of some sort
lastNode = frag = _document.createTextNode(html);
}
// Other Edge case - selected data spans multiple blocks.
if (isInline) {
range.deleteContents();
} else { // not inline insert
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);
}
} else {
// 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
//console.log('setStartAfter', parent);
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];
}
}
}
} else {
range.deleteContents();
}
}
range.insertNode(frag);
if (lastNode) {
api.setSelectionToElementEnd(lastNode);
}
}
/* NOT FUNCTIONAL YET
// under Firefox, we may have a selection that needs to be normalized
isSelectionContainerWhole_taTextElement: function (){
var range = rangy.getSelection().getRangeAt(0);
var container = range.commonAncestorContainer;
if (container.nodeName.toLowerCase() === 'div' &&
/^taTextElement/.test(container.id)) {
// container is the whole taTextElement
return true;
}
return false;
},
setNormalizedSelection: function (){
var range = rangy.getSelection().getRangeAt(0);
var container = range.commonAncestorContainer;
console.log(range);
console.log(container.childNodes);
if (range.collapsed) {
// we know what to do...
console.log(container.childNodes[range.startOffset]);
api.setSelectionToElementStart(container.childNodes[range.startOffset]);
}
},
*/
};
return api;
}]).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;
});
© 2015 - 2025 Weber Informatics LLC | Privacy Policy