Skip to content

Commit

Permalink
fix(textAngular): corrected undefined event #796
Browse files Browse the repository at this point in the history
  • Loading branch information
JoelParke committed Jul 15, 2015
1 parent 4b2bb15 commit 6186ed5
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 251 deletions.
125 changes: 69 additions & 56 deletions dist/textAngular.js
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
var _firstMatch = /(^[^<]|>)[^<]/i.exec(_blankVal);
var _firstTagIndex;
if(!_firstMatch){
// find the end of the first tag removing all the
// find the end of the first tag removing all the
// Don't do a global replace as that would be waaayy too long, just replace the first 4 occurences should be enough
_blankVal = _blankVal.toString().replace(/="[^"]*"/i, '').replace(/="[^"]*"/i, '').replace(/="[^"]*"/i, '').replace(/="[^"]*"/i, '');
_firstTagIndex = _blankVal.indexOf('>');
Expand Down Expand Up @@ -1019,11 +1019,11 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
.directive('taBind', [
'taSanitize', '$timeout', '$window', '$document', 'taFixChrome', 'taBrowserTag',
'taSelection', 'taSelectableElements', 'taApplyCustomRenderers', 'taOptions',
'_taBlankTest', '$parse', 'taDOM',
'_taBlankTest', '$parse', 'taDOM', 'textAngularManager',
function(
taSanitize, $timeout, $window, $document, taFixChrome, taBrowserTag,
taSelection, taSelectableElements, taApplyCustomRenderers, taOptions,
_taBlankTest, $parse, taDOM){
_taBlankTest, $parse, taDOM, textAngularManager){
// Uses for this are textarea or input with ng-model and ta-bind='text'
// OR any non-form element with contenteditable="contenteditable" ta-bind="html|text" ng-model
return {
Expand All @@ -1043,7 +1043,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
var BLOCKED_KEYS = /^(9|19|20|27|33|34|35|36|37|38|39|40|45|112|113|114|115|116|117|118|119|120|121|122|123|144|145)$/i;
var UNDO_TRIGGER_KEYS = /^(8|13|32|46|59|61|107|109|186|187|188|189|190|191|192|219|220|221|222)$/i; // spaces, enter, delete, backspace, all punctuation
var _pasteHandler;

// defaults to the paragraph element, but we need the line-break or it doesn't allow you to type into the empty element
// non IE is '<p><br/></p>', ie is '<p></p>' as for once IE gets it correct...
var _defaultVal, _defaultTest;
Expand All @@ -1065,12 +1065,12 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
'<' + attrs.taDefaultWrap.toUpperCase() + '>&nbsp;</' + attrs.taDefaultWrap.toUpperCase() + '>' :
'<' + attrs.taDefaultWrap + '>&nbsp;</' + attrs.taDefaultWrap + '>';
}

/* istanbul ignore else */
if(!ngModelOptions.$options) ngModelOptions.$options = {}; // ng-model-options support

var _blankTest = _taBlankTest(_defaultTest);

var _ensureContentWrapped = function(value){
if(_blankTest(value)) return value;
var domTest = angular.element("<div>" + value + "</div>");
Expand Down Expand Up @@ -1102,13 +1102,13 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
}
return value;
};

if(attrs.taPaste) _pasteHandler = $parse(attrs.taPaste);

element.addClass('ta-bind');

var _undoKeyupTimeout;

scope['$undoManager' + (attrs.id || '')] = ngModel.$undoManager = {
_stack: [],
_index: 0,
Expand Down Expand Up @@ -1142,7 +1142,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
return this._stack[this._index];
}
};

var _redoUndoTimeout;
var _undo = scope['$undoTaBind' + (attrs.id || '')] = function(){
/* istanbul ignore else: can't really test it due to all changes being ignored as well in readonly */
Expand All @@ -1159,7 +1159,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
}
}
};

var _redo = scope['$redoTaBind' + (attrs.id || '')] = function(){
/* istanbul ignore else: can't really test it due to all changes being ignored as well in readonly */
if(!_isReadonly && _isContentEditable){
Expand All @@ -1176,14 +1176,14 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
}
}
};

// in here we are undoing the converts used elsewhere to prevent the < > and & being displayed when they shouldn't in the code.
var _compileHtml = function(){
if(_isContentEditable) return element[0].innerHTML;
if(_isInputFriendly) return element.val();
throw ('textAngular Error: attempting to update non-editable taBind');
};

var _setViewValue = function(_val, triggerUndo, skipRender){
_skipRender = skipRender || false;
if(typeof triggerUndo === "undefined" || triggerUndo === null) triggerUndo = true && _isContentEditable; // if not contentEditable then the native undo/redo is fine
Expand All @@ -1201,18 +1201,18 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
}
ngModel.$render();
};

//used for updating when inserting wrapped elements
scope['updateTaBind' + (attrs.id || '')] = function(){
if(!_isReadonly) _setViewValue(undefined, undefined, true);
};

// catch DOM XSS via taSanitize
// Sanitizing both ways is identical
var _sanitize = function(unsafe){
return (ngModel.$oldViewValue = taSanitize(taFixChrome(unsafe), ngModel.$oldViewValue, _disableSanitizer));
};

