diff --git a/src/textAngular.js b/src/textAngular.js index 1abd2c0a..ae874d13 100644 --- a/src/textAngular.js +++ b/src/textAngular.js @@ -821,7 +821,7 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a listElement.remove(); selectLi($target.find('li')[0]); }; - return function(taDefaultWrap){ + return function(taDefaultWrap, topNode){ taDefaultWrap = taBrowserTag(taDefaultWrap); return function(command, showUI, options){ var i, $target, html, _nodes, next, optionsTagName, selectedElement; @@ -988,11 +988,11 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a var _selection = taSelection.getSelection(); if(_selection.collapsed){ // insert text at selection, then select then just let normal exec-command run - taSelection.insertHtml('' + options + ''); + taSelection.insertHtml('' + options + '', topNode); return; } }else if(command.toLowerCase() === 'inserthtml'){ - taSelection.insertHtml(options); + taSelection.insertHtml(options, topNode); return; } } @@ -1020,14 +1020,14 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a // 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 '


', ie is '

' as for once IE gets it correct... - var _defaultVal, _defaultTest, _trimTest; + var _defaultVal, _defaultTest; + var _trimTest = /^<[^>]+>(\s| )*<\/[^>]+>$/ig; // set the default to be a paragraph value if(attrs.taDefaultWrap === undefined) attrs.taDefaultWrap = 'p'; /* istanbul ignore next: ie specific test */ if(attrs.taDefaultWrap === ''){ _defaultVal = ''; _defaultTest = (ie === undefined)? '

' : (ie >= 11)? '


' : (ie <= 8)? '

 

' : '

 

'; - _trimTest = (ie === undefined)? /^
(\s| )*<\/div>$/ig : /^

(\s| )*<\/p>$/ig; }else{ _defaultVal = (ie === undefined || ie >= 11)? '<' + attrs.taDefaultWrap + '>
' : @@ -1039,9 +1039,15 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a (ie <= 8)? '<' + attrs.taDefaultWrap.toUpperCase() + '> ' : '<' + attrs.taDefaultWrap + '> '; - _trimTest = new RegExp('^<' + attrs.taDefaultWrap + '>(\\s| )*<\\/' + attrs.taDefaultWrap + '>$', 'ig'); } + var _blankTest = function(val){ + val = val.trim(); + if(val.length === 0 || val === _defaultTest || _trimTest.test(val)) return true; + if(/>\s*(([^\s<]+)\s*)+]+\s*)+$/.test(val)) return false;// this regex tests if there is some content that isn't white space between tags, or there is just some text passed in + return true; + }; + element.addClass('ta-bind'); var _undoKeyupTimeout; @@ -1118,7 +1124,7 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a var _setViewValue = function(val, triggerUndo){ if(typeof triggerUndo === "undefined" || triggerUndo === null) triggerUndo = true && _isContentEditable; // if not contentEditable then the native undo/redo is fine if(!val) val = _compileHtml(); - if(val === _defaultTest || _trimTest.test(val)){ + if(_blankTest(val)){ // this avoids us from tripping the ng-pristine flag if we click in and out with out typing if(ngModel.$viewValue !== '') ngModel.$setViewValue(''); if(triggerUndo && ngModel.$undoManager.current() !== '') ngModel.$undoManager.push(''); @@ -1235,7 +1241,7 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a } text = taSanitize(text, '', _disableSanitizer); - taSelection.insertHtml(text); + taSelection.insertHtml(text, element[0]); $timeout(function(){ ngModel.$setViewValue(_compileHtml()); }, 0); @@ -1379,7 +1385,7 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a // trigger the validation calls var _validity = function(value){ - if(attrs.required) ngModel.$setValidity('required', !(!value || value.trim() === _defaultTest || value.trim().match(_trimTest) || value.trim() === '')); + if(attrs.required) ngModel.$setValidity('required', !_blankTest(value)); return value; }; // parsers trigger from the above keyup function or any other time that the viewValue is updated and parses it for storage in the ngModel @@ -1387,6 +1393,14 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a ngModel.$parsers.push(_validity); // because textAngular is bi-directional (which is awesome) we need to also sanitize values going in from the server ngModel.$formatters.push(_sanitize); + ngModel.$formatters.push(function(value){ + if(_blankTest(value)) return value; + var domTest = angular.element("

" + value + "
"); + if(domTest.children().length === 0){ + value = "<" + attrs.taDefaultWrap + ">" + value + ""; + } + return value; + }); ngModel.$formatters.push(_validity); ngModel.$formatters.push(function(value){ return ngModel.$undoManager.push(value || ''); @@ -2235,7 +2249,8 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a rangy.getSelection().setSingleRange(range); }, // from http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div - insertHtml: function(html){ + // topNode is the contenteditable normally, all manipulation MUST be inside this. + insertHtml: function(html, topNode){ var parent, secondParent, _childI, nodes, startIndex, startNodes, endNodes, i, lastNode; var element = angular.element("
" + html + "
"); var range = rangy.getSelection().getRangeAt(0); @@ -2266,7 +2281,7 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a if(isInline){ range.deleteContents(); }else{ // not inline insert - if(range.collapsed){ + if(range.collapsed && range.startContainer !== topNode && range.startContainer.parentNode !== topNode){ // split element into 2 and insert block element in middle if(range.startContainer.nodeType === 3){ // if text node parent = range.startContainer.parentNode; diff --git a/test/taBind/taBind.spec.js b/test/taBind/taBind.spec.js index 9070a3b0..0d7288d9 100644 --- a/test/taBind/taBind.spec.js +++ b/test/taBind/taBind.spec.js @@ -49,6 +49,11 @@ describe('taBind', function () { $rootScope.$digest(); expect(element.html()).toBe('
Test 2 Content
'); }); + it('should wrap content from model change', function () { + $rootScope.html = 'Test 2 Content'; + $rootScope.$digest(); + expect(element.html()).toBe('

Test 2 Content

'); + }); it('should prevent links default event', function () { $rootScope.html = '
Test 2 Content
'; diff --git a/test/taBind/taBind.validation.spec.js b/test/taBind/taBind.validation.spec.js index 8af15bfe..0d6ef124 100644 --- a/test/taBind/taBind.validation.spec.js +++ b/test/taBind/taBind.validation.spec.js @@ -151,6 +151,22 @@ describe('taBind.validation', function () { }); }); + describe('should handle blank test', function () { + beforeEach(function(){ + $rootScope.html = '

'; + $rootScope.$digest(); + }); + it('ng-required', function(){ + expect($rootScope.form.test.$error.required).toBe(true); + }); + it('valid', function(){ + expect($rootScope.form.$valid).toBe(false); + }); + it('field valid', function(){ + expect($rootScope.form.test.$valid).toBe(false); + }); + }); + describe('should change on input update', function () { beforeEach(inject(function(textAngularManager){ element.html('
Test Change Content
'); diff --git a/test/textAngular.spec.js b/test/textAngular.spec.js index 8b7203bf..f3a19a8c 100644 --- a/test/textAngular.spec.js +++ b/test/textAngular.spec.js @@ -762,14 +762,14 @@ describe('textAngular', function(){ $rootScope.html = undefined; var element = $compile('Test Contents')($rootScope); $rootScope.$digest(); - expect(textAngularManager.retrieveEditor('test').scope.displayElements.text.html()).toBe('Test Contents'); + expect(textAngularManager.retrieveEditor('test').scope.displayElements.text.html()).toBe('

Test Contents

'); })); it('should handle initial null to originalContents', inject(function ($compile, $rootScope, textAngularManager) { $rootScope.html = null; var element = $compile('Test Contents')($rootScope); $rootScope.$digest(); - expect(textAngularManager.retrieveEditor('test').scope.displayElements.text.html()).toBe('Test Contents'); + expect(textAngularManager.retrieveEditor('test').scope.displayElements.text.html()).toBe('

Test Contents

'); })); describe('should reset', function(){