From 373a90df887916b59d9f495876e1162f07d2afad Mon Sep 17 00:00:00 2001 From: Simeon Date: Thu, 5 Dec 2013 15:27:05 +1300 Subject: [PATCH] Fixes for: #38, #29, #35, #30, #19 Updates the fix introduced in #5 to be more specific and less of a catch all. Changed version to v1.1.2 --- .bower.json | 2 +- README.md | 2 +- changelog.md | 7 +++ demo/index.html | 4 ++ demo/js/index.js | 3 +- textAngular.js | 114 ++++++++++++++++++++++----------------------- textAngular.min.js | 2 +- 7 files changed, 72 insertions(+), 62 deletions(-) diff --git a/.bower.json b/.bower.json index 52e4320d..0cb2d1e1 100644 --- a/.bower.json +++ b/.bower.json @@ -1,6 +1,6 @@ { "name": "textAngular", - "version": "1.1.1", + "version": "1.1.2", "main": "./textAngular.js", "description": "A radically powerful Text-Editor/Wysiwyg editor for Angular.js", "keywords": [ diff --git a/README.md b/README.md index abdab3c1..80c78c6e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -textAngular v1.1.1 +textAngular v1.1.2 =========== Demo: http://www.textangular.com diff --git a/changelog.md b/changelog.md index 9b8dbe6f..f8de71e9 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ ###Changelog +2013-12-05 v1.1.2 + +- Added bundled demo pages. +- Fixed Escaping of < and > #30 +- Fixed stripping of style and class attributes whilt maintaining the chrome fixes. #35 #5 +- Fixed two-way-binding not working #38 + 2013-11-27 v1.1.1 - Fixed buttons still submitting form diff --git a/demo/index.html b/demo/index.html index 716591fb..af7dd3ec 100644 --- a/demo/index.html +++ b/demo/index.html @@ -28,9 +28,13 @@
+

Editor

+

Raw HTML in a text area

+

Bound with ng-bind-html

+

Note: although we support classes and styles, angularjs' ng-bind-html directive will strip out all style attributes.

