All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 + '');
                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 + '';
            } else {
                html += '<' + taBrowserTag(selfTag) + '>';
                html += '
  • ' + listElement[0].innerHTML + '
  • '; html += ''; } $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 += ''; var html2 = ''; html2 += '<' + listTag + '>'; for (i = foundIndex + 1; i < children.length; i++) { html2 += '
  • ' + children[i].innerHTML + '
  • '; } html2 += ''; //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 + ''; listElements[j].remove(); } } else { html += '<' + taBrowserTag(selfTag) + '>'; for (j = 0; j < listElements.length; j++) { html += listElements[j].outerHTML; listElements[j].remove(); } html += ''; } $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 += ''; var html2 = ''; html2 += '<' + listTag + '>'; for (i = foundIndexes[listElements.length - 1] + 1; i < children.length; i++) { html2 += '
  • ' + children[i].innerHTML + '
  • '; } html2 += ''; 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 + ''); 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 + ''; } var $target = angular.element('<' + newListTag + '>' + html + ''); 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 + '
  • '); $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 + ''; } $nodes.unshift($n); } } //console.log('$nodes', $nodes); $target = angular.element('<' + selfTag + '>' + html + ''); $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