// trigger the validation calls
if(element.attr('required')) ngModel.$validators.required = function(modelValue, viewValue) {
return !_blankTest(modelValue || viewValue);
Expand All @@ -1226,7 +1226,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
ngModel.$formatters.unshift(function(value){
return ngModel.$undoManager.push(value || '');
});

//this code is used to update the models when data is entered/deleted
if(_isInputFriendly){
scope.events = {};
Expand All @@ -1235,7 +1235,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
element.on('change blur', scope.events.change = scope.events.blur = function(){
if(!_isReadonly) ngModel.$setViewValue(_compileHtml());
});

element.on('keydown', scope.events.keydown = function(event, eventData){
/* istanbul ignore else: this is for catching the jqLite testing*/
if(eventData) angular.extend(event, eventData);
Expand All @@ -1245,36 +1245,36 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
// get caret position/selection
var start = this.selectionStart;
var end = this.selectionEnd;

var value = element.val();
if(event.shiftKey){
// find \t
var _linebreak = value.lastIndexOf('\n', start), _tab = value.lastIndexOf('\t', start);
if(_tab !== -1 && _tab >= _linebreak){
// set textarea value to: text before caret + tab + text after caret
element.val(value.substring(0, _tab) + value.substring(_tab + 1));

// put caret at right position again (add one for the tab)
this.selectionStart = this.selectionEnd = start - 1;
}
}else{
// set textarea value to: text before caret + tab + text after caret
element.val(value.substring(0, start) + "\t" + value.substring(end));

// put caret at right position again (add one for the tab)
this.selectionStart = this.selectionEnd = start + 1;
}
// prevent the focus lose
event.preventDefault();
}
});

var _repeat = function(string, n){
var result = '';
for(var _n = 0; _n < n; _n++) result += string;
return result;
};

var recursiveListFormat = function(listNode, tablevel){
var _html = '', _children = listNode.childNodes;
tablevel++;
Expand Down Expand Up @@ -1304,7 +1304,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
else htmlValue += '' + _children[i].outerHTML;
}
}

return htmlValue;
});
}else{
Expand Down Expand Up @@ -1346,7 +1346,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
if(!dom[0].childNodes[i] || dom[0].childNodes[i].nodeName === "#text" || dom[0].childNodes[i].tagName.toLowerCase() !== "p") continue;
var el = angular.element(dom[0].childNodes[i]);
var _listMatch = (el.attr('class') || '').match(/MsoList(Bullet|Number|Paragraph)(CxSp(First|Middle|Last)|)/i);

if(_listMatch){
if(el[0].childNodes.length < 2 || el[0].childNodes[1].childNodes.length < 1){
continue;
Expand All @@ -1356,9 +1356,9 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
var indent = parseFloat((_indentMatch)?_indentMatch[1]:0);
var _levelMatch = (el.attr('style') || '').match(/mso-list:l([0-9]+) level([0-9]+) lfo[0-9+]($|;)/i);
// prefers the mso-list syntax

if(_levelMatch && _levelMatch[2]) indent = parseInt(_levelMatch[2]);

if ((_levelMatch && (!_list.lastLevelMatch || _levelMatch[1] !== _list.lastLevelMatch[1])) || !_listMatch[3] || _listMatch[3].toLowerCase() === "first" || (_list.lastIndent.peek() === null) || (_list.isUl !== isUl && _list.lastIndent.peek() === indent)) {
_resetList(isUl);
targetDom.append(_list.element);
Expand All @@ -1383,7 +1383,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
targetDom.append(_list.element);
}
}

_list.lastLevelMatch = _levelMatch;
if(indent !== _list.lastIndent.peek()) _list.lastIndent.push(indent);
_list.lastLi = angular.element("<li>");
Expand All @@ -1400,7 +1400,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
for(var _n = node[0].childNodes.length - 1; _n >= 0; _n--) node.after(node[0].childNodes[_n]);
node.remove();
};

angular.forEach(targetDom.find('span'), function(node){
node.removeAttribute('lang');
if(node.attributes.length <= 0) _unwrapElement(node);
Expand Down Expand Up @@ -1432,16 +1432,16 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
// Webkit on Apple tags
text = text.replace(/<br class="Apple-interchange-newline"[^>]*?>/ig, '').replace(/<span class="Apple-converted-space">( |&nbsp;)<\/span>/ig, '&nbsp;');
}

if (/<li(\s.*)?>/i.test(text) && /(<ul(\s.*)?>|<ol(\s.*)?>).*<li(\s.*)?>/i.test(text) === false) {
// insert missing parent of li element
text = text.replace(/<li(\s.*)?>.*<\/li(\s.*)?>/i, '<ul>$&</ul>');
}

if(_pasteHandler) text = _pasteHandler(scope, {$html: text}) || text;

text = taSanitize(text, '', _disableSanitizer);

taSelection.insertHtml(text, element[0]);
$timeout(function(){
ngModel.$setViewValue(_compileHtml());
Expand All @@ -1453,7 +1453,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
element.removeClass('processing-paste');
}
};

element.on('paste', scope.events.paste = function(e, eventData){
/* istanbul ignore else: this is for catching the jqLite testing*/
if(eventData) angular.extend(e, eventData);
Expand All @@ -1462,7 +1462,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
e.preventDefault();
return false;
}

// Code adapted from http://stackoverflow.com/questions/2176861/javascript-get-clipboard-data-on-paste-event-cross-browser/6804718#6804718
_processingPaste = true;
element.addClass('processing-paste');
Expand All @@ -1479,7 +1479,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
} else if (/text\/plain/i.test(_types)) {
pastedContent = clipboardData.getData('text/plain');
}

processpaste(pastedContent);
e.stopPropagation();
e.preventDefault();
Expand All @@ -1505,10 +1505,20 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
}, 0);
else e.preventDefault();
});