diff --git a/demo/js/index.js b/demo/js/index.js index aca9f2dc..ab7ee8b1 100644 --- a/demo/js/index.js +++ b/demo/js/index.js @@ -4,7 +4,8 @@ angular.module("textAngularTest", ['textAngular']); wysiwygeditor = function($scope) { - return $scope.htmlcontent = '

Hello World!

'; + $scope.orightml = '

Try me!

textAngular is a super cool WYSIWYG Text Editor directive for AngularJS

Features:

  1. Automatic Seamless Two-Way-Binding
  2. Super Easy Theming Options
  3. Simple Editor Instance Creation
  4. Safely Parses Html for Custom Toolbar Icons
  5. Doesn't Use an iFrame
  6. Works with Firefox, Chrome, and IE8+

Code at GitHub: Here

'; + $scope.htmlcontent = $scope.orightml; }; this.wysiwygeditor = wysiwygeditor; diff --git a/textAngular.js b/textAngular.js index 1c2b6582..280b51f9 100644 --- a/textAngular.js +++ b/textAngular.js @@ -10,7 +10,7 @@ See README.md or http://github.com/fraywing/textangular for requirements and use var textAngular = angular.module("textAngular", ['ngSanitize']); //This makes ngSanitize required -textAngular.directive("textAngular", function($compile, $sce, $window, $document, $rootScope, $timeout, taFixSelection) { +textAngular.directive("textAngular", function($compile, $sce, $window, $document, $rootScope, $timeout, taFixChrome) { console.log("Thank you for using textAngular! http://www.textangular.com") // deepExtend instead of angular.extend in order to allow easy customization of "display" for default buttons // snatched from: http://stackoverflow.com/a/15311794/2966847 @@ -200,19 +200,17 @@ textAngular.directive("textAngular", function($compile, $sce, $window, $document // get the settings from the defaults and add our specific functions that need to be on the scope angular.extend(scope, $rootScope.textAngularOpts, { // wraps the selection in the provided tag / execCommand function. - wrapSelection: function(command, opt, updateDisplay) { - // the default value for updateDisplay is true - if (updateDisplay == null) updateDisplay = true; + wrapSelection: function(command, opt) { document.execCommand(command, false, opt); // strip out the chrome specific rubbish that gets put in when using lists - if(command === 'insertUnorderedList' || command === 'insertOrderedList') taFixSelection(); + if(command === 'insertUnorderedList' || command === 'insertOrderedList') taFixChrome(scope.displayElements.text); // refocus on the shown display element, this fixes a display bug when using :focus styles to outline the box. You still have focus on the text/html input it just doesn't show up if (scope.showHtml) scope.displayElements.html[0].focus(); else scope.displayElements.text[0].focus(); // note that wrapSelection is called via ng-click in the tool plugins so we are already within a $apply - if (updateDisplay && !scope.showHtml) scope.updateTaBindtext(); // only update if NOT in html mode + if (!scope.showHtml) scope.updateTaBindtext(); // only update if NOT in html mode }, showHtml: false }); @@ -336,7 +334,7 @@ textAngular.directive("textAngular", function($compile, $sce, $window, $document scope.displayElements.text.on('mouseup', mouseup); } }; -}).directive('taBind', function($sce, $sanitize, $document, taFixSelection){ +}).directive('taBind', function($sce, $sanitize, $document, taFixChrome){ // ngSanitize is a requirement for the module so this shouldn't cause any trouble var sanitizationWrapper = function(html) { return $sce.trustAsHtml(html); @@ -346,49 +344,60 @@ textAngular.directive("textAngular", function($compile, $sce, $window, $document require: 'ngModel', scope: {'taBind': '@'}, link: function(scope,element,attrs,ngModel){ + // 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(){ + var result = taFixChrome(angular.element("
").append(element.html())).html(); + if(scope.taBind !== 'text') result = result.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, '&'); + return result; + }; + scope.$parent['updateTaBind' + scope.taBind] = function(){//used for updating when inserting wrapped elements - console.log(element.html()); + var compHtml = compileHtml(); var tempParsers = ngModel.$parsers; ngModel.$parsers = []; // temp disable of the parsers - ngModel.$oldViewValue = element.html(); - ngModel.$setViewValue(element.html()); + ngModel.$oldViewValue = compHtml; + ngModel.$setViewValue(compHtml); ngModel.$parsers = tempParsers; }; + + //this code is used to update the models when data is entered/deleted element.on('keyup', function(e){ - if(e.which === 8 || e.which === 46) taFixSelection();//catch backspace/delete and remove the chrome rubbish if any - // in here we are undoing the converts used elsewhere to prevent the < > and & being displayed when they shouldn't in the code. - var compHtml = angular.element("
").append(element.html()).html().replace(/</g, "<").replace(/>/g, ">"); - if(scope.taBind !== 'text') compHtml = compHtml.replace(/&/g, '&'); - - if(e.which === 13){ // bypass the parsers for inserting the linebreak text or whatever it happens to be - scope.$apply(function(){ - var tempParsers = ngModel.$parsers; - ngModel.$parsers = []; // temp disable of the parsers - ngModel.$oldViewValue = compHtml; - ngModel.$setViewValue(compHtml); - ngModel.$parsers = tempParsers; - }); - }else{ - ngModel.$setViewValue(compHtml); - } + ngModel.$setViewValue(compileHtml()); }); ngModel.$parsers.push(function(value){ // all the code here takes the information from the above keyup function or any other time that the viewValue is updated and parses it for storage in the ngModel if(ngModel.$oldViewValue === undefined) ngModel.$oldViewValue = value; if(scope.taBind === 'text' && value !== ngModel.$oldViewValue){//WYSIWYG Mode and view is changed + if(value === undefined || value === '') return value; //this code is specifically to catch the insertion of < and > which need to be converted to < and > respectively - // finds the first difference from the start of the text. - var start; - for(start = 0; start < Math.max(value.length, ngModel.$oldViewValue.length); start++) - if(value.charAt(start) !== ngModel.$oldViewValue.charAt(start)) break; // finds the end of the difference var end; - for(end = 0; Math.max(value.length, ngModel.$oldViewValue.length); end++) - if(value.charAt(value.length - end) !== ngModel.$oldViewValue.charAt(ngModel.$oldViewValue.length - end)) break; + for(end = 0; end < Math.min(value.length, ngModel.$oldViewValue.length); end++){ + var oldViewEnd = ngModel.$oldViewValue.length - end; + if(value.charAt(value.length - end) !== ngModel.$oldViewValue.charAt(oldViewEnd)){ + // note: value never has > or <, oldViewValue ALLWAYS has them. This code dynamically equates < to < and > to > + if((value.charAt(value.length - end) === '<' && ngModel.$oldViewValue.substring(oldViewEnd - 4, oldViewEnd) === '<') || ( value.charAt(value.length - end) === '>' && ngModel.$oldViewValue.substring(oldViewEnd - 4, oldViewEnd) === '>')){ + value = value.substring(0,value.length - end) + ngModel.$oldViewValue.substring(oldViewEnd - 4, oldViewEnd) + value.substring(value.length - end + 1); + end += 3; + }else break; + }else if(value.charAt(value.length - end) === '>' && value.substring(value.length - end - 1, value.length - end + 1) === '>>') break; //specific catch for inserting a '<' just before an invisible html tag + } + // finds the first difference from the start of the text. + var start; + for(start = 0; start < Math.min(value.length, ngModel.$oldViewValue.length) && Math.min(value.length, ngModel.$oldViewValue.length) - end > start; start++){ + if(value.charAt(start) !== ngModel.$oldViewValue.charAt(start)){ + // note: value never has > or <, oldViewValue ALLWAYS has them. This code dynamically equates < to < and > to > + if((value.charAt(start) === '<' && ngModel.$oldViewValue.substring(start, start + 4) === '<') || ( value.charAt(start) === '>' && ngModel.$oldViewValue.substring(start, start + 4) === '>')){ + value = value.substring(0,start) + ngModel.$oldViewValue.substring(start, start + 4) + value.substring(start + 1); + start += 3; + }else break; + }else if(value.charAt(start) === '<' && value.substring(start, start + 2) === '<<') break; //specific catch for inserting a '<' just before an invisible html tag + } // get the inserted text var insert = value.substring(start, value.length - end + 1); - if(insert.match(/[<>]/gi)){// doesn't match on deletes, but this code is for when we are INSERTING < or > + // doesn't match on deletes, but this code is for when we are INSERTING < or >. The <= 3 is a catch so for when deleting lines - so a large difference as it's 1+ html tags, the normal human won't hit more than 2 keys at the same time by accident that usually includes a < or >. + if((insert.match(/[<>]/gi) && insert.length <= 3) || insert.match(/^[<>]+$/gi)){ value = value.substring(0,start) + insert.replace(//g, ">") + value.substring(value.length - end + 1); ngModel.$oldViewValue = value; ngModel.$setViewValue(value); // don't forget to update the view @@ -400,7 +409,7 @@ textAngular.directive("textAngular", function($compile, $sce, $window, $document return ngModel.$oldViewValue; //prevents the errors occuring when we are typing in html code } } - ngModel.$oldViewValue = ngModel.$viewValue; + ngModel.$oldViewValue = value; return value; }); @@ -419,31 +428,20 @@ textAngular.directive("textAngular", function($compile, $sce, $window, $document }; } }; -}).factory('taFixSelection', function(){ - return function(){ - var currentNode = angular.element(window.getSelection().focusNode.parentNode); - if(currentNode.html().match(/style="font-family: inherit; line-height: 1.428571429;"/i)){ - for(i in currentNode[0].childNodes){ - if(angular.element(currentNode[0].childNodes[i])[0].tagName === 'SPAN'){ - currentNode = angular.element(currentNode[0].childNodes[i]); - break - } - } - }else{ - while(currentNode.attr('style') !== 'font-family: inherit; line-height: 1.428571429;' && !currentNode.attr('contenteditable')) currentNode = currentNode.parent(); - } - if(currentNode.attr('style') === 'font-family: inherit; line-height: 1.428571429;' && !currentNode.attr('contenteditable')){ - // chrome wraps the origional line, if not wrapped already, in the following code: ...
- // this code will strip the above out if it has been added - if(currentNode[0].tagName === 'SPAN'){ - currentNode = angular.element(currentNode); - currentNode.replaceWith(currentNode.html()); - // if the text was already wrapped in a b or i or similar then chrome adds the offending style and appends a
. This catches that case. - }else{ - currentNode.removeAttr('style'); - var nextNode = currentNode.next(); - if(nextNode[0].tagName === 'BR') nextNode.remove(); +}).factory('taFixChrome', function(){ + var taFixChrome = function($html){ // should be an angular.element object, returns object for chaining convenience + // fix the chrome trash that gets inserted sometimes + var spans = angular.element($html).find('span'); // default wrapper is a span so find all of them + for(var s = 0; s < spans.length; s++){ + var span = angular.element(spans[s]); + if(span.attr('style') && span.attr('style').match(/line-height: 1.428571429;/i)){ // chrome specific string that gets inserted into the style attribute, other parts may vary. + if(span.next().length > 0 && span.next()[0].tagName === 'BR') span.next().remove() + span.replaceWith(span.html()); } } + var result = $html.html().replace(/style="[^"]*?line-height: 1.428571429;[^"]*"/ig, ''); // regex to replace ONLY offending styles - these can be inserted into various other tags on delete + $html.html(result); + return $html; }; + return taFixChrome; }); \ No newline at end of file diff --git a/textAngular.min.js b/textAngular.min.js index 10d01526..b1413c8d 100644 --- a/textAngular.min.js +++ b/textAngular.min.js @@ -1 +1 @@ -var textAngular=angular.module("textAngular",["ngSanitize"]);textAngular.directive("textAngular",function($compile,$sce,$window,$document,$rootScope,$timeout,taFixSelection){console.log("Thank you for using textAngular! http://www.textangular.com");$rootScope.textAngularOpts=angular.extend({toolbar:[["h1","h2","h3","p","pre","bold","italics","ul","ol","redo","undo","clear"],["html","insertImage","insertLink"]],classes:{toolbar:"btn-toolbar",toolbarGroup:"btn-group",toolbarButton:"btn btn-default",toolbarButtonActive:"active",textEditor:"form-control",htmlEditor:"form-control"}},$rootScope.textAngularOpts!=null?$rootScope.textAngularOpts:{});function deepExtend(destination,source){for(var property in source){if(source[property]&&source[property].constructor&&source[property].constructor===Object){destination[property]=destination[property]||{};arguments.callee(destination[property],source[property])}else{destination[property]=source[property]}}return destination}$rootScope.textAngularTools=deepExtend({html:{display:"",action:function(){var ht,_this=this;this.$parent.showHtml=!this.$parent.showHtml;if(this.$parent.showHtml){$timeout(function(){return _this.$parent.displayElements.html[0].focus()},100)}else{$timeout(function(){return _this.$parent.displayElements.text[0].focus()},100)}this.active=this.$parent.showHtml}},h1:{display:"",action:function(){return this.$parent.wrapSelection("formatBlock","

")}},h2:{display:"",action:function(){return this.$parent.wrapSelection("formatBlock","

")}},h3:{display:"",action:function(){return this.$parent.wrapSelection("formatBlock","

")}},p:{display:"",action:function(){return this.$parent.wrapSelection("formatBlock","

")}},pre:{display:"",action:function(){return this.$parent.wrapSelection("formatBlock","

")}},ul:{display:"",action:function(){return this.$parent.wrapSelection("insertUnorderedList",null)}},ol:{display:"",action:function(){return this.$parent.wrapSelection("insertOrderedList",null)}},quote:{display:"",action:function(){return this.$parent.wrapSelection("formatBlock","
")}},undo:{display:"",action:function(){return this.$parent.wrapSelection("undo",null)}},redo:{display:"",action:function(){return this.$parent.wrapSelection("redo",null)}},bold:{display:"",action:function(){return this.$parent.wrapSelection("bold",null)},activeState:function(){return $document[0].queryCommandState("bold")}},justifyLeft:{display:"",action:function(){return this.$parent.wrapSelection("justifyLeft",null)},activeState:function(){return $document[0].queryCommandState("justifyLeft")}},justifyRight:{display:"",action:function(){return this.$parent.wrapSelection("justifyRight",null)},activeState:function(){return $document[0].queryCommandState("justifyRight")}},justifyCenter:{display:"",action:function(){return this.$parent.wrapSelection("justifyCenter",null)},activeState:function(){return $document[0].queryCommandState("justifyCenter")}},italics:{display:"",action:function(){return this.$parent.wrapSelection("italic",null)},activeState:function(){return $document[0].queryCommandState("italic")}},clear:{display:"",action:function(){return this.$parent.wrapSelection("FormatBlock","
")}},insertImage:{display:"",action:function(){var imageLink;imageLink=prompt("Please enter an image URL to insert","http://");if(imageLink!==""){return this.$parent.wrapSelection("insertImage",imageLink)}}},insertLink:{display:"",action:function(){var urlLink;urlLink=prompt("Please enter an URL to insert","http://");if(urlLink!==""){return this.$parent.wrapSelection("createLink",urlLink)}}}},$rootScope.textAngularTools!=null?$rootScope.textAngularTools:{});return{require:"ngModel",scope:{},restrict:"EA",link:function(scope,element,attrs,ngModel){var group,groupElement,keydown,keyup,tool,toolElement;angular.extend(scope,$rootScope.textAngularOpts,{wrapSelection:function(command,opt,updateDisplay){if(updateDisplay==null)updateDisplay=true;document.execCommand(command,false,opt);if(command==="insertUnorderedList"||command==="insertOrderedList")taFixSelection();if(scope.showHtml)scope.displayElements.html[0].focus();else scope.displayElements.text[0].focus();if(updateDisplay&&!scope.showHtml)scope.updateTaBindtext()},showHtml:false});if(!!attrs.taToolbar)scope.toolbar=scope.$eval(attrs.taToolbar);if(!!attrs.taToolbarClass)scope.classes.toolbar=attrs.taToolbarClass;if(!!attrs.taToolbarGroupClass)scope.classes.toolbarGroup=attrs.taToolbarGroupClass;if(!!attrs.taToolbarButtonClass)scope.classes.toolbarButton=attrs.taToolbarButtonClass;if(!!attrs.taToolbarActiveButtonClass)scope.classes.toolbarButtonActive=attrs.taToolbarActiveButtonClass;if(!!attrs.taTextEditorClass)scope.classes.textEditor=attrs.taTextEditorClass;if(!!attrs.taHtmlEditorClass)scope.classes.htmlEditor=attrs.taHtmlEditorClass;scope.displayElements={toolbar:angular.element("
"),html:angular.element("
"),text:angular.element("
")};element.append(scope.displayElements.toolbar);element.append(scope.displayElements.text);element.append(scope.displayElements.html);$compile(scope.displayElements.text)(scope);$compile(scope.displayElements.html)(scope);element.addClass("ta-root");scope.displayElements.toolbar.addClass("ta-toolbar "+scope.classes.toolbar);scope.displayElements.text.addClass("ta-text ta-editor "+scope.classes.textEditor);scope.displayElements.html.addClass("ta-html ta-editor "+scope.classes.textEditor);scope.tools={};for(var _i=0;_i
");groupElement.addClass(scope.classes.toolbarGroup);for(var _j=0;_j").append(element.html()).html().replace(/</g,"<").replace(/>/g,">");if(scope.taBind!=="text")compHtml=compHtml.replace(/&/g,"&");if(e.which===13){scope.$apply(function(){var tempParsers=ngModel.$parsers;ngModel.$parsers=[];ngModel.$oldViewValue=compHtml;ngModel.$setViewValue(compHtml);ngModel.$parsers=tempParsers})}else{ngModel.$setViewValue(compHtml)}});ngModel.$parsers.push(function(value){if(ngModel.$oldViewValue===undefined)ngModel.$oldViewValue=value;if(scope.taBind==="text"&&value!==ngModel.$oldViewValue){var start;for(start=0;start]/gi)){value=value.substring(0,start)+insert.replace(//g,">")+value.substring(value.length-end+1);ngModel.$oldViewValue=value;ngModel.$setViewValue(value)}}else{try{$sanitize(value)}catch(e){return ngModel.$oldViewValue}}ngModel.$oldViewValue=ngModel.$viewValue;return value});ngModel.$render=function(){if(ngModel.$viewValue===undefined)return;if($document[0].activeElement!==element[0]){var val=ngModel.$viewValue||"";ngModel.$oldViewValue=val;if(scope.taBind=="text")element.html(sanitizationWrapper(val));else element.html(sanitizationWrapper(val.replace(/&/g,"&").replace(//g,">")))}}}}}).factory("taFixSelection",function(){return function(){var currentNode=angular.element(window.getSelection().focusNode.parentNode);if(currentNode.html().match(/style="font-family: inherit; line-height: 1.428571429;"/i)){for(i in currentNode[0].childNodes){if(angular.element(currentNode[0].childNodes[i])[0].tagName==="SPAN"){currentNode=angular.element(currentNode[0].childNodes[i]);break}}}else{while(currentNode.attr("style")!=="font-family: inherit; line-height: 1.428571429;"&&!currentNode.attr("contenteditable"))currentNode=currentNode.parent()}if(currentNode.attr("style")==="font-family: inherit; line-height: 1.428571429;"&&!currentNode.attr("contenteditable")){if(currentNode[0].tagName==="SPAN"){currentNode=angular.element(currentNode);currentNode.replaceWith(currentNode.html())}else{currentNode.removeAttr("style");var nextNode=currentNode.next();if(nextNode[0].tagName==="BR")nextNode.remove()}}}}); \ No newline at end of file +var textAngular=angular.module("textAngular",["ngSanitize"]);textAngular.directive("textAngular",function($compile,$sce,$window,$document,$rootScope,$timeout,taFixChrome){console.log("Thank you for using textAngular! http://www.textangular.com");function deepExtend(destination,source){for(var property in source){if(source[property]&&source[property].constructor&&source[property].constructor===Object){destination[property]=destination[property]||{};arguments.callee(destination[property],source[property])}else{destination[property]=source[property]}}return destination}$rootScope.textAngularOpts=deepExtend({toolbar:[["h1","h2","h3","p","pre","bold","italics","ul","ol","redo","undo","clear"],["html","insertImage","insertLink"]],classes:{toolbar:"btn-toolbar",toolbarGroup:"btn-group",toolbarButton:"btn btn-default",toolbarButtonActive:"active",textEditor:"form-control",htmlEditor:"form-control"}},$rootScope.textAngularOpts!=null?$rootScope.textAngularOpts:{});$rootScope.textAngularTools=deepExtend({html:{display:"",action:function(){var ht,_this=this;this.$parent.showHtml=!this.$parent.showHtml;if(this.$parent.showHtml){$timeout(function(){return _this.$parent.displayElements.html[0].focus()},100)}else{$timeout(function(){return _this.$parent.displayElements.text[0].focus()},100)}this.active=this.$parent.showHtml}},h1:{display:"",action:function(){return this.$parent.wrapSelection("formatBlock","

")}},h2:{display:"",action:function(){return this.$parent.wrapSelection("formatBlock","

")}},h3:{display:"",action:function(){return this.$parent.wrapSelection("formatBlock","

")}},p:{display:"",action:function(){return this.$parent.wrapSelection("formatBlock","

")}},pre:{display:"",action:function(){return this.$parent.wrapSelection("formatBlock","

")}},ul:{display:"",action:function(){return this.$parent.wrapSelection("insertUnorderedList",null)}},ol:{display:"",action:function(){return this.$parent.wrapSelection("insertOrderedList",null)}},quote:{display:"",action:function(){return this.$parent.wrapSelection("formatBlock","
")}},undo:{display:"",action:function(){return this.$parent.wrapSelection("undo",null)}},redo:{display:"",action:function(){return this.$parent.wrapSelection("redo",null)}},bold:{display:"",action:function(){return this.$parent.wrapSelection("bold",null)},activeState:function(){return $document[0].queryCommandState("bold")}},justifyLeft:{display:"",action:function(){return this.$parent.wrapSelection("justifyLeft",null)},activeState:function(){return $document[0].queryCommandState("justifyLeft")}},justifyRight:{display:"",action:function(){return this.$parent.wrapSelection("justifyRight",null)},activeState:function(){return $document[0].queryCommandState("justifyRight")}},justifyCenter:{display:"",action:function(){return this.$parent.wrapSelection("justifyCenter",null)},activeState:function(){return $document[0].queryCommandState("justifyCenter")}},italics:{display:"",action:function(){return this.$parent.wrapSelection("italic",null)},activeState:function(){return $document[0].queryCommandState("italic")}},clear:{display:"",action:function(){return this.$parent.wrapSelection("FormatBlock","
")}},insertImage:{display:"",action:function(){var imageLink;imageLink=prompt("Please enter an image URL to insert","http://");if(imageLink!==""){return this.$parent.wrapSelection("insertImage",imageLink)}}},insertLink:{display:"",action:function(){var urlLink;urlLink=prompt("Please enter an URL to insert","http://");if(urlLink!==""){return this.$parent.wrapSelection("createLink",urlLink)}}}},$rootScope.textAngularTools!=null?$rootScope.textAngularTools:{});return{require:"ngModel",scope:{},restrict:"EA",link:function(scope,element,attrs,ngModel){var group,groupElement,keydown,keyup,tool,toolElement;angular.extend(scope,$rootScope.textAngularOpts,{wrapSelection:function(command,opt){document.execCommand(command,false,opt);if(command==="insertUnorderedList"||command==="insertOrderedList")taFixChrome(scope.displayElements.text);if(scope.showHtml)scope.displayElements.html[0].focus();else scope.displayElements.text[0].focus();if(!scope.showHtml)scope.updateTaBindtext()},showHtml:false});if(!!attrs.taToolbar)scope.toolbar=scope.$eval(attrs.taToolbar);if(!!attrs.taToolbarClass)scope.classes.toolbar=attrs.taToolbarClass;if(!!attrs.taToolbarGroupClass)scope.classes.toolbarGroup=attrs.taToolbarGroupClass;if(!!attrs.taToolbarButtonClass)scope.classes.toolbarButton=attrs.taToolbarButtonClass;if(!!attrs.taToolbarActiveButtonClass)scope.classes.toolbarButtonActive=attrs.taToolbarActiveButtonClass;if(!!attrs.taTextEditorClass)scope.classes.textEditor=attrs.taTextEditorClass;if(!!attrs.taHtmlEditorClass)scope.classes.htmlEditor=attrs.taHtmlEditorClass;scope.displayElements={toolbar:angular.element("
"),html:angular.element("
"),text:angular.element("
")};element.append(scope.displayElements.toolbar);element.append(scope.displayElements.text);element.append(scope.displayElements.html);$compile(scope.displayElements.text)(scope);$compile(scope.displayElements.html)(scope);element.addClass("ta-root");scope.displayElements.toolbar.addClass("ta-toolbar "+scope.classes.toolbar);scope.displayElements.text.addClass("ta-text ta-editor "+scope.classes.textEditor);scope.displayElements.html.addClass("ta-html ta-editor "+scope.classes.textEditor);scope.tools={};for(var _i=0;_i
");groupElement.addClass(scope.classes.toolbarGroup);for(var _j=0;_j").append(element.html())).html();if(scope.taBind!=="text")result=result.replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&");return result};scope.$parent["updateTaBind"+scope.taBind]=function(){var compHtml=compileHtml();var tempParsers=ngModel.$parsers;ngModel.$parsers=[];ngModel.$oldViewValue=compHtml;ngModel.$setViewValue(compHtml);ngModel.$parsers=tempParsers};element.on("keyup",function(e){ngModel.$setViewValue(compileHtml())});ngModel.$parsers.push(function(value){if(ngModel.$oldViewValue===undefined)ngModel.$oldViewValue=value;if(scope.taBind==="text"&&value!==ngModel.$oldViewValue){if(value===undefined||value==="")return value;var end;for(end=0;end"&&ngModel.$oldViewValue.substring(oldViewEnd-4,oldViewEnd)===">"){value=value.substring(0,value.length-end)+ngModel.$oldViewValue.substring(oldViewEnd-4,oldViewEnd)+value.substring(value.length-end+1);end+=3}else break}else if(value.charAt(value.length-end)===">"&&value.substring(value.length-end-1,value.length-end+1)===">>")break}var start;for(start=0;startstart;start++){if(value.charAt(start)!==ngModel.$oldViewValue.charAt(start)){if(value.charAt(start)==="<"&&ngModel.$oldViewValue.substring(start,start+4)==="<"||value.charAt(start)===">"&&ngModel.$oldViewValue.substring(start,start+4)===">"){value=value.substring(0,start)+ngModel.$oldViewValue.substring(start,start+4)+value.substring(start+1);start+=3}else break}else if(value.charAt(start)==="<"&&value.substring(start,start+2)==="<<")break}var insert=value.substring(start,value.length-end+1);if(insert.match(/[<>]/gi)&&insert.length<=3||insert.match(/^[<>]+$/gi)){value=value.substring(0,start)+insert.replace(//g,">")+value.substring(value.length-end+1);ngModel.$oldViewValue=value;ngModel.$setViewValue(value)}}else{try{$sanitize(value)}catch(e){return ngModel.$oldViewValue}}ngModel.$oldViewValue=value;return value});ngModel.$render=function(){if(ngModel.$viewValue===undefined)return;if($document[0].activeElement!==element[0]){var val=ngModel.$viewValue||"";ngModel.$oldViewValue=val;if(scope.taBind=="text")element.html(sanitizationWrapper(val));else element.html(sanitizationWrapper(val.replace(/&/g,"&").replace(//g,">")))}}}}}).factory("taFixChrome",function(){var taFixChrome=function($html){var spans=angular.element($html).find("span");for(var s=0;s0&&span.next()[0].tagName==="BR")span.next().remove();span.replaceWith(span.html())}}var result=$html.html().replace(/style="[^"]*?line-height: 1.428571429;[^"]*"/gi,"");$html.html(result);return $html};return taFixChrome}); \ No newline at end of file