element.on('keydown', scope.events.keydown = function(event, eventData){
/* istanbul ignore else: this is for catching the jqLite testing*/
if(eventData) angular.extend(event, eventData);
// keyCode 9 is the TAB key
/* istanbul ignore next: not sure how to test this */
if (event.ctrlKey===false && event.metaKey===false && event.keyCode===9) {
event.preventDefault();
event.specialKey = 'TabKey';
if (event.shiftKey) {
event.specialKey = 'ShiftTabKey';
}
textAngularManager.sendKeyCommand(scope, event);
}
/* istanbul ignore else: readonly check */
if(!_isReadonly){
if(!event.altKey && (event.metaKey || event.ctrlKey)){
Expand Down Expand Up @@ -1566,7 +1576,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
while(!selection.tagName.match(VALIDELEMENTS) && selection !== element[0]){
selection = selection.parentNode;
}

if(selection.tagName.toLowerCase() !== attrs.taDefaultWrap && selection.tagName.toLowerCase() !== 'li' && (selection.innerHTML.trim() === '' || selection.innerHTML.trim() === '<br>')){
var _new = angular.element(_defaultVal);
angular.element(selection).replaceWith(_new);
Expand Down Expand Up @@ -1622,12 +1632,12 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
element.removeClass('placeholder-text');
_reApplyOnSelectorHandlers();
});

element.on('mouseup', scope.events.mouseup = function(){
var _selection = taSelection.getSelection();
if(_selection.start.element === element[0] && element.children().length) taSelection.setSelectionToElementStart(element.children()[0]);
});

// prevent propagation on mousedown in editor, see #206
element.on('mousedown', scope.events.mousedown = function(event, eventData){
/* istanbul ignore else: this is for catching the jqLite testing*/
Expand Down Expand Up @@ -1670,7 +1680,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
.on('click', selectorClickHandler);
});
};

var _setInnerHTML = function(newval){
element[0].innerHTML = newval;
};
Expand All @@ -1691,7 +1701,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
element.removeClass('placeholder-text');
if(_renderTimeout) $timeout.cancel(_renderTimeout);
_renderTimeout = $timeout(function(){
/* istanbul ignore if: Can't be bothered testing this... */
/* istanbul ignore if: Can't be bothered testing this... */
if(!_focussed){
element[0].focus();
taSelection.setSelectionToElementEnd(element.children()[element.children().length - 1]);
Expand Down Expand Up @@ -1737,7 +1747,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
}
_renderInProgress = _skipRender = false;
};

if(attrs.taReadonly){
//set initial value
_isReadonly = scope.$eval(attrs.taReadonly);
Expand Down Expand Up @@ -2406,17 +2416,6 @@ textAngular.directive("textAngular", [
};
// start updating on keydown
_keydown = function(){
// keyCode 9 is the TAB key
/* istanbul ignore next: not sure how to test this */
if (event.ctrlKey===false && event.metaKey===false && event.keyCode===9) {
event.preventDefault();
var extraEventData = { specialKey: 'TabKey' };
if (event.shiftKey) {
extraEventData.specialKey = 'ShiftTabKey';
}
// since nether tab nor shift-tab generate a keypress event, we call directly
_keypress(event, extraEventData);
}
/* istanbul ignore next: ie catch */
if(!scope.focussed){
scope._bUpdateSelectedStyles = false;
Expand Down Expand Up @@ -2728,6 +2727,20 @@ textAngular.service('textAngularManager', ['taToolExecuteAction', 'taTools', 'ta
/* istanbul ignore else: phase catch */
if(!editors[name].scope.$$phase) editors[name].scope.$digest();
}else throw('textAngular Error: No Editor with name "' + name + '" exists');
},
// this is used by taBind to send a key command in response to a special key event
sendKeyCommand: function(scope, event){
angular.forEach(editors, function(_editor){
/* istanbul ignore else: if nothing to do, do nothing */
if (_editor.editorFunctions.sendKeyCommand(event)){
/* istanbul ignore else: don't run if already running */
if(!scope._bUpdateSelectedStyles){
scope.updateSelectedStyles();
}
event.preventDefault();
return false;
}
});
}
};
}]);
Expand Down
4 changes: 2 additions & 2 deletions dist/textAngular.min.js

Large diffs are not rendered by default.

Loading

0 comments on commit 6186ed5

Please sign in to comment.