odania 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/{LICENSE.txt → MIT-LICENSE} +1 -3
  3. data/README.md +3 -3
  4. data/Rakefile +33 -3
  5. data/app/assets/config/odania_manifest.js +0 -0
  6. data/app/assets/javascripts/application.js +13 -0
  7. data/app/assets/javascripts/textAngular.js +15 -0
  8. data/app/assets/javascripts/textAngular/textAngular-rangy.min.js +478 -0
  9. data/app/assets/javascripts/textAngular/textAngular-sanitize.min.js +322 -0
  10. data/app/assets/javascripts/textAngular/textAngular.min.js +1481 -0
  11. data/app/assets/stylesheets/scaffold.css +80 -0
  12. data/app/assets/stylesheets/textAngular/application.css +15 -0
  13. data/app/assets/stylesheets/textAngular/textAngular.css +204 -0
  14. data/app/controllers/admin/home_controller.rb +2 -0
  15. data/app/controllers/admin/languages_controller.rb +74 -0
  16. data/app/controllers/admin_controller.rb +4 -0
  17. data/app/controllers/application_controller.rb +3 -0
  18. data/app/controllers/categories_controller.rb +34 -0
  19. data/app/controllers/home_controller.rb +11 -0
  20. data/app/controllers/protected/home_controller.rb +2 -0
  21. data/app/controllers/protected_controller.rb +22 -0
  22. data/app/controllers/registration_controller.rb +12 -0
  23. data/app/helpers/standard_form_builder.rb +71 -0
  24. data/app/helpers/standard_form_helper.rb +13 -0
  25. data/app/models/admin.rb +37 -0
  26. data/app/models/language.rb +5 -0
  27. data/app/models/user.rb +47 -0
  28. data/app/views/admin/home/index.html.erb +1 -0
  29. data/app/views/admin/languages/_form.html.erb +16 -0
  30. data/app/views/admin/languages/_language.json.jbuilder +2 -0
  31. data/app/views/admin/languages/edit.html.erb +6 -0
  32. data/app/views/admin/languages/index.html.erb +27 -0
  33. data/app/views/admin/languages/index.json.jbuilder +1 -0
  34. data/app/views/admin/languages/new.html.erb +5 -0
  35. data/app/views/admin/languages/show.html.erb +9 -0
  36. data/app/views/admin/languages/show.json.jbuilder +1 -0
  37. data/app/views/categories/index.html.erb +9 -0
  38. data/app/views/categories/show.html.erb +16 -0
  39. data/app/views/devise/confirmations/new.html.erb +11 -0
  40. data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
  41. data/app/views/devise/mailer/email_changed.html.erb +7 -0
  42. data/app/views/devise/mailer/password_change.html.erb +4 -0
  43. data/app/views/devise/mailer/reset_password_instructions.html.erb +9 -0
  44. data/app/views/devise/mailer/unlock_instructions.html.erb +8 -0
  45. data/app/views/devise/passwords/edit.html.erb +13 -0
  46. data/app/views/devise/passwords/new.html.erb +11 -0
  47. data/app/views/devise/registrations/edit.html.erb +39 -0
  48. data/app/views/devise/registrations/new.html.erb +15 -0
  49. data/app/views/devise/sessions/new.html.erb +14 -0
  50. data/app/views/devise/shared/_links.html.erb +25 -0
  51. data/app/views/devise/unlocks/new.html.erb +11 -0
  52. data/app/views/home/index.html.erb +11 -0
  53. data/app/views/languages/_form.html.erb +20 -0
  54. data/app/views/languages/edit.html.erb +6 -0
  55. data/app/views/languages/index.html.erb +27 -0
  56. data/app/views/languages/new.html.erb +5 -0
  57. data/app/views/languages/show.html.erb +9 -0
  58. data/app/views/protected/home/index.html.erb +1 -0
  59. data/config/initializers/elasticsearch.rb +5 -0
  60. data/config/locales/devise.en.yml +64 -0
  61. data/config/routes.rb +23 -0
  62. data/db/seeds.rb +5 -0
  63. data/lib/odania.rb +7 -56
  64. data/lib/odania/engine.rb +14 -0
  65. data/lib/odania/version.rb +1 -1
  66. data/lib/tasks/odania_tasks.rake +4 -0
  67. data/lib/templates/erb/scaffold/_form.html.erb +27 -0
  68. data/lib/templates/erb/scaffold/edit.html.erb +6 -0
  69. data/lib/templates/erb/scaffold/index.html.erb +31 -0
  70. data/lib/templates/erb/scaffold/new.html.erb +5 -0
  71. data/lib/templates/erb/scaffold/show.html.erb +11 -0
  72. metadata +129 -84
  73. data/.codeclimate.yml +0 -30
  74. data/.gitignore +0 -17
  75. data/.rspec +0 -2
  76. data/.rubocop.yml +0 -1156
  77. data/.travis.yml +0 -20
  78. data/Gemfile +0 -4
  79. data/Gemfile.lock +0 -113
  80. data/Guardfile +0 -31
  81. data/features/plugin.feature +0 -35
  82. data/features/step_definitions/plugin_steps.rb +0 -75
  83. data/features/support/env.rb +0 -1
  84. data/lib/odania/config.rb +0 -17
  85. data/lib/odania/config/backend.rb +0 -31
  86. data/lib/odania/config/backend_group.rb +0 -43
  87. data/lib/odania/config/domain.rb +0 -59
  88. data/lib/odania/config/duplicates.rb +0 -28
  89. data/lib/odania/config/global_config.rb +0 -210
  90. data/lib/odania/config/layout.rb +0 -30
  91. data/lib/odania/config/page.rb +0 -29
  92. data/lib/odania/config/page_base.rb +0 -47
  93. data/lib/odania/config/plugin_config.rb +0 -58
  94. data/lib/odania/config/style.rb +0 -36
  95. data/lib/odania/config/sub_domain.rb +0 -113
  96. data/lib/odania/config/subdomain_config.rb +0 -124
  97. data/lib/odania/consul.rb +0 -138
  98. data/lib/odania/plugin.rb +0 -103
  99. data/odania.gemspec +0 -34
  100. data/spec/fixtures/global_config.json +0 -135
  101. data/spec/fixtures/plugin_config_1.json +0 -102
  102. data/spec/lib/odania/config/global_config_spec.rb +0 -69
  103. data/spec/lib/odania/config/plugin_config_spec.rb +0 -31
  104. data/spec/lib/odania/plugin_spec.rb +0 -25
  105. data/spec/lib/odania_spec.rb +0 -10
  106. data/spec/spec_helper.rb +0 -19
  107. data/spec/support/consul_mock.rb +0 -123
  108. data/tasks/odania.rake +0 -8
  109. data/tasks/rspec.rake +0 -7
@@ -0,0 +1,322 @@
1
+ /**
2
+ * @license AngularJS v1.3.10
3
+ * (c) 2010-2014 Google, Inc. http://angularjs.org
4
+ * License: MIT
5
+ */
6
+ !function(a,b,c){"use strict";/**
7
+ * @ngdoc module
8
+ * @name ngSanitize
9
+ * @description
10
+ *
11
+ * # ngSanitize
12
+ *
13
+ * The `ngSanitize` module provides functionality to sanitize HTML.
14
+ *
15
+ *
16
+ * <div doc-module-components="ngSanitize"></div>
17
+ *
18
+ * See {@link ngSanitize.$sanitize `$sanitize`} for usage.
19
+ */
20
+ /*
21
+ * HTML Parser By Misko Hevery (misko@hevery.com)
22
+ * based on: HTML Parser By John Resig (ejohn.org)
23
+ * Original code by Erik Arvidsson, Mozilla Public License
24
+ * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
25
+ *
26
+ * // Use like so:
27
+ * htmlParser(htmlString, {
28
+ * start: function(tag, attrs, unary) {},
29
+ * end: function(tag) {},
30
+ * chars: function(text) {},
31
+ * comment: function(text) {}
32
+ * });
33
+ *
34
+ */
35
+ /**
36
+ * @ngdoc service
37
+ * @name $sanitize
38
+ * @kind function
39
+ *
40
+ * @description
41
+ * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are
42
+ * then serialized back to properly escaped html string. This means that no unsafe input can make
43
+ * it into the returned string, however, since our parser is more strict than a typical browser
44
+ * parser, it's possible that some obscure input, which would be recognized as valid HTML by a
45
+ * browser, won't make it through the sanitizer. The input may also contain SVG markup.
46
+ * The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
47
+ * `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
48
+ *
49
+ * @param {string} html HTML input.
50
+ * @returns {string} Sanitized HTML.
51
+ *
52
+ * @example
53
+ <example module="sanitizeExample" deps="angular-sanitize.js">
54
+ <file name="index.html">
55
+ <script>
56
+ angular.module('sanitizeExample', ['ngSanitize'])
57
+ .controller('ExampleController', ['$scope', '$sce', function($scope, $sce) {
58
+ $scope.snippet =
59
+ '<p style="color:blue">an html\n' +
60
+ '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
61
+ 'snippet</p>';
62
+ $scope.deliberatelyTrustDangerousSnippet = function() {
63
+ return $sce.trustAsHtml($scope.snippet);
64
+ };
65
+ }]);
66
+ </script>
67
+ <div ng-controller="ExampleController">
68
+ Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
69
+ <table>
70
+ <tr>
71
+ <td>Directive</td>
72
+ <td>How</td>
73
+ <td>Source</td>
74
+ <td>Rendered</td>
75
+ </tr>
76
+ <tr id="bind-html-with-sanitize">
77
+ <td>ng-bind-html</td>
78
+ <td>Automatically uses $sanitize</td>
79
+ <td><pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
80
+ <td><div ng-bind-html="snippet"></div></td>
81
+ </tr>
82
+ <tr id="bind-html-with-trust">
83
+ <td>ng-bind-html</td>
84
+ <td>Bypass $sanitize by explicitly trusting the dangerous value</td>
85
+ <td>
86
+ <pre>&lt;div ng-bind-html="deliberatelyTrustDangerousSnippet()"&gt;
87
+ &lt;/div&gt;</pre>
88
+ </td>
89
+ <td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
90
+ </tr>
91
+ <tr id="bind-default">
92
+ <td>ng-bind</td>
93
+ <td>Automatically escapes</td>
94
+ <td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
95
+ <td><div ng-bind="snippet"></div></td>
96
+ </tr>
97
+ </table>
98
+ </div>
99
+ </file>
100
+ <file name="protractor.js" type="protractor">
101
+ it('should sanitize the html snippet by default', function() {
102
+ expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
103
+ toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
104
+ });
105
+
106
+ it('should inline raw snippet if bound to a trusted value', function() {
107
+ expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).
108
+ toBe("<p style=\"color:blue\">an html\n" +
109
+ "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
110
+ "snippet</p>");
111
+ });
112
+
113
+ it('should escape snippet without any filter', function() {
114
+ expect(element(by.css('#bind-default div')).getInnerHtml()).
115
+ toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
116
+ "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
117
+ "snippet&lt;/p&gt;");
118
+ });
119
+
120
+ it('should update', function() {
121
+ element(by.model('snippet')).clear();
122
+ element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
123
+ expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
124
+ toBe('new <b>text</b>');
125
+ expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe(
126
+ 'new <b onclick="alert(1)">text</b>');
127
+ expect(element(by.css('#bind-default div')).getInnerHtml()).toBe(
128
+ "new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");
129
+ });
130
+ </file>
131
+ </example>
132
+ */
133
+ function d(){this.$get=["$$sanitizeUri",function(a){return function(b){"undefined"!=typeof arguments[1]&&(arguments[1].version="taSanitize");var c=[];return g(b,l(c,function(b,c){return!/^unsafe/.test(a(b,c))})),c.join("")}}]}function e(a){var c=[],d=l(c,b.noop);return d.chars(a),c.join("")}function f(a){var b,c={},d=a.split(",");for(b=0;b<d.length;b++)c[d[b]]=!0;return c}/**
134
+ * @example
135
+ * htmlParser(htmlString, {
136
+ * start: function(tag, attrs, unary) {},
137
+ * end: function(tag) {},
138
+ * chars: function(text) {},
139
+ * comment: function(text) {}
140
+ * });
141
+ *
142
+ * @param {string} html string
143
+ * @param {object} handler
144
+ */
145
+ function g(a,c){function d(a,d,f,g){if(d=b.lowercase(d),D[d])for(;k.last()&&E[k.last()];)e("",k.last());C[d]&&k.last()==d&&e("",d),g=z[d]||!!g,g||k.push(d);var i={};f.replace(p,function(a,b,c,d,e){var f=c||d||e||"";i[b]=h(f)}),c.start&&c.start(d,i,g)}function e(a,d){var e,f=0;if(d=b.lowercase(d))
146
+ // Find the closest opened tag of the same type
147
+ for(f=k.length-1;f>=0&&k[f]!=d;f--);if(f>=0){
148
+ // Close all the open elements, up the stack
149
+ for(e=k.length-1;e>=f;e--)c.end&&c.end(k[e]);
150
+ // Remove the open elements from the stack
151
+ k.length=f}}"string"!=typeof a&&(a=null===a||"undefined"==typeof a?"":""+a);var f,g,i,j,k=[],l=a;for(k.last=function(){return k[k.length-1]};a;){
152
+ // Make sure we're not in a script or style element
153
+ if(j="",g=!0,k.last()&&G[k.last()])a=a.replace(new RegExp("([^]*)<\\s*\\/\\s*"+k.last()+"[^>]*>","i"),function(a,b){return b=b.replace(s,"$1").replace(v,"$1"),c.chars&&c.chars(h(b)),""}),e("",k.last());else{
154
+ // White space
155
+ if(y.test(a)){if(i=a.match(y)){i[0];c.whitespace&&c.whitespace(i[0]),a=a.replace(i[0],""),g=!1}}else t.test(a)?(i=a.match(t),i&&(c.comment&&c.comment(i[1]),a=a.replace(i[0],""),g=!1)):u.test(a)?(i=a.match(u),i&&(a=a.replace(i[0],""),g=!1)):r.test(a)?(i=a.match(o),i&&(a=a.substring(i[0].length),i[0].replace(o,e),g=!1)):q.test(a)&&(i=a.match(n),i?(
156
+ // We only have a valid start-tag if there is a '>'.
157
+ i[4]&&(a=a.substring(i[0].length),i[0].replace(n,d)),g=!1):(
158
+ // no ending tag found --- this piece should be encoded as an entity.
159
+ j+="<",a=a.substring(1)));g&&(f=a.indexOf("<"),j+=f<0?a:a.substring(0,f),a=f<0?"":a.substring(f),c.chars&&c.chars(h(j)))}if(a==l)throw m("badparse","The sanitizer was unable to parse the following block of html: {0}",a);l=a}
160
+ // Clean up any remaining tags
161
+ e()}/**
162
+ * decodes all entities into regular string
163
+ * @param value
164
+ * @returns {string} A string with decoded entities.
165
+ */
166
+ function h(a){if(!a)return"";
167
+ // Note: IE8 does not preserve spaces at the start/end of innerHTML
168
+ // so we must capture them and reattach them afterward
169
+ var b=N.exec(a),c=b[1],d=b[3],e=b[2];
170
+ // innerText depends on styling as it doesn't display hidden elements.
171
+ // Therefore, it's better to use textContent not to cause unnecessary
172
+ // reflows. However, IE<9 don't support textContent so the innerText
173
+ // fallback is necessary.
174
+ return e&&(M.innerHTML=e.replace(/</g,"&lt;"),e="textContent"in M?M.textContent:M.innerText),c+e+d}/**
175
+ * Escapes all potentially dangerous characters, so that the
176
+ * resulting string can be safely inserted into attribute or
177
+ * element text.
178
+ * @param value
179
+ * @returns {string} escaped text
180
+ */
181
+ function i(a){return a.replace(/&/g,"&amp;").replace(w,function(a){var b=a.charCodeAt(0),c=a.charCodeAt(1);return"&#"+(1024*(b-55296)+(c-56320)+65536)+";"}).replace(x,function(a){
182
+ // unsafe chars are: \u0000-\u001f \u007f-\u009f \u00ad \u0600-\u0604 \u070f \u17b4 \u17b5 \u200c-\u200f \u2028-\u202f \u2060-\u206f \ufeff \ufff0-\uffff from jslint.com/lint.html
183
+ // decimal values are: 0-31, 127-159, 173, 1536-1540, 1807, 6068, 6069, 8204-8207, 8232-8239, 8288-8303, 65279, 65520-65535
184
+ var b=a.charCodeAt(0);
185
+ // if unsafe character encode
186
+ // if unsafe character encode
187
+ return b<=159||173==b||b>=1536&&b<=1540||1807==b||6068==b||6069==b||b>=8204&&b<=8207||b>=8232&&b<=8239||b>=8288&&b<=8303||65279==b||b>=65520&&b<=65535?"&#"+b+";":a}).replace(/</g,"&lt;").replace(/>/g,"&gt;")}
188
+ // Custom logic for accepting certain style options only - textAngular
189
+ // Currently allows only the color, background-color, text-align, float, width and height attributes
190
+ // all other attributes should be easily done through classes.
191
+ function j(a){var c="",d=a.split(";");return b.forEach(d,function(a){var d=a.split(":");if(2==d.length){var e=O(b.lowercase(d[0])),a=O(b.lowercase(d[1]));(("color"===e||"background-color"===e)&&(a.match(/^rgb\([0-9%,\. ]*\)$/i)||a.match(/^rgba\([0-9%,\. ]*\)$/i)||a.match(/^hsl\([0-9%,\. ]*\)$/i)||a.match(/^hsla\([0-9%,\. ]*\)$/i)||a.match(/^#[0-9a-f]{3,6}$/i)||a.match(/^[a-z]*$/i))||"text-align"===e&&("left"===a||"right"===a||"center"===a||"justify"===a)||"text-decoration"===e&&("underline"===a||"line-through"===a)||"font-weight"===e&&"bold"===a||"font-style"===e&&"italic"===a||"float"===e&&("left"===a||"right"===a||"none"===a)||"vertical-align"===e&&("baseline"===a||"sub"===a||"super"===a||"test-top"===a||"text-bottom"===a||"middle"===a||"top"===a||"bottom"===a||a.match(/[0-9]*(px|em)/)||a.match(/[0-9]+?%/))||"font-size"===e&&("xx-small"===a||"x-small"===a||"small"===a||"medium"===a||"large"===a||"x-large"===a||"xx-large"===a||"larger"===a||"smaller"===a||a.match(/[0-9]*\.?[0-9]*(px|em|rem|mm|q|cm|in|pt|pc|%)/))||("width"===e||"height"===e)&&a.match(/[0-9\.]*(px|em|rem|%)/)||// Reference #520
192
+ "direction"===e&&a.match(/^ltr|rtl|initial|inherit$/))&&(c+=e+": "+a+";")}}),c}
193
+ // this function is used to manually allow specific attributes on specific tags with certain prerequisites
194
+ function k(a,b,c,d){
195
+ // catch the div placeholder for the iframe replacement
196
+ return!("img"!==a||!b["ta-insert-video"]||"ta-insert-video"!==c&&"allowfullscreen"!==c&&"frameborder"!==c&&("contenteditable"!==c||"false"!==d))}/**
197
+ * create an HTML/XML writer which writes to buffer
198
+ * @param {Array} buf use buf.jain('') to get out sanitized html string
199
+ * @returns {object} in the form of {
200
+ * start: function(tag, attrs, unary) {},
201
+ * end: function(tag) {},
202
+ * chars: function(text) {},
203
+ * comment: function(text) {}
204
+ * }
205
+ */
206
+ function l(a,c){var d=!1,e=b.bind(a,a.push);return{start:function(a,f,g){a=b.lowercase(a),!d&&G[a]&&(d=a),d||H[a]!==!0||(e("<"),e(a),b.forEach(f,function(d,g){var h=b.lowercase(g),l="img"===a&&"src"===h||"background"===h;("style"===h&&""!==(d=j(d))||k(a,f,h,d)||L[h]===!0&&(I[h]!==!0||c(d,l)))&&(e(" "),e(g),e('="'),e(i(d)),e('"'))}),e(g?"/>":">"))},comment:function(a){e(a)},whitespace:function(a){e(i(a))},end:function(a){a=b.lowercase(a),d||H[a]!==!0||(e("</"),e(a),e(">")),a==d&&(d=!1)},chars:function(a){d||e(i(a))}}}var m=b.$$minErr("$sanitize"),n=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,o=/^<\/\s*([\w:-]+)[^>]*>/,p=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,q=/^</,r=/^<\//,s=/<!--(.*?)-->/g,t=/(^<!--.*?-->)/,u=/<!DOCTYPE([^>]*?)>/i,v=/<!\[CDATA\[(.*?)]]>/g,w=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
207
+ // Match everything outside of normal chars and " (quote character)
208
+ x=/([^\#-~| |!])/g,y=/^(\s+)/,z=f("area,br,col,hr,img,wbr,input"),A=f("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),B=f("rp,rt"),C=b.extend({},B,A),D=b.extend({},A,f("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),E=b.extend({},B,f("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),F=f("animate,animateColor,animateMotion,animateTransform,circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,radialGradient,rect,set,stop,svg,switch,text,title,tspan,use"),G=f("script,style"),H=b.extend({},z,D,E,C,F),I=f("background,cite,href,longdesc,src,usemap,xlink:href"),J=f("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,id,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,target,title,type,valign,value,vspace,width"),K=f("accent-height,accumulate,additive,alphabetic,arabic-form,ascent,attributeName,attributeType,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan"),L=b.extend({},I,K,J),M=document.createElement("pre"),N=/^(\s*)([\s\S]*?)(\s*)$/,O=function(){
209
+ // native trim is way faster: http://jsperf.com/angular-trim-test
210
+ // but IE doesn't have it... :-(
211
+ // TODO: we should move this into IE/ES5 polyfill
212
+ // native trim is way faster: http://jsperf.com/angular-trim-test
213
+ // but IE doesn't have it... :-(
214
+ // TODO: we should move this into IE/ES5 polyfill
215
+ return String.prototype.trim?function(a){return b.isString(a)?a.trim():a}:function(a){return b.isString(a)?a.replace(/^\s\s*/,"").replace(/\s\s*$/,""):a}}();
216
+ // define ngSanitize module and register $sanitize service
217
+ b.module("ngSanitize",[]).provider("$sanitize",d),/* global sanitizeText: false */
218
+ /**
219
+ * @ngdoc filter
220
+ * @name linky
221
+ * @kind function
222
+ *
223
+ * @description
224
+ * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
225
+ * plain email address links.
226
+ *
227
+ * Requires the {@link ngSanitize `ngSanitize`} module to be installed.
228
+ *
229
+ * @param {string} text Input text.
230
+ * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
231
+ * @returns {string} Html-linkified text.
232
+ *
233
+ * @usage
234
+ <span ng-bind-html="linky_expression | linky"></span>
235
+ *
236
+ * @example
237
+ <example module="linkyExample" deps="angular-sanitize.js">
238
+ <file name="index.html">
239
+ <script>
240
+ angular.module('linkyExample', ['ngSanitize'])
241
+ .controller('ExampleController', ['$scope', function($scope) {
242
+ $scope.snippet =
243
+ 'Pretty text with some links:\n'+
244
+ 'http://angularjs.org/,\n'+
245
+ 'mailto:us@somewhere.org,\n'+
246
+ 'another@somewhere.org,\n'+
247
+ 'and one more: ftp://127.0.0.1/.';
248
+ $scope.snippetWithTarget = 'http://angularjs.org/';
249
+ }]);
250
+ </script>
251
+ <div ng-controller="ExampleController">
252
+ Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
253
+ <table>
254
+ <tr>
255
+ <td>Filter</td>
256
+ <td>Source</td>
257
+ <td>Rendered</td>
258
+ </tr>
259
+ <tr id="linky-filter">
260
+ <td>linky filter</td>
261
+ <td>
262
+ <pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
263
+ </td>
264
+ <td>
265
+ <div ng-bind-html="snippet | linky"></div>
266
+ </td>
267
+ </tr>
268
+ <tr id="linky-target">
269
+ <td>linky target</td>
270
+ <td>
271
+ <pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
272
+ </td>
273
+ <td>
274
+ <div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
275
+ </td>
276
+ </tr>
277
+ <tr id="escaped-html">
278
+ <td>no filter</td>
279
+ <td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
280
+ <td><div ng-bind="snippet"></div></td>
281
+ </tr>
282
+ </table>
283
+ </file>
284
+ <file name="protractor.js" type="protractor">
285
+ it('should linkify the snippet with urls', function() {
286
+ expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
287
+ toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' +
288
+ 'another@somewhere.org, and one more: ftp://127.0.0.1/.');
289
+ expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);
290
+ });
291
+
292
+ it('should not linkify snippet without the linky filter', function() {
293
+ expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).
294
+ toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' +
295
+ 'another@somewhere.org, and one more: ftp://127.0.0.1/.');
296
+ expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);
297
+ });
298
+
299
+ it('should update', function() {
300
+ element(by.model('snippet')).clear();
301
+ element(by.model('snippet')).sendKeys('new http://link.');
302
+ expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
303
+ toBe('new http://link.');
304
+ expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);
305
+ expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())
306
+ .toBe('new http://link.');
307
+ });
308
+
309
+ it('should work with the target property', function() {
310
+ expect(element(by.id('linky-target')).
311
+ element(by.binding("snippetWithTarget | linky:'_blank'")).getText()).
312
+ toBe('http://angularjs.org/');
313
+ expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
314
+ });
315
+ </file>
316
+ </example>
317
+ */
318
+ b.module("ngSanitize").filter("linky",["$sanitize",function(a){var c=/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/,d=/^mailto:/;return function(f,g){function h(a){a&&n.push(e(a))}function i(a,c){n.push("<a "),b.isDefined(g)&&n.push('target="',g,'" '),n.push('href="',a.replace(/"/g,"&quot;"),'">'),h(c),n.push("</a>")}if(!f)return f;for(var j,k,l,m=f,n=[];j=m.match(c);)
319
+ // We can not end in these as they are sometimes found at the end of the sentence
320
+ k=j[0],
321
+ // if we did not match ftp/http/www/mailto then assume mailto
322
+ j[2]||j[4]||(k=(j[3]?"http://":"mailto:")+k),l=j.index,h(m.substr(0,l)),i(k,j[0].replace(d,"")),m=m.substring(l+j[0].length);return h(m),a(n.join(""))}}])}(window,window.angular);
@@ -0,0 +1,1481 @@
1
+ !function(a,b){"function"==typeof define&&define.amd?
2
+ // AMD. Register as an anonymous module unless amdModuleId is set
3
+ define("textAngular",["rangy","rangy/lib/rangy-selectionsaverestore"],function(c,d){return a["textAngular.name"]=b(c,d)}):"object"==typeof exports?
4
+ // Node. Does not work with strict CommonJS, but
5
+ // only CommonJS-like environments that support module.exports,
6
+ // like Node.
7
+ module.exports=b(require("rangy"),require("rangy/lib/rangy-selectionsaverestore")):a.textAngular=b(rangy)}(this,function(a){
8
+ // tests against the current jqLite/jquery implementation if this can be an element
9
+ function b(a){try{return 0!==angular.element(a).length}catch(a){return!1}}/*
10
+ A tool definition is an object with the following key/value parameters:
11
+ action: [function(deferred, restoreSelection)]
12
+ a function that is executed on clicking on the button - this will allways be executed using ng-click and will
13
+ overwrite any ng-click value in the display attribute.
14
+ The function is passed a deferred object ($q.defer()), if this is wanted to be used `return false;` from the action and
15
+ manually call `deferred.resolve();` elsewhere to notify the editor that the action has finished.
16
+ restoreSelection is only defined if the rangy library is included and it can be called as `restoreSelection()` to restore the users
17
+ selection in the WYSIWYG editor.
18
+ display: [string]?
19
+ Optional, an HTML element to be displayed as the button. The `scope` of the button is the tool definition object with some additional functions
20
+ If set this will cause buttontext and iconclass to be ignored
21
+ class: [string]?
22
+ Optional, if set will override the taOptions.classes.toolbarButton class.
23
+ buttontext: [string]?
24
+ if this is defined it will replace the contents of the element contained in the `display` element
25
+ iconclass: [string]?
26
+ if this is defined an icon (<i>) will be appended to the `display` element with this string as it's class
27
+ tooltiptext: [string]?
28
+ Optional, a plain text description of the action, used for the title attribute of the action button in the toolbar by default.
29
+ activestate: [function(commonElement)]?
30
+ this function is called on every caret movement, if it returns true then the class taOptions.classes.toolbarButtonActive
31
+ will be applied to the `display` element, else the class will be removed
32
+ disabled: [function()]?
33
+ if this function returns true then the tool will have the class taOptions.classes.disabled applied to it, else it will be removed
34
+ Other functions available on the scope are:
35
+ name: [string]
36
+ the name of the tool, this is the first parameter passed into taRegisterTool
37
+ isDisabled: [function()]
38
+ returns true if the tool is disabled, false if it isn't
39
+ displayActiveToolClass: [function(boolean)]
40
+ returns true if the tool is 'active' in the currently focussed toolbar
41
+ onElementSelect: [Object]
42
+ This object contains the following key/value pairs and is used to trigger the ta-element-select event
43
+ element: [String]
44
+ an element name, will only trigger the onElementSelect action if the tagName of the element matches this string
45
+ filter: [function(element)]?
46
+ an optional filter that returns a boolean, if true it will trigger the onElementSelect.
47
+ action: [function(event, element, editorScope)]
48
+ the action that should be executed if the onElementSelect function runs
49
+ */
50
+ // name and toolDefinition to add into the tools available to be added on the toolbar
51
+ function c(a,c){if(!a||""===a||e.hasOwnProperty(a))throw"textAngular Error: A unique name is required for a Tool Definition";if(c.display&&(""===c.display||!b(c.display))||!c.display&&!c.buttontext&&!c.iconclass)throw'textAngular Error: Tool Definition for "'+a+'" does not have a valid display/iconclass/buttontext value';e[a]=c}
52
+ // usage is:
53
+ // var t0 = performance.now();
54
+ // doSomething();
55
+ // var t1 = performance.now();
56
+ // console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to do something!');
57
+ //
58
+ // turn html into pure text that shows visiblity
59
+ function d(a){var b=document.createElement("DIV");b.innerHTML=a;var c=b.textContent||b.innerText||"";// zero width space
60
+ return c.replace("​",""),c=c.trim()}
61
+ // setup the global contstant functions for setting up the toolbar
62
+ // all tool definitions
63
+ var e={};angular.module("textAngularSetup",[]).constant("taRegisterTool",c).value("taTools",e).value("taOptions",{
64
+ //////////////////////////////////////////////////////////////////////////////////////
65
+ // forceTextAngularSanitize
66
+ // set false to allow the textAngular-sanitize provider to be replaced
67
+ // with angular-sanitize or a custom provider.
68
+ forceTextAngularSanitize:!0,
69
+ ///////////////////////////////////////////////////////////////////////////////////////
70
+ // keyMappings
71
+ // allow customizable keyMappings for specialized key boards or languages
72
+ //
73
+ // keyMappings provides key mappings that are attached to a given commandKeyCode.
74
+ // To modify a specific keyboard binding, simply provide function which returns true
75
+ // for the event you wish to map to.
76
+ // Or to disable a specific keyboard binding, provide a function which returns false.
77
+ // Note: 'RedoKey' and 'UndoKey' are internally bound to the redo and undo functionality.
78
+ // At present, the following commandKeyCodes are in use:
79
+ // 98, 'TabKey', 'ShiftTabKey', 105, 117, 'UndoKey', 'RedoKey'
80
+ //
81
+ // To map to an new commandKeyCode, add a new key mapping such as:
82
+ // {commandKeyCode: 'CustomKey', testForKey: function (event) {
83
+ // if (event.keyCode=57 && event.ctrlKey && !event.shiftKey && !event.altKey) return true;
84
+ // } }
85
+ // to the keyMappings. This example maps ctrl+9 to 'CustomKey'
86
+ // Then where taRegisterTool(...) is called, add a commandKeyCode: 'CustomKey' and your
87
+ // tool will be bound to ctrl+9.
88
+ //
89
+ // To disble one of the already bound commandKeyCodes such as 'RedoKey' or 'UndoKey' add:
90
+ // {commandKeyCode: 'RedoKey', testForKey: function (event) { return false; } },
91
+ // {commandKeyCode: 'UndoKey', testForKey: function (event) { return false; } },
92
+ // to disable them.
93
+ //
94
+ keyMappings:[],toolbar:[["h1","h2","h3","h4","h5","h6","p","pre","quote"],["bold","italics","underline","strikeThrough","ul","ol","redo","undo","clear"],["justifyLeft","justifyCenter","justifyRight","justifyFull","indent","outdent"],["html","insertImage","insertLink","insertVideo","wordcount","charcount"]],classes:{focussed:"focussed",toolbar:"btn-toolbar",toolbarGroup:"btn-group",toolbarButton:"btn btn-default",toolbarButtonActive:"active",disabled:"disabled",textEditor:"form-control",htmlEditor:"form-control"},defaultTagAttributes:{a:{target:""}},setup:{
95
+ // wysiwyg mode
96
+ textEditorSetup:function(a){},
97
+ // raw html
98
+ htmlEditorSetup:function(a){}},defaultFileDropHandler:/* istanbul ignore next: untestable image processing */
99
+ function(a,b){var c=new FileReader;return"image"===a.type.substring(0,5)&&(c.onload=function(){""!==c.result&&b("insertImage",c.result,!0)},c.readAsDataURL(a),!0)}}).value("taSelectableElements",["a","img"]).value("taCustomRenderers",[{
100
+ // Parse back out: '<div class="ta-insert-video" ta-insert-video src="' + urlLink + '" allowfullscreen="true" width="300" frameborder="0" height="250"></div>'
101
+ // To correct video element. For now only support youtube
102
+ selector:"img",customAttribute:"ta-insert-video",renderLogic:function(a){var b=angular.element("<iframe></iframe>"),c=a.prop("attributes");
103
+ // loop through element attributes and apply them on iframe
104
+ angular.forEach(c,function(a){b.attr(a.name,a.value)}),b.attr("src",b.attr("ta-insert-video")),a.replaceWith(b)}}]).value("taTranslations",{
105
+ // moved to sub-elements
106
+ //toggleHTML: "Toggle HTML",
107
+ //insertImage: "Please enter a image URL to insert",
108
+ //insertLink: "Please enter a URL to insert",
109
+ //insertVideo: "Please enter a youtube URL to embed",
110
+ html:{tooltip:"Toggle html / Rich Text"},
111
+ // tooltip for heading - might be worth splitting
112
+ heading:{tooltip:"Heading "},p:{tooltip:"Paragraph"},pre:{tooltip:"Preformatted text"},ul:{tooltip:"Unordered List"},ol:{tooltip:"Ordered List"},quote:{tooltip:"Quote/unquote selection or paragraph"},undo:{tooltip:"Undo"},redo:{tooltip:"Redo"},bold:{tooltip:"Bold"},italic:{tooltip:"Italic"},underline:{tooltip:"Underline"},strikeThrough:{tooltip:"Strikethrough"},justifyLeft:{tooltip:"Align text left"},justifyRight:{tooltip:"Align text right"},justifyFull:{tooltip:"Justify text"},justifyCenter:{tooltip:"Center"},indent:{tooltip:"Increase indent"},outdent:{tooltip:"Decrease indent"},clear:{tooltip:"Clear formatting"},insertImage:{dialogPrompt:"Please enter an image URL to insert",tooltip:"Insert image",hotkey:"the - possibly language dependent hotkey ... for some future implementation"},insertVideo:{tooltip:"Insert video",dialogPrompt:"Please enter a youtube URL to embed"},insertLink:{tooltip:"Insert / edit link",dialogPrompt:"Please enter a URL to insert"},editLink:{reLinkButton:{tooltip:"Relink"},unLinkButton:{tooltip:"Unlink"},targetToggle:{buttontext:"Open in New Window"}},wordcount:{tooltip:"Display words Count"},charcount:{tooltip:"Display characters Count"}}).factory("taToolFunctions",["$window","taTranslations",function(a,b){return{imgOnSelectAction:function(a,b,c){
113
+ // setup the editor toolbar
114
+ // Credit to the work at http://hackerwins.github.io/summernote/ for this editbar logic/display
115
+ var d=function(){c.updateTaBindtaTextElement(),c.hidePopover()};a.preventDefault(),c.displayElements.popover.css("width","375px");var e=c.displayElements.popoverContainer;e.empty();var f=angular.element('<div class="btn-group" style="padding-right: 6px;">'),g=angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1">100% </button>');g.on("click",function(a){a.preventDefault(),b.css({width:"100%",height:""}),d()});var h=angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1">50% </button>');h.on("click",function(a){a.preventDefault(),b.css({width:"50%",height:""}),d()});var i=angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1">25% </button>');i.on("click",function(a){a.preventDefault(),b.css({width:"25%",height:""}),d()});var j=angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1">Reset</button>');j.on("click",function(a){a.preventDefault(),b.css({width:"",height:""}),d()}),f.append(g),f.append(h),f.append(i),f.append(j),e.append(f),f=angular.element('<div class="btn-group" style="padding-right: 6px;">');var k=angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1"><i class="fa fa-align-left"></i></button>');k.on("click",function(a){a.preventDefault(),
116
+ // webkit
117
+ b.css("float","left"),
118
+ // firefox
119
+ b.css("cssFloat","left"),
120
+ // IE < 8
121
+ b.css("styleFloat","left"),d()});var l=angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1"><i class="fa fa-align-right"></i></button>');l.on("click",function(a){a.preventDefault(),
122
+ // webkit
123
+ b.css("float","right"),
124
+ // firefox
125
+ b.css("cssFloat","right"),
126
+ // IE < 8
127
+ b.css("styleFloat","right"),d()});var m=angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1"><i class="fa fa-align-justify"></i></button>');m.on("click",function(a){a.preventDefault(),
128
+ // webkit
129
+ b.css("float",""),
130
+ // firefox
131
+ b.css("cssFloat",""),
132
+ // IE < 8
133
+ b.css("styleFloat",""),d()}),f.append(k),f.append(m),f.append(l),e.append(f),f=angular.element('<div class="btn-group">');var n=angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1"><i class="fa fa-trash-o"></i></button>');n.on("click",function(a){a.preventDefault(),b.remove(),d()}),f.append(n),e.append(f),c.showPopover(b),c.showResizeOverlay(b)},aOnSelectAction:function(c,d,e){
134
+ // setup the editor toolbar
135
+ // Credit to the work at http://hackerwins.github.io/summernote/ for this editbar logic
136
+ c.preventDefault(),e.displayElements.popover.css("width","436px");var f=e.displayElements.popoverContainer;f.empty(),f.css("line-height","28px");var g=angular.element('<a href="'+d.attr("href")+'" target="_blank">'+d.attr("href")+"</a>");g.css({display:"inline-block","max-width":"200px",overflow:"hidden","text-overflow":"ellipsis","white-space":"nowrap","vertical-align":"middle"}),f.append(g);var h=angular.element('<div class="btn-group pull-right">'),i=angular.element('<button type="button" class="btn btn-default btn-sm btn-small" tabindex="-1" unselectable="on" title="'+b.editLink.reLinkButton.tooltip+'"><i class="fa fa-edit icon-edit"></i></button>');i.on("click",function(c){c.preventDefault();var f=a.prompt(b.insertLink.dialogPrompt,d.attr("href"));f&&""!==f&&"http://"!==f&&(d.attr("href",f),e.updateTaBindtaTextElement()),e.hidePopover()}),h.append(i);var j=angular.element('<button type="button" class="btn btn-default btn-sm btn-small" tabindex="-1" unselectable="on" title="'+b.editLink.unLinkButton.tooltip+'"><i class="fa fa-unlink icon-unlink"></i></button>');
137
+ // directly before this click event is fired a digest is fired off whereby the reference to $element is orphaned off
138
+ j.on("click",function(a){a.preventDefault(),d.replaceWith(d.contents()),e.updateTaBindtaTextElement(),e.hidePopover()}),h.append(j);var k=angular.element('<button type="button" class="btn btn-default btn-sm btn-small" tabindex="-1" unselectable="on">'+b.editLink.targetToggle.buttontext+"</button>");"_blank"===d.attr("target")&&k.addClass("active"),k.on("click",function(a){a.preventDefault(),d.attr("target","_blank"===d.attr("target")?"":"_blank"),k.toggleClass("active"),e.updateTaBindtaTextElement()}),h.append(k),f.append(h),e.showPopover(d)},extractYoutubeVideoId:function(a){var b=/(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/i,c=a.match(b);return c&&c[1]||null}}}]).run(["taRegisterTool","$window","taTranslations","taSelection","taToolFunctions","$sanitize","taOptions","$log",function(a,b,c,d,e,f,g,h){
139
+ // test for the version of $sanitize that is in use
140
+ // You can disable this check by setting taOptions.textAngularSanitize == false
141
+ var i={};/* istanbul ignore next, throws error */
142
+ if(f("",i),g.forceTextAngularSanitize===!0&&"taSanitize"!==i.version)throw angular.$$minErr("textAngular")("textAngularSetup","The textAngular-sanitize provider has been replaced by another -- have you included angular-sanitize by mistake?");a("html",{iconclass:"fa fa-code",tooltiptext:c.html.tooltip,action:function(){this.$editor().switchView()},activeState:function(){return this.$editor().showHtml}});
143
+ // add the Header tools
144
+ // convenience functions so that the loop works correctly
145
+ var j=function(a){return function(){return this.$editor().queryFormatBlockState(a)}},k=function(){return this.$editor().wrapSelection("formatBlock","<"+this.name.toUpperCase()+">")};angular.forEach(["h1","h2","h3","h4","h5","h6"],function(b){a(b.toLowerCase(),{buttontext:b.toUpperCase(),tooltiptext:c.heading.tooltip+b.charAt(1),action:k,activeState:j(b.toLowerCase())})}),a("p",{buttontext:"P",tooltiptext:c.p.tooltip,action:function(){return this.$editor().wrapSelection("formatBlock","<P>")},activeState:function(){return this.$editor().queryFormatBlockState("p")}}),
146
+ // key: pre -> taTranslations[key].tooltip, taTranslations[key].buttontext
147
+ a("pre",{buttontext:"pre",tooltiptext:c.pre.tooltip,action:function(){return this.$editor().wrapSelection("formatBlock","<PRE>")},activeState:function(){return this.$editor().queryFormatBlockState("pre")}}),a("ul",{iconclass:"fa fa-list-ul",tooltiptext:c.ul.tooltip,action:function(){return this.$editor().wrapSelection("insertUnorderedList",null)},activeState:function(){return this.$editor().queryCommandState("insertUnorderedList")}}),a("ol",{iconclass:"fa fa-list-ol",tooltiptext:c.ol.tooltip,action:function(){return this.$editor().wrapSelection("insertOrderedList",null)},activeState:function(){return this.$editor().queryCommandState("insertOrderedList")}}),a("quote",{iconclass:"fa fa-quote-right",tooltiptext:c.quote.tooltip,action:function(){return this.$editor().wrapSelection("formatBlock","<BLOCKQUOTE>")},activeState:function(){return this.$editor().queryFormatBlockState("blockquote")}}),a("undo",{iconclass:"fa fa-undo",tooltiptext:c.undo.tooltip,action:function(){return this.$editor().wrapSelection("undo",null)}}),a("redo",{iconclass:"fa fa-repeat",tooltiptext:c.redo.tooltip,action:function(){return this.$editor().wrapSelection("redo",null)}}),a("bold",{iconclass:"fa fa-bold",tooltiptext:c.bold.tooltip,action:function(){return this.$editor().wrapSelection("bold",null)},activeState:function(){return this.$editor().queryCommandState("bold")},commandKeyCode:98}),a("justifyLeft",{iconclass:"fa fa-align-left",tooltiptext:c.justifyLeft.tooltip,action:function(){return this.$editor().wrapSelection("justifyLeft",null)},activeState:function(a){/* istanbul ignore next: */
148
+ if(a&&"#document"===a.nodeName)return!1;var b=!1;if(a)
149
+ // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions
150
+ // so we do try catch here...
151
+ try{b="left"===a.css("text-align")||"left"===a.attr("align")||"right"!==a.css("text-align")&&"center"!==a.css("text-align")&&"justify"!==a.css("text-align")&&!this.$editor().queryCommandState("justifyRight")&&!this.$editor().queryCommandState("justifyCenter")&&!this.$editor().queryCommandState("justifyFull")}catch(a){/* istanbul ignore next: error handler */
152
+ //console.log(e);
153
+ b=!1}return b=b||this.$editor().queryCommandState("justifyLeft")}}),a("justifyRight",{iconclass:"fa fa-align-right",tooltiptext:c.justifyRight.tooltip,action:function(){return this.$editor().wrapSelection("justifyRight",null)},activeState:function(a){/* istanbul ignore next: */
154
+ if(a&&"#document"===a.nodeName)return!1;var b=!1;if(a)
155
+ // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions
156
+ // so we do try catch here...
157
+ try{b="right"===a.css("text-align")}catch(a){/* istanbul ignore next: error handler */
158
+ //console.log(e);
159
+ b=!1}return b=b||this.$editor().queryCommandState("justifyRight")}}),a("justifyFull",{iconclass:"fa fa-align-justify",tooltiptext:c.justifyFull.tooltip,action:function(){return this.$editor().wrapSelection("justifyFull",null)},activeState:function(a){var b=!1;if(a)
160
+ // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions
161
+ // so we do try catch here...
162
+ try{b="justify"===a.css("text-align")}catch(a){/* istanbul ignore next: error handler */
163
+ //console.log(e);
164
+ b=!1}return b=b||this.$editor().queryCommandState("justifyFull")}}),a("justifyCenter",{iconclass:"fa fa-align-center",tooltiptext:c.justifyCenter.tooltip,action:function(){return this.$editor().wrapSelection("justifyCenter",null)},activeState:function(a){/* istanbul ignore next: */
165
+ if(a&&"#document"===a.nodeName)return!1;var b=!1;if(a)
166
+ // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions
167
+ // so we do try catch here...
168
+ try{b="center"===a.css("text-align")}catch(a){/* istanbul ignore next: error handler */
169
+ //console.log(e);
170
+ b=!1}return b=b||this.$editor().queryCommandState("justifyCenter")}}),a("indent",{iconclass:"fa fa-indent",tooltiptext:c.indent.tooltip,action:function(){return this.$editor().wrapSelection("indent",null)},activeState:function(){return this.$editor().queryFormatBlockState("blockquote")},commandKeyCode:"TabKey"}),a("outdent",{iconclass:"fa fa-outdent",tooltiptext:c.outdent.tooltip,action:function(){return this.$editor().wrapSelection("outdent",null)},activeState:function(){return!1},commandKeyCode:"ShiftTabKey"}),a("italics",{iconclass:"fa fa-italic",tooltiptext:c.italic.tooltip,action:function(){return this.$editor().wrapSelection("italic",null)},activeState:function(){return this.$editor().queryCommandState("italic")},commandKeyCode:105}),a("underline",{iconclass:"fa fa-underline",tooltiptext:c.underline.tooltip,action:function(){return this.$editor().wrapSelection("underline",null)},activeState:function(){return this.$editor().queryCommandState("underline")},commandKeyCode:117}),a("strikeThrough",{iconclass:"fa fa-strikethrough",tooltiptext:c.strikeThrough.tooltip,action:function(){return this.$editor().wrapSelection("strikeThrough",null)},activeState:function(){return document.queryCommandState("strikeThrough")}}),a("clear",{iconclass:"fa fa-ban",tooltiptext:c.clear.tooltip,action:function(a,b){var c;this.$editor().wrapSelection("removeFormat",null);var e=angular.element(d.getSelectionElement());c=d.getAllSelectedElements();
171
+ //$log.log('selectedElements:', selectedElements);
172
+ // remove lists
173
+ var f=function(a,b){a=angular.element(a);var c=b;return b||(c=a),angular.forEach(a.children(),function(a){if("ul"===a.tagName.toLowerCase()||"ol"===a.tagName.toLowerCase())c=f(a,c);else{var b=angular.element("<p></p>");b.html(angular.element(a).html()),c.after(b),c=b}}),a.remove(),c};angular.forEach(c,function(a){"ul"!==a.nodeName.toLowerCase()&&"ol"!==a.nodeName.toLowerCase()||
174
+ //console.log('removeListElements', element);
175
+ f(a)}),angular.forEach(e.find("ul"),f),angular.forEach(e.find("ol"),f);
176
+ // clear out all class attributes. These do not seem to be cleared via removeFormat
177
+ var g=this.$editor(),h=function(a){a=angular.element(a),/* istanbul ignore next: this is not triggered in tests any longer since we now never select the whole displayELement */
178
+ a[0]!==g.displayElements.text[0]&&a.removeAttr("class"),angular.forEach(a.children(),h)};angular.forEach(e,h),
179
+ // check if in list. If not in list then use formatBlock option
180
+ e[0]&&"li"!==e[0].tagName.toLowerCase()&&"ol"!==e[0].tagName.toLowerCase()&&"ul"!==e[0].tagName.toLowerCase()&&"true"!==e[0].getAttribute("contenteditable")&&this.$editor().wrapSelection("formatBlock","default"),b()}});/* jshint -W099 */
181
+ /****************************
182
+ // we don't use this code - since the previous way CLEAR is expected to work does not clear partially selected <li>
183
+
184
+ var removeListElement = function(listE){
185
+ console.log(listE);
186
+ var _list = listE.parentNode.childNodes;
187
+ console.log('_list', _list);
188
+ var _preLis = [], _postLis = [], _found = false;
189
+ for (i = 0; i < _list.length; i++) {
190
+ if (_list[i] === listE) {
191
+ _found = true;
192
+ } else if (!_found) _preLis.push(_list[i]);
193
+ else _postLis.push(_list[i]);
194
+ }
195
+ var _parent = angular.element(listE.parentNode);
196
+ var newElem = angular.element('<p></p>');
197
+ newElem.html(angular.element(listE).html());
198
+ if (_preLis.length === 0 || _postLis.length === 0) {
199
+ if (_postLis.length === 0) _parent.after(newElem);
200
+ else _parent[0].parentNode.insertBefore(newElem[0], _parent[0]);
201
+
202
+ if (_preLis.length === 0 && _postLis.length === 0) _parent.remove();
203
+ else angular.element(listE).remove();
204
+ } else {
205
+ var _firstList = angular.element('<' + _parent[0].tagName + '></' + _parent[0].tagName + '>');
206
+ var _secondList = angular.element('<' + _parent[0].tagName + '></' + _parent[0].tagName + '>');
207
+ for (i = 0; i < _preLis.length; i++) _firstList.append(angular.element(_preLis[i]));
208
+ for (i = 0; i < _postLis.length; i++) _secondList.append(angular.element(_postLis[i]));
209
+ _parent.after(_secondList);
210
+ _parent.after(newElem);
211
+ _parent.after(_firstList);
212
+ _parent.remove();
213
+ }
214
+ taSelection.setSelectionToElementEnd(newElem[0]);
215
+ };
216
+
217
+ elementsSeen = [];
218
+ if (selectedElements.length !==0) console.log(selectedElements);
219
+ angular.forEach(selectedElements, function (element) {
220
+ if (elementsSeen.indexOf(element) !== -1 || elementsSeen.indexOf(element.parentElement) !== -1) {
221
+ return;
222
+ }
223
+ elementsSeen.push(element);
224
+ if (element.nodeName.toLowerCase() === 'li') {
225
+ console.log('removeListElement', element);
226
+ removeListElement(element);
227
+ }
228
+ else if (element.parentElement && element.parentElement.nodeName.toLowerCase() === 'li') {
229
+ console.log('removeListElement', element.parentElement);
230
+ elementsSeen.push(element.parentElement);
231
+ removeListElement(element.parentElement);
232
+ }
233
+ });
234
+ **********************/
235
+ /**********************
236
+ if(possibleNodes[0].tagName.toLowerCase() === 'li'){
237
+ var _list = possibleNodes[0].parentNode.childNodes;
238
+ var _preLis = [], _postLis = [], _found = false;
239
+ for(i = 0; i < _list.length; i++){
240
+ if(_list[i] === possibleNodes[0]){
241
+ _found = true;
242
+ }else if(!_found) _preLis.push(_list[i]);
243
+ else _postLis.push(_list[i]);
244
+ }
245
+ var _parent = angular.element(possibleNodes[0].parentNode);
246
+ var newElem = angular.element('<p></p>');
247
+ newElem.html(angular.element(possibleNodes[0]).html());
248
+ if(_preLis.length === 0 || _postLis.length === 0){
249
+ if(_postLis.length === 0) _parent.after(newElem);
250
+ else _parent[0].parentNode.insertBefore(newElem[0], _parent[0]);
251
+
252
+ if(_preLis.length === 0 && _postLis.length === 0) _parent.remove();
253
+ else angular.element(possibleNodes[0]).remove();
254
+ }else{
255
+ var _firstList = angular.element('<'+_parent[0].tagName+'></'+_parent[0].tagName+'>');
256
+ var _secondList = angular.element('<'+_parent[0].tagName+'></'+_parent[0].tagName+'>');
257
+ for(i = 0; i < _preLis.length; i++) _firstList.append(angular.element(_preLis[i]));
258
+ for(i = 0; i < _postLis.length; i++) _secondList.append(angular.element(_postLis[i]));
259
+ _parent.after(_secondList);
260
+ _parent.after(newElem);
261
+ _parent.after(_firstList);
262
+ _parent.remove();
263
+ }
264
+ taSelection.setSelectionToElementEnd(newElem[0]);
265
+ }
266
+ *******************/
267
+ /* istanbul ignore next: if it's javascript don't worry - though probably should show some kind of error message */
268
+ var l=function(a){return a.toLowerCase().indexOf("javascript")!==-1};a("insertImage",{iconclass:"fa fa-picture-o",tooltiptext:c.insertImage.tooltip,action:function(){var a;if(a=b.prompt(c.insertImage.dialogPrompt,"http://"),a&&""!==a&&"http://"!==a&&!l(a)){d.getSelectionElement().tagName&&"a"===d.getSelectionElement().tagName.toLowerCase()&&
269
+ // due to differences in implementation between FireFox and Chrome, we must move the
270
+ // insertion point past the <a> element, otherwise FireFox inserts inside the <a>
271
+ // With this change, both FireFox and Chrome behave the same way!
272
+ d.setSelectionAfterElement(d.getSelectionElement());
273
+ // In the past we used the simple statement:
274
+ //return this.$editor().wrapSelection('insertImage', imageLink, true);
275
+ //
276
+ // However on Firefox only, when the content is empty this is a problem
277
+ // See Issue #1201
278
+ // Investigation reveals that Firefox only inserts a <p> only!!!!
279
+ // So now we use insertHTML here and all is fine.
280
+ // NOTE: this is what 'insertImage' is supposed to do anyway!
281
+ var e='<img src="'+a+'">';return this.$editor().wrapSelection("insertHTML",e,!0)}},onElementSelect:{element:"img",action:e.imgOnSelectAction}}),a("insertVideo",{iconclass:"fa fa-youtube-play",tooltiptext:c.insertVideo.tooltip,action:function(){var a;
282
+ // block javascript here
283
+ /* istanbul ignore else: if it's javascript don't worry - though probably should show some kind of error message */
284
+ if(a=b.prompt(c.insertVideo.dialogPrompt,"https://"),!l(a)&&a&&""!==a&&"https://"!==a&&(videoId=e.extractYoutubeVideoId(a),videoId)){
285
+ // create the embed link
286
+ var f="https://www.youtube.com/embed/"+videoId,g='<img class="ta-insert-video" src="https://img.youtube.com/vi/'+videoId+'/hqdefault.jpg" ta-insert-video="'+f+'" contenteditable="false" allowfullscreen="true" frameborder="0" />';
287
+ // insert
288
+ /* istanbul ignore next: don't know how to test this... since it needs a dialogPrompt */
289
+ // due to differences in implementation between FireFox and Chrome, we must move the
290
+ // insertion point past the <a> element, otherwise FireFox inserts inside the <a>
291
+ // With this change, both FireFox and Chrome behave the same way!
292
+ return d.getSelectionElement().tagName&&"a"===d.getSelectionElement().tagName.toLowerCase()&&d.setSelectionAfterElement(d.getSelectionElement()),this.$editor().wrapSelection("insertHTML",g,!0)}},onElementSelect:{element:"img",onlyWithAttrs:["ta-insert-video"],action:e.imgOnSelectAction}}),a("insertLink",{tooltiptext:c.insertLink.tooltip,iconclass:"fa fa-link",action:function(){var a;if(
293
+ // if this link has already been set, we need to just edit the existing link
294
+ /* istanbul ignore if: we do not test this */
295
+ a=d.getSelectionElement().tagName&&"a"===d.getSelectionElement().tagName.toLowerCase()?b.prompt(c.insertLink.dialogPrompt,d.getSelectionElement().href):b.prompt(c.insertLink.dialogPrompt,"http://"),a&&""!==a&&"http://"!==a&&!l(a))return this.$editor().wrapSelection("createLink",a,!0)},activeState:function(a){return!!a&&"A"===a[0].tagName},onElementSelect:{element:"a",action:e.aOnSelectAction}}),a("wordcount",{display:'<div id="toolbarWC" style="display:block; min-width:100px;">Words: <span ng-bind="wordcount"></span></div>',disabled:!0,wordcount:0,activeState:function(){// this fires on keyup
296
+ var a=this.$editor().displayElements.text,b=a[0].innerHTML||"",c=0;/* istanbul ignore if: will default to '' when undefined */
297
+ //Set current scope
298
+ //Set editor scope
299
+ return""!==b.replace(/\s*<[^>]*?>\s*/g,"")&&""!==b.trim()&&(c=b.replace(/<\/?(b|i|em|strong|span|u|strikethrough|a|img|small|sub|sup|label)( [^>*?])?>/gi,"").replace(/(<[^>]*?>\s*<[^>]*?>)/gi," ").replace(/(<[^>]*?>)/gi,"").replace(/\s+/gi," ").match(/\S+/g).length),this.wordcount=c,this.$editor().wordcount=c,!1}}),a("charcount",{display:'<div id="toolbarCC" style="display:block; min-width:120px;">Characters: <span ng-bind="charcount"></span></div>',disabled:!0,charcount:0,activeState:function(){// this fires on keyup
300
+ var a=this.$editor().displayElements.text,b=a[0].innerText||a[0].textContent,c=b.replace(/(\r\n|\n|\r)/gm,"").replace(/^\s+/g," ").replace(/\s+$/g," ").length;
301
+ //Set current scope
302
+ //Set editor scope
303
+ return this.charcount=c,this.$editor().charcount=c,!1}})}]);// NOTE: textAngularVersion must match the Gruntfile.js 'setVersion' task.... and have format v/d+./d+./d+
304
+ var f="v1.5.16",g={ie:function(){for(var a,b=3,c=document.createElement("div"),d=c.getElementsByTagName("i");c.innerHTML="<!--[if gt IE "+ ++b+"]><i></i><![endif]-->",d[0];);return b>4?b:a}(),webkit:/AppleWebKit\/([\d.]+)/i.test(navigator.userAgent),isFirefox:navigator.userAgent.toLowerCase().indexOf("firefox")>-1},h=h||{};/* istanbul ignore next: untestable browser check */
305
+ h.now=function(){return h.now||h.mozNow||h.msNow||h.oNow||h.webkitNow||function(){return(new Date).getTime()}}();
306
+ // Global to textAngular REGEXP vars for block and list elements.
307
+ var i=/^(address|article|aside|audio|blockquote|canvas|center|dd|div|dl|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|noscript|ol|output|p|pre|section|table|tfoot|ul|video)$/i,j=/^(ul|li|ol)$/i,k=/^(#text|span|address|article|aside|audio|blockquote|canvas|center|dd|div|dl|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|noscript|ol|output|p|pre|section|table|tfoot|ul|video|li)$/i;
308
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Compatibility
309
+ /* istanbul ignore next: trim shim for older browsers */
310
+ String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")});/*
311
+ Custom stylesheet for the placeholders rules.
312
+ Credit to: http://davidwalsh.name/add-rules-stylesheets
313
+ */
314
+ var l,m,n,o,p,q;/* istanbul ignore else: IE <8 test*/
315
+ if(g.ie>8||void 0===g.ie){/* istanbul ignore next: preference for stylesheet loaded externally */
316
+ for(var r=document.styleSheets,s=0;s<r.length;s++)if((0===r[s].media.length||r[s].media.mediaText.match(/(all|screen)/gi))&&r[s].href&&r[s].href.match(/textangular\.(min\.|)css/gi)){l=r[s];break}/* istanbul ignore next: preference for stylesheet loaded externally */
317
+ l||(
318
+ // this sheet is used for the placeholders later on.
319
+ l=function(){
320
+ // Create the <style> tag
321
+ var a=document.createElement("style");/* istanbul ignore else : WebKit hack :( */
322
+ // Add the <style> element to the page, add as first so the styles can be overridden by custom stylesheets
323
+ return g.webkit&&a.appendChild(document.createTextNode("")),document.getElementsByTagName("head")[0].appendChild(a),a.sheet}()),
324
+ // use as: addCSSRule("header", "float: left");
325
+ m=function(a,b){return o(l,a,b)},o=function(a,b,c){var d,e;
326
+ // return the inserted stylesheet rule
327
+ // This order is important as IE 11 has both cssRules and rules but they have different lengths - cssRules is correct, rules gives an error in IE 11
328
+ /* istanbul ignore next: browser catches */
329
+ /* istanbul ignore else: untestable IE option */
330
+ /* istanbul ignore next: browser catches */
331
+ return a.cssRules?d=Math.max(a.cssRules.length-1,0):a.rules&&(d=Math.max(a.rules.length-1,0)),a.insertRule?a.insertRule(b+"{"+c+"}",d):a.addRule(b,c,d),l.rules?e=l.rules[d]:l.cssRules&&(e=l.cssRules[d]),e},q=function(a,b){var c,d;for(c=0;c<b.length;c++)/* istanbul ignore else: check for correct rule */
332
+ if(b[c].cssText===a.cssText){d=c;break}return d},n=function(a){p(l,a)},/* istanbul ignore next: tests are browser specific */
333
+ p=function(a,b){var c=a.cssRules||a.rules;if(c&&0!==c.length){var d=q(b,c);a.removeRule?a.removeRule(d):a.deleteRule(d)}}}angular.module("textAngular.factories",[]).factory("taBrowserTag",[function(){return function(a){/* istanbul ignore next: ie specific test */
334
+ /* istanbul ignore next: ie specific test */
335
+ return a?""===a?void 0===g.ie?"div":g.ie<=8?"P":"p":g.ie<=8?a.toUpperCase():a:g.ie<=8?"P":"p"}}]).factory("taApplyCustomRenderers",["taCustomRenderers","taDOM",function(a,b){return function(c){var d=angular.element("<div></div>");return d[0].innerHTML=c,angular.forEach(a,function(a){var c=[];
336
+ // get elements based on what is defined. If both defined do secondary filter in the forEach after using selector string
337
+ a.selector&&""!==a.selector?c=d.find(a.selector):a.customAttribute&&""!==a.customAttribute&&(c=b.getByAttribute(d,a.customAttribute)),
338
+ // process elements if any found
339
+ angular.forEach(c,function(b){b=angular.element(b),a.selector&&""!==a.selector&&a.customAttribute&&""!==a.customAttribute?void 0!==b.attr(a.customAttribute)&&a.renderLogic(b):a.renderLogic(b)})}),d[0].innerHTML}}]).factory("taFixChrome",function(){
340
+ // get whaterever rubbish is inserted in chrome
341
+ // should be passed an html string, returns an html string
342
+ var a=function(a,b){if(!a||!angular.isString(a)||a.length<=0)return a;
343
+ // remove all the Apple-converted-space spans and replace with the content of the span
344
+ //console.log('before:', html);
345
+ /* istanbul ignore next: apple-contereted-space span match */
346
+ for(
347
+ // grab all elements with a style attibute
348
+ // a betterSpanMatch matches only a style=... with matching quotes
349
+ // this captures the whole:
350
+ // 'style="background-color: rgb(255, 255, 255);"'
351
+ var c,d,e,f=/style\s?=\s?(["'])(?:(?=(\\?))\2.)*?\1/gi,g=/<span class="Apple-converted-space">([^<]+)<\/span>/gi,h="",i=0;c=g.exec(a);)e=c[1],e=e.replace(/&nbsp;/gi," "),h+=a.substring(i,c.index)+e,i=c.index+c[0].length;
352
+ /////////////////////////////////////////////////////////////
353
+ //
354
+ // Allow control of this modification
355
+ // taKeepStyles: False - removes these modification
356
+ //
357
+ // taFixChrome removes the following styles:
358
+ // font-family: inherit;
359
+ // line-height: <number>
360
+ // color: inherit;
361
+ // color: rgb( <rgb-component>#{3} )
362
+ // background-color: rgb( <rgb-component>#{3} )
363
+ //
364
+ /////////////////////////////////////////////////////////////
365
+ if(/* istanbul ignore next: apple-contereted-space span has matched */
366
+ i&&(
367
+ // modified....
368
+ h+=a.substring(i),a=h,h="",i=0),!b){for(;c=f.exec(a);)h+=a.substring(i,c.index-1),d=c[0],
369
+ // test for chrome inserted junk
370
+ c=/font-family: inherit;|line-height: 1.[0-9]{3,12};|color: inherit; line-height: 1.1;|color: rgb\(\d{1,3}, \d{1,3}, \d{1,3}\);|background-color: rgb\(\d{1,3}, \d{1,3}, \d{1,3}\);/gi.exec(d),c?(d=d.replace(/( |)font-family: inherit;|( |)line-height: 1.[0-9]{3,12};|( |)color: inherit;|( |)color: rgb\(\d{1,3}, \d{1,3}, \d{1,3}\);|( |)background-color: rgb\(\d{1,3}, \d{1,3}, \d{1,3}\);/gi,""),
371
+ //console.log(styleVal, styleVal.length);
372
+ d.length>8&&(h+=" "+d)):h+=" "+d,i=f.lastIndex;h+=a.substring(i)}
373
+ //console.log('final:', finalHtml);
374
+ // only replace when something has changed, else we get focus problems on inserting lists
375
+ if(i>0){
376
+ // replace all empty strings
377
+ var j=h.replace(/<span\s?>(.*?)<\/span>(<br(\/|)>|)/gi,"$1");return j}return a};return a}).factory("taSanitize",["$sanitize",function(a){function b(a,b){for(var c,d=0,e=0,f=/<[^>]*>/gi;c=f.exec(a);)if(e=c.index,"/"===c[0].substr(1,1)){if(0===d)break;d--}else d++;
378
+ // get the start tags reversed - this is safe as we construct the strings with no content except the tags
379
+ return b+a.substring(0,e)+angular.element(b)[0].outerHTML.substring(b.length)+a.substring(e)}function c(a){if(!a||!angular.isString(a)||a.length<=0)return a;for(var d,f,g,h,i,k,l=/<([^>\/]+?)style=("([^"]+)"|'([^']+)')([^>]*)>/gi,m="",n="",o=0;f=l.exec(a);){
380
+ // one of the quoted values ' or "
381
+ /* istanbul ignore next: quotations match */
382
+ h=f[3]||f[4];var p=new RegExp(j,"i");
383
+ // test for style values to change
384
+ if(angular.isString(h)&&p.test(h)){
385
+ // remove build tag list
386
+ i="";
387
+ // find relevand tags and build a string of them
388
+ for(
389
+ // init regex here for exec
390
+ var q=new RegExp(j,"ig");g=q.exec(h);)for(d=0;d<e.length;d++)g[2*d+2]&&(i+="<"+e[d].tag+">");
391
+ // recursively find more legacy styles in html before this tag and after the previous match (if any)
392
+ k=c(a.substring(o,f.index)),
393
+ // build up html
394
+ n+=m.length>0?b(k,m):k,
395
+ // grab the style val without the transformed values
396
+ h=h.replace(new RegExp(j,"ig"),""),
397
+ // build the html tag
398
+ n+="<"+f[1].trim(),h.length>0&&(n+=' style="'+h+'"'),n+=f[5]+">",
399
+ // update the start index to after this tag
400
+ o=f.index+f[0].length,m=i}}return n+=m.length>0?b(a.substring(o),m):a.substring(o)}function d(a){if(!a||!angular.isString(a)||a.length<=0)return a;
401
+ // match all attr tags
402
+ for(
403
+ // replace all align='...' tags with text-align attributes
404
+ var b,c=/<([^>\/]+?)align=("([^"]+)"|'([^']+)')([^>]*)>/gi,d="",e=0;b=c.exec(a);){
405
+ // add all html before this tag
406
+ d+=a.substring(e,b.index),
407
+ // record last index after this tag
408
+ e=b.index+b[0].length;
409
+ // construct tag without the align attribute
410
+ var f="<"+b[1]+b[5];
411
+ // add the style attribute
412
+ /style=("([^"]+)"|'([^']+)')/gi.test(f)?/* istanbul ignore next: quotations match */
413
+ f=f.replace(/style=("([^"]+)"|'([^']+)')/i,'style="$2$3 text-align:'+(b[3]||b[4])+';"'):/* istanbul ignore next: quotations match */
414
+ f+=' style="text-align:'+(b[3]||b[4])+';"',f+=">",
415
+ // add to html
416
+ d+=f}
417
+ // return with remaining html
418
+ return d+a.substring(e)}for(var e=[{property:"font-weight",values:["bold"],tag:"b"},{property:"font-style",values:["italic"],tag:"i"}],f=[],g=0;g<e.length;g++){for(var h="("+e[g].property+":\\s*(",i=0;i<e[g].values.length;i++)/* istanbul ignore next: not needed to be tested yet */
419
+ i>0&&(h+="|"),h+=e[g].values[i];h+=");)",f.push(h)}var j="("+f.join("|")+")",k=new RegExp(/<span id="selectionBoundary_\d+_\d+" class="rangySelectionBoundary">[^<>]+?<\/span>/gi),l=new RegExp(/<span class="rangySelectionBoundary" id="selectionBoundary_\d+_\d+">[^<>]+?<\/span>/gi),m=new RegExp(/<span id="selectionBoundary_\d+_\d+" class="rangySelectionBoundary">[^<>]+?<\/span>/gi);return function(b,e,f){
420
+ // unsafe html should NEVER built into a DOM object via angular.element. This allows XSS to be inserted and run.
421
+ if(!f)try{b=c(b)}catch(a){}
422
+ // we had an issue in the past, where we dumped a whole bunch of <span>'s into the content...
423
+ // so we remove them here
424
+ // IN A FUTURE release this can be removed after all have updated through release 1.5.9
425
+ if(
426
+ // unsafe and oldsafe should be valid HTML strings
427
+ // any exceptions (lets say, color for example) should be made here but with great care
428
+ // setup unsafe element for modification
429
+ b=d(b))try{b=b.replace(k,""),b=b.replace(l,""),b=b.replace(k,""),b=b.replace(m,"")}catch(a){}var g;try{g=a(b),
430
+ // do this afterwards, then the $sanitizer should still throw for bad markup
431
+ f&&(g=b)}catch(a){g=e||""}
432
+ // Do processing for <pre> tags, removing tabs and return carriages outside of them
433
+ var h,i=g.match(/(<pre[^>]*>.*?<\/pre[^>]*>)/gi),j=g.replace(/(&#(9|10);)*/gi,""),n=/<pre[^>]*>.*?<\/pre[^>]*>/gi,o=0,p=0;for(g="";null!==(h=n.exec(j))&&o<i.length;)g+=j.substring(p,h.index)+i[o],p=h.index+h[0].length,o++;return g+j.substring(p)}}]).factory("taToolExecuteAction",["$q","$log",function(a,b){
434
+ // this must be called on a toolScope or instance
435
+ return function(c){void 0!==c&&(this.$editor=function(){return c});var d,e=a.defer(),f=e.promise,g=this.$editor();try{d=this.action(e,g.startAction()),
436
+ // We set the .finally callback here to make sure it doesn't get executed before any other .then callback.
437
+ f.finally(function(){g.endAction.call(g)})}catch(a){b.error(a)}(d||void 0===d)&&
438
+ // if true or undefined is returned then the action has finished. Otherwise the deferred action will be resolved manually.
439
+ e.resolve()}}]),angular.module("textAngular.DOM",["textAngular.factories"]).factory("taExecCommand",["taSelection","taBrowserTag","$document",function(b,c,d){var e=function(a,c){var d,e,f=a.find("li");for(e=f.length-1;e>=0;e--)d=angular.element("<"+c+">"+f[e].innerHTML+"</"+c+">"),a.after(d);a.remove(),b.setSelectionToElementEnd(d[0])},f=function(a,d,e,f,g){var h,i,j,k,l,m=a.find("li");for(i=0;i<m.length;i++)if(m[i].outerHTML===d[0].outerHTML){
440
+ // found it...
441
+ l=i,i>0&&(j=m[i-1]),i+1<m.length&&(k=m[i+1]);break}
442
+ //console.log('listElementToSelfTag', list, listElement, selfTag, bDefault, priorElement, nextElement);
443
+ // un-list the listElement
444
+ var n="";
445
+ //console.log('$target', $target[0]);
446
+ if(f?n+="<"+g+">"+d[0].innerHTML+"</"+g+">":(n+="<"+c(e)+">",n+="<li>"+d[0].innerHTML+"</li>",n+="</"+c(e)+">"),h=angular.element(n),!j)
447
+ // this is the first the list, so we just remove it...
448
+ return d.remove(),a.after(angular.element(a[0].outerHTML)),a.after(h),a.remove(),void b.setSelectionToElementEnd(h[0]);if(k){var o=(a.parent(),""),p=a[0].nodeName.toLowerCase();for(o+="<"+p+">",i=0;i<l;i++)o+="<li>"+m[i].innerHTML+"</li>";o+="</"+p+">";var q="";for(q+="<"+p+">",i=l+1;i<m.length;i++)q+="<li>"+m[i].innerHTML+"</li>";q+="</"+p+">",
449
+ //console.log(html1, $target[0], html2);
450
+ a.after(angular.element(q)),a.after(h),a.after(angular.element(o)),a.remove(),
451
+ //console.log('parent ******XXX*****', p[0]);
452
+ b.setSelectionToElementEnd(h[0])}else
453
+ // this is the last in the list, so we just remove it..
454
+ d.remove(),a.after(h),b.setSelectionToElementEnd(h[0])},g=function(a,d,e,f,g){var h,i,j,k,l,m=a.find("li"),n=[];for(i=0;i<m.length;i++)for(j=0;j<d.length;j++)m[i].isEqualNode(d[j])&&(
455
+ // found it...
456
+ n[j]=i);n[0]>0&&(k=m[n[0]-1]),n[d.length-1]+1<m.length&&(l=m[n[d.length-1]+1]);
457
+ //console.log('listElementsToSelfTag', list, listElements, selfTag, bDefault, !priorElement, !afterElement, foundIndexes[listElements.length-1], children.length);
458
+ // un-list the listElements
459
+ var o="";if(f)for(j=0;j<d.length;j++)o+="<"+g+">"+d[j].innerHTML+"</"+g+">",d[j].remove();else{for(o+="<"+c(e)+">",j=0;j<d.length;j++)o+=d[j].outerHTML,d[j].remove();o+="</"+c(e)+">"}if(h=angular.element(o),!k)
460
+ // this is the first the list, so we just remove it...
461
+ return a.after(angular.element(a[0].outerHTML)),a.after(h),a.remove(),void b.setSelectionToElementEnd(h[0]);if(!l)
462
+ // this is the last in the list, so we just remove it..
463
+ return a.after(h),void b.setSelectionToElementEnd(h[0]);
464
+ // okay it was some where in the middle... so we need to break apart the list...
465
+ var p="",q=a[0].nodeName.toLowerCase();for(p+="<"+q+">",i=0;i<n[0];i++)p+="<li>"+m[i].innerHTML+"</li>";p+="</"+q+">";var r="";for(r+="<"+q+">",i=n[d.length-1]+1;i<m.length;i++)r+="<li>"+m[i].innerHTML+"</li>";r+="</"+q+">",a.after(angular.element(r)),a.after(h),a.after(angular.element(p)),a.remove(),
466
+ //console.log('parent ******YYY*****', list.parent()[0]);
467
+ b.setSelectionToElementEnd(h[0])},h=function(a){/(<br(|\/)>)$/i.test(a.innerHTML.trim())?b.setSelectionBeforeElement(angular.element(a).find("br")[0]):b.setSelectionToElementEnd(a)},k=function(a,b){var c=angular.element("<"+b+">"+a[0].innerHTML+"</"+b+">");a.after(c),a.remove(),h(c.find("li")[0])},l=function(a,b,d){for(var e="",f=0;f<a.length;f++)e+="<"+c("li")+">"+a[f].innerHTML+"</"+c("li")+">";var g=angular.element("<"+d+">"+e+"</"+d+">");b.after(g),b.remove(),h(g.find("li")[0])},m=function(a,b){for(var c=0;c<a.childNodes.length;c++){var d=a.childNodes[c];/* istanbul ignore next - more complex testing*/
468
+ d.tagName&&d.tagName.match(i)&&m(d,b)}/* istanbul ignore next - very rare condition that we do not test*/
469
+ if(null===a.parentNode)
470
+ // nothing left to do..
471
+ return a;/* istanbul ignore next - not sure have to test this */
472
+ if("<br>"===b)return a;var e=angular.element(b);return e[0].innerHTML=a.innerHTML,a.parentNode.insertBefore(e[0],a),a.parentNode.removeChild(a),e};return function(h,n){
473
+ // NOTE: here we are dealing with the html directly from the browser and not the html the user sees.
474
+ // IF you want to modify the html the user sees, do it when the user does a switchView
475
+ return h=c(h),function(o,p,q,r){var s,t,u,v,w,x,y,z,A=angular.element("<"+h+">");try{b.getSelection&&(z=b.getSelection()),y=b.getSelectionElement();
476
+ // special checks and fixes when we are selecting the whole container
477
+ var B,C;/* istanbul ignore next */
478
+ void 0!==y.tagName&&("div"===y.tagName.toLowerCase()&&/taTextElement.+/.test(y.id)&&z&&z.start&&1===z.start.offset&&1===z.end.offset?(
479
+ // opps we are actually selecting the whole container!
480
+ //console.log('selecting whole container!');
481
+ B=y.innerHTML,/<br>/i.test(B)&&(
482
+ // Firefox adds <br>'s and so we remove the <br>
483
+ B=B.replace(/<br>/i,"&#8203;")),/<br\/>/i.test(B)&&(
484
+ // Firefox adds <br/>'s and so we remove the <br/>
485
+ B=B.replace(/<br\/>/i,"&#8203;")),
486
+ // remove stacked up <span>'s
487
+ /<span>(<span>)+/i.test(B)&&(B=__.replace(/<span>(<span>)+/i,"<span>")),
488
+ // remove stacked up </span>'s
489
+ /<\/span>(<\/span>)+/i.test(B)&&(B=__.replace(/<\/span>(<\/span>)+/i,"</span>")),/<span><\/span>/i.test(B)&&(
490
+ // if we end up with a <span></span> here we remove it...
491
+ B=B.replace(/<span><\/span>/i,"")),
492
+ //console.log('inner whole container', selectedElement.childNodes);
493
+ C="<div>"+B+"</div>",y.innerHTML=C,b.setSelectionToElementEnd(y.childNodes[0]),y=b.getSelectionElement()):"span"===y.tagName.toLowerCase()&&z&&z.start&&1===z.start.offset&&1===z.end.offset?(
494
+ // just a span -- this is a problem...
495
+ //console.log('selecting span!');
496
+ B=y.innerHTML,/<br>/i.test(B)&&(
497
+ // Firefox adds <br>'s and so we remove the <br>
498
+ B=B.replace(/<br>/i,"&#8203;")),/<br\/>/i.test(B)&&(
499
+ // Firefox adds <br/>'s and so we remove the <br/>
500
+ B=B.replace(/<br\/>/i,"&#8203;")),
501
+ // remove stacked up <span>'s
502
+ /<span>(<span>)+/i.test(B)&&(B=__.replace(/<span>(<span>)+/i,"<span>")),
503
+ // remove stacked up </span>'s
504
+ /<\/span>(<\/span>)+/i.test(B)&&(B=__.replace(/<\/span>(<\/span>)+/i,"</span>")),/<span><\/span>/i.test(B)&&(
505
+ // if we end up with a <span></span> here we remove it...
506
+ B=B.replace(/<span><\/span>/i,"")),
507
+ //console.log('inner span', selectedElement.childNodes);
508
+ // we wrap this in a <div> because otherwise the browser get confused when we attempt to select the whole node
509
+ // and the focus is not set correctly no matter what we do
510
+ C="<div>"+B+"</div>",y.innerHTML=C,b.setSelectionToElementEnd(y.childNodes[0]),y=b.getSelectionElement()):"p"===y.tagName.toLowerCase()&&z&&z.start&&1===z.start.offset&&1===z.end.offset?(
511
+ //console.log('p special');
512
+ // we need to remove the </br> that firefox adds!
513
+ B=y.innerHTML,/<br>/i.test(B)&&(
514
+ // Firefox adds <br>'s and so we remove the <br>
515
+ B=B.replace(/<br>/i,"&#8203;"),// no space-space
516
+ y.innerHTML=B)):"li"===y.tagName.toLowerCase()&&z&&z.start&&z.start.offset===z.end.offset&&(
517
+ // we need to remove the </br> that firefox adds!
518
+ B=y.innerHTML,/<br>/i.test(B)&&(
519
+ // Firefox adds <br>'s and so we remove the <br>
520
+ B=B.replace(/<br>/i,""),// nothing
521
+ y.innerHTML=B)))}catch(a){}
522
+ //console.log('************** selectedElement:', selectedElement);
523
+ /* istanbul ignore if: */
524
+ if(y){var D=angular.element(y),E=y&&y.tagName&&y.tagName.toLowerCase()||/* istanbul ignore next: */
525
+ "";if("insertorderedlist"===o.toLowerCase()||"insertunorderedlist"===o.toLowerCase()){var F=c("insertorderedlist"===o.toLowerCase()?"ol":"ul"),G=b.getOnlySelectedElements();
526
+ //console.log('PPPPPPPPPPPPP', tagName, selfTag, selectedElements, tagName.match(BLOCKELEMENTS), $selected.hasClass('ta-bind'), $selected.parent()[0].tagName);
527
+ if(G.length>1&&("ol"===E||"ul"===E))return g(D,G,F,F===E,h);if(E===F)
528
+ // if all selected then we should remove the list
529
+ // grab all li elements and convert to taDefaultWrap tags
530
+ //console.log('tagName===selfTag');
531
+ // if all selected then we should remove the list
532
+ // grab all li elements and convert to taDefaultWrap tags
533
+ //console.log('tagName===selfTag');
534
+ return D[0].childNodes.length!==G.length&&1===G.length?(D=angular.element(G[0]),f(D.parent(),D,F,!0,h)):e(D,h);if("li"===E&&D.parent()[0].tagName.toLowerCase()===F&&1===D.parent().children().length)
535
+ // catch for the previous statement if only one li exists
536
+ return e(D.parent(),h);if("li"===E&&D.parent()[0].tagName.toLowerCase()!==F&&1===D.parent().children().length)
537
+ // catch for the previous statement if only one li exists
538
+ return k(D.parent(),F);if(E.match(i)&&!D.hasClass("ta-bind")){
539
+ // if it's one of those block elements we have to change the contents
540
+ // if it's a ol/ul we are changing from one to the other
541
+ if(G.length&&D[0].childNodes.length!==G.length&&1===G.length)
542
+ //console.log('&&&&&&&&&&&&&&& --------- &&&&&&&&&&&&&&&&', selectedElements[0], $selected[0].childNodes);
543
+ return D=angular.element(G[0]),f(D.parent(),D,F,F===E,h);if("ol"===E||"ul"===E)
544
+ // now if this is a set of selected elements... behave diferently
545
+ return k(D,F);var H=!1;return angular.forEach(D.children(),function(a){a.tagName.match(i)&&(H=!0)}),H?l(D.children(),D,F):l([angular.element("<div>"+y.innerHTML+"</div>")[0]],D,F)}if(E.match(i)){
546
+ //console.log('_nodes', _nodes, tagName);
547
+ if(
548
+ // if we get here then the contents of the ta-bind are selected
549
+ v=b.getOnlySelectedElements(),0===v.length)
550
+ // here is if there is only text in ta-bind ie <div ta-bind>test content</div>
551
+ t=angular.element("<"+F+"><li>"+y.innerHTML+"</li></"+F+">"),D.html(""),D.append(t);else{if(1===v.length&&("ol"===v[0].tagName.toLowerCase()||"ul"===v[0].tagName.toLowerCase()))return v[0].tagName.toLowerCase()===F?e(angular.element(v[0]),h):k(angular.element(v[0]),F);u="";var I=[];for(s=0;s<v.length;s++)/* istanbul ignore else: catch for real-world can't make it occur in testing */
552
+ if(3!==v[s].nodeType){var J=angular.element(v[s]);/* istanbul ignore if: browser check only, phantomjs doesn't return children nodes but chrome at least does */
553
+ if("li"===v[s].tagName.toLowerCase())continue;u+="ol"===v[s].tagName.toLowerCase()||"ul"===v[s].tagName.toLowerCase()?J[0].innerHTML:"span"!==v[s].tagName.toLowerCase()||"ol"!==v[s].childNodes[0].tagName.toLowerCase()&&"ul"!==v[s].childNodes[0].tagName.toLowerCase()?"<"+c("li")+">"+J[0].innerHTML+"</"+c("li")+">":J[0].childNodes[0].innerHTML,I.unshift(J)}
554
+ //console.log('$nodes', $nodes);
555
+ t=angular.element("<"+F+">"+u+"</"+F+">"),I.pop().replaceWith(t),angular.forEach(I,function(a){a.remove()})}return void b.setSelectionToElementEnd(t[0])}}else{if("formatblock"===o.toLowerCase()){
556
+ // find the first blockElement
557
+ for(x=q.toLowerCase().replace(/[<>]/gi,""),"default"===x.trim()&&(x=h,q="<"+h+">"),t="li"===E?D.parent():D;!t[0].tagName||!t[0].tagName.match(i)&&!t.parent().attr("contenteditable");)t=t.parent(),/* istanbul ignore next */
558
+ E=(t[0].tagName||"").toLowerCase();if(E===x){
559
+ // $target is wrap element
560
+ v=t.children();var K=!1;for(s=0;s<v.length;s++)K=K||v[s].tagName.match(i);K?(t.after(v),w=t.next(),t.remove(),t=w):(A.append(t[0].childNodes),t.after(A),t.remove(),t=A)}else if(t.parent()[0].tagName.toLowerCase()!==x||t.parent().hasClass("ta-bind"))if(E.match(j))
561
+ // wrapping a list element
562
+ t.wrap(q);else{
563
+ // find the parent block element if any of the nodes are inline or text
564
+ for(
565
+ // default wrap behaviour
566
+ v=b.getOnlySelectedElements(),0===v.length&&(
567
+ // no nodes at all....
568
+ v=[t[0]]),s=0;s<v.length;s++)if(3===v[s].nodeType||!v[s].tagName.match(i))for(;3===v[s].nodeType||!v[s].tagName||!v[s].tagName.match(i);)v[s]=v[s].parentNode;if(
569
+ // remove any duplicates from the array of _nodes!
570
+ v=v.filter(function(a,b,c){return c.indexOf(a)===b}),
571
+ // remove all whole taTextElement if it is here... unless it is the only element!
572
+ v.length>1&&(v=v.filter(function(a,b,c){return!("div"===a.nodeName.toLowerCase()&&/^taTextElement/.test(a.id))})),angular.element(v[0]).hasClass("ta-bind"))t=angular.element(q),t[0].innerHTML=v[0].innerHTML,v[0].innerHTML=t[0].outerHTML;else if("blockquote"===x){for(
573
+ // blockquotes wrap other block elements
574
+ u="",s=0;s<v.length;s++)u+=v[s].outerHTML;for(t=angular.element(q),t[0].innerHTML=u,v[0].parentNode.insertBefore(t[0],v[0]),s=v.length-1;s>=0;s--)/* istanbul ignore else: */
575
+ v[s].parentNode&&v[s].parentNode.removeChild(v[s])}else/* istanbul ignore next: not tested since identical to blockquote */
576
+ if("pre"===x&&b.getStateShiftKey()){for(
577
+ //console.log('shift pre', _nodes);
578
+ // pre wrap other block elements
579
+ u="",s=0;s<v.length;s++)u+=v[s].outerHTML;for(t=angular.element(q),t[0].innerHTML=u,v[0].parentNode.insertBefore(t[0],v[0]),s=v.length-1;s>=0;s--)/* istanbul ignore else: */
580
+ v[s].parentNode&&v[s].parentNode.removeChild(v[s])}else
581
+ //console.log(optionsTagName, _nodes);
582
+ // regular block elements replace other block elements
583
+ for(s=0;s<v.length;s++){var L=m(v[s],q);v[s]===t[0]&&(t=angular.element(L))}}else{
584
+ //unwrap logic for parent
585
+ var M=t.parent(),N=M.contents();for(s=0;s<N.length;s++)/* istanbul ignore next: can't test - some wierd thing with how phantomjs works */
586
+ M.parent().hasClass("ta-bind")&&3===N[s].nodeType&&(A=angular.element("<"+h+">"),A[0].innerHTML=N[s].outerHTML,N[s]=A[0]),M.parent()[0].insertBefore(N[s],M[0]);M.remove()}
587
+ // looses focus when we have the whole container selected and no text!
588
+ // refocus on the shown display element, this fixes a bug when using firefox
589
+ return b.setSelectionToElementEnd(t[0]),void t[0].focus()}if("createlink"===o.toLowerCase()){/* istanbul ignore next: firefox specific fix */
590
+ if("a"===E)
591
+ // already a link!!! we are just replacing it...
592
+ return void(b.getSelectionElement().href=q);var O='<a href="'+q+'" target="'+(r.a.target?r.a.target:"")+'">',P="</a>",Q=b.getSelection();if(Q.collapsed)
593
+ //console.log('collapsed');
594
+ // insert text at selection, then select then just let normal exec-command run
595
+ b.insertHtml(O+q+P,n);else if(a.getSelection().getRangeAt(0).canSurroundContents()){var R=angular.element(O+P)[0];a.getSelection().getRangeAt(0).surroundContents(R)}return}if("inserthtml"===o.toLowerCase())
596
+ //console.log('inserthtml');
597
+ return void b.insertHtml(q,n)}try{d[0].execCommand(o,p,q)}catch(a){}}}}}]).service("taSelection",["$document","taDOM","$log",/* istanbul ignore next: all browser specifics and PhantomJS dosen't seem to support half of it */
598
+ function(b,c,d){
599
+ // need to dereference the document else the calls don't work correctly
600
+ var e,f=b[0],g=function(a,b){/* check if selection is a BR element at the beginning of a container. If so, get
601
+ * the parentNode instead.
602
+ * offset should be zero in this case. Otherwise, return the original
603
+ * element.
604
+ */
605
+ /* check if selection is a BR element at the beginning of a container. If so, get
606
+ * the parentNode instead.
607
+ * offset should be zero in this case. Otherwise, return the original
608
+ * element.
609
+ */
610
+ return a.tagName&&a.tagName.match(/^br$/i)&&0===b&&!a.previousSibling?{element:a.parentNode,offset:0}:{element:a,offset:b}},h={getSelection:function(){var b;try{
611
+ // catch any errors from rangy and ignore the issue
612
+ b=a.getSelection().getRangeAt(0)}catch(a){
613
+ //console.info(e);
614
+ return}var c=b.commonAncestorContainer,d={start:g(b.startContainer,b.startOffset),end:g(b.endContainer,b.endOffset),collapsed:b.collapsed};
615
+ //console.log('***selection container:', selection.container.nodeName, selection.start.offset, selection.container);
616
+ // This has problems under Firefox.
617
+ // On Firefox with
618
+ // <p>Try me !</p>
619
+ // <ul>
620
+ // <li>line 1</li>
621
+ // <li>line 2</li>
622
+ // </ul>
623
+ // <p>line 3</p>
624
+ // <ul>
625
+ // <li>line 4</li>
626
+ // <li>line 5</li>
627
+ // </ul>
628
+ // <p>Hello textAngular</p>
629
+ // WITH the cursor after the 3 on line 3, it gets the commonAncestorContainer as:
630
+ // <TextNode textContent='line 3'>
631
+ // AND Chrome gets the commonAncestorContainer as:
632
+ // <p>line 3</p>
633
+ //
634
+ // Check if the container is a text node and return its parent if so
635
+ // unless this is the whole taTextElement. If so we return the textNode
636
+ //console.log('*********taTextElement************');
637
+ //console.log('commonAncestorContainer:', container);
638
+ return 3===c.nodeType&&("div"===c.parentNode.nodeName.toLowerCase()&&/^taTextElement/.test(c.parentNode.id)||(c=c.parentNode)),"div"===c.nodeName.toLowerCase()&&/^taTextElement/.test(c.id)?(d.start.element=c.childNodes[d.start.offset],d.end.element=c.childNodes[d.end.offset],d.container=c):c.parentNode===d.start.element||c.parentNode===d.end.element?d.container=c.parentNode:d.container=c,d},
639
+ // if we use the LEFT_ARROW and we are at the special place <span>&#65279;</span> we move the cursor over by one...
640
+ // Chrome and Firefox behave differently so so fix this for Firefox here. No adjustment needed for Chrome.
641
+ updateLeftArrowKey:function(b){var c=a.getSelection().getRangeAt(0);if(c&&c.collapsed){var d=h.getFlattenedDom(c);if(!d.findIndex)return;var e,f,g=c.startContainer,i=d.findIndex(function(a,b){if(a.node===g)return!0;var c=a.parents.indexOf(g);return c!==-1});
642
+ //console.log('updateLeftArrowKey', range.startOffset, range.startContainer.textContent);
643
+ // this first section handles the case for Chrome browser
644
+ // if the first character of the nextNode is a \ufeff we know that we are just before the special span...
645
+ // and so we most left by one character
646
+ if(
647
+ //console.log('indexStartContainer', indexStartContainer, _nodes.length, 'startContainer:', _node, _node === _nodes[indexStartContainer].node);
648
+ d.forEach(function(a,b){
649
+ //console.log(i, n.node);
650
+ a.parents.forEach(function(a,b){})}),i+1<d.length&&(
651
+ // we need the node just after this startContainer
652
+ // so we can check and see it this is a special place
653
+ f=d[i+1].node),f&&f.textContent&&(e=/^\ufeff([^\ufeff]*)$/.exec(f.textContent)))
654
+ // we are before the special node with begins with a \ufeff character
655
+ //console.log('LEFT ...found it...', 'startOffset:', range.startOffset, m[0].length, m[1].length);
656
+ // no need to change anything in this case
657
+ return;var j;if(i>0&&(
658
+ // we need the node just after this startContainer
659
+ // so we can check and see it this is a special place
660
+ j=d[i-1].node),0===c.startOffset&&j&&(
661
+ //console.log(nextNodeToLeft, range.startOffset, nextNodeToLeft.textContent);
662
+ e=/^\ufeff([^\ufeff]*)$/.exec(j.textContent)))
663
+ //console.log('LEFT &&&&&&&&&&&&&&&&&&&...found it...&&&&&&&&&&&', nextNodeToLeft, m[0].length, m[1].length);
664
+ // move over to the left my one -- Firefox triggers this case
665
+ return void h.setSelectionToElementEnd(j)}},
666
+ // if we use the RIGHT_ARROW and we are at the special place <span>&#65279;</span> we move the cursor over by one...
667
+ updateRightArrowKey:function(a){},getFlattenedDom:function(a){function b(a){if(a.node.childNodes.length){var c=Array.prototype.slice.call(a.node.childNodes);// converts NodeList to Array
668
+ c.forEach(function(c){var d=a.parents.slice();d.slice(-1)[0]!==a.node&&d.push(a.node),b({parents:d,node:c})})}else d.push({parents:a.parents,node:a.node})}var c=a.commonAncestorContainer.parentNode;if(!c)return a.commonAncestorContainer.childNodes;var d=Array.prototype.slice.call(c.childNodes),e=d.indexOf(a.startContainer);
669
+ // make sure that we have a big enough set of nodes
670
+ // now walk the parent
671
+ return e+1<d.length&&e>0||c.parentNode&&(c=c.parentNode),d=[],b({parents:[c],node:c}),d},getOnlySelectedElements:function(){var b=a.getSelection().getRangeAt(0),c=b.commonAncestorContainer;
672
+ // get the nodes in the range that are ELEMENT_NODE and are children of the container
673
+ // in this range...
674
+ // Node.TEXT_NODE === 3
675
+ // Node.ELEMENT_NODE === 1
676
+ // Node.COMMENT_NODE === 8
677
+ // Check if the container is a text node and return its parent if so
678
+ return c=3===c.nodeType?c.parentNode:c,b.getNodes([1],function(a){return a.parentNode===c})},
679
+ // this includes the container element if all children are selected
680
+ getAllSelectedElements:function(){var b=a.getSelection().getRangeAt(0),c=b.commonAncestorContainer;
681
+ // Node.TEXT_NODE === 3
682
+ // Node.ELEMENT_NODE === 1
683
+ // Node.COMMENT_NODE === 8
684
+ // Check if the container is a text node and return its parent if so
685
+ c=3===c.nodeType?c.parentNode:c;
686
+ // get the nodes in the range that are ELEMENT_NODE and are children of the container
687
+ // in this range...
688
+ var d=b.getNodes([1],function(a){return a.parentNode===c}),e=c.innerHTML;
689
+ //console.log(innerHtml);
690
+ //console.log(range.toHtml());
691
+ //console.log(innerHtml === range.toHtml());
692
+ if(
693
+ // remove the junk that rangy has put down
694
+ e=e.replace(/<span id=.selectionBoundary[^>]+>\ufeff?<\/span>/gi,""),e===b.toHtml()&&("div"!==c.nodeName.toLowerCase()||!/^taTextElement/.test(c.id))){for(var f=[],g=d.length;g--;f.unshift(d[g]));d=f,d.push(c)}return d},
695
+ // Some basic selection functions
696
+ getSelectionElement:function(){var a=h.getSelection();return a?h.getSelection().container:void 0},setSelection:function(b,c,d,e){var f=a.createRange();f.setStart(b,d),f.setEnd(c,e),a.getSelection().setSingleRange(f)},setSelectionBeforeElement:function(b){var c=a.createRange();c.selectNode(b),c.collapse(!0),a.getSelection().setSingleRange(c)},setSelectionAfterElement:function(b){var c=a.createRange();c.selectNode(b),c.collapse(!1),a.getSelection().setSingleRange(c)},setSelectionToElementStart:function(b){var c=a.createRange();c.selectNodeContents(b),c.collapse(!0),a.getSelection().setSingleRange(c)},setSelectionToElementEnd:function(b){var c=a.createRange();c.selectNodeContents(b),c.collapse(!1),b.childNodes&&b.childNodes[b.childNodes.length-1]&&"br"===b.childNodes[b.childNodes.length-1].nodeName&&(c.startOffset=c.endOffset=c.startOffset-1),a.getSelection().setSingleRange(c)},setStateShiftKey:function(a){e=a},getStateShiftKey:function(){return e},
697
+ // from http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
698
+ // topNode is the contenteditable normally, all manipulation MUST be inside this.
699
+ insertHtml:function(b,d){var e,g,j,l,m,n,o,p=angular.element("<div>"+b+"</div>"),q=a.getSelection().getRangeAt(0),r=f.createDocumentFragment(),s=p[0].childNodes,t=!0;if(s.length>0){for(
700
+ // NOTE!! We need to do the following:
701
+ // 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.
702
+ // 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).
703
+ l=[],j=0;j<s.length;j++){var u=s[j];"p"===u.nodeName.toLowerCase()&&""===u.innerHTML.trim()||(/****************
704
+ * allow any text to be inserted...
705
+ if(( _cnode.nodeType === 3 &&
706
+ _cnode.nodeValue === '\ufeff'[0] &&
707
+ _cnode.nodeValue.trim() === '') // empty no-space space element
708
+ ) {
709
+ // no change to isInline
710
+ nodes.push(_cnode);
711
+ continue;
712
+ }
713
+ if(_cnode.nodeType === 3 &&
714
+ _cnode.nodeValue.trim() === '') { // empty text node
715
+ continue;
716
+ }
717
+ *****************/
718
+ t=t&&!i.test(u.nodeName),l.push(u))}for(var v=0;v<l.length;v++)n=r.appendChild(l[v]);!t&&q.collapsed&&/^(|<br(|\/)>)$/i.test(q.startContainer.innerHTML)&&q.selectNode(q.startContainer)}else t=!0,
719
+ // paste text of some sort
720
+ n=r=f.createTextNode(b);
721
+ // Other Edge case - selected data spans multiple blocks.
722
+ if(t)q.deleteContents();else// not inline insert
723
+ if(q.collapsed&&q.startContainer!==d)if(q.startContainer.innerHTML&&q.startContainer.innerHTML.match(/^<[^>]*>$/i))
724
+ // this log is to catch when innerHTML is something like `<img ...>`
725
+ e=q.startContainer,1===q.startOffset?(
726
+ // before single tag
727
+ q.setStartAfter(e),q.setEndAfter(e)):(
728
+ // after single tag
729
+ q.setStartBefore(e),q.setEndBefore(e));else{
730
+ // split element into 2 and insert block element in middle
731
+ if(3===q.startContainer.nodeType&&q.startContainer.parentNode!==d)
732
+ // Escape out of the inline tags like b
733
+ for(// if text node
734
+ e=q.startContainer.parentNode,g=e.cloneNode(),
735
+ // split the nodes into two lists - before and after, splitting the node with the selection into 2 text nodes.
736
+ c.splitNodes(e.childNodes,e,g,q.startContainer,q.startOffset);!k.test(e.nodeName);){angular.element(e).after(g),e=e.parentNode;var w=g;g=e.cloneNode(),
737
+ // split the nodes into two lists - before and after, splitting the node with the selection into 2 text nodes.
738
+ c.splitNodes(e.childNodes,e,g,w)}else e=q.startContainer,g=e.cloneNode(),c.splitNodes(e.childNodes,e,g,void 0,void 0,q.startOffset);if(angular.element(e).after(g),
739
+ // put cursor to end of inserted content
740
+ //console.log('setStartAfter', parent);
741
+ q.setStartAfter(e),q.setEndAfter(e),/^(|<br(|\/)>)$/i.test(e.innerHTML.trim())&&(q.setStartBefore(e),q.setEndBefore(e),angular.element(e).remove()),/^(|<br(|\/)>)$/i.test(g.innerHTML.trim())&&angular.element(g).remove(),"li"===e.nodeName.toLowerCase()){for(o=f.createDocumentFragment(),m=0;m<r.childNodes.length;m++)p=angular.element("<li>"),c.transferChildNodes(r.childNodes[m],p[0]),c.transferNodeAttributes(r.childNodes[m],p[0]),o.appendChild(p[0]);r=o,n&&(n=r.childNodes[r.childNodes.length-1],n=n.childNodes[n.childNodes.length-1])}}else q.deleteContents();q.insertNode(r),n&&h.setSelectionToElementEnd(n)}};return h}]).service("taDOM",function(){var a={
742
+ // recursive function that returns an array of angular.elements that have the passed attribute set on them
743
+ getByAttribute:function(b,c){var d=[],e=b.children();return e.length&&angular.forEach(e,function(b){d=d.concat(a.getByAttribute(angular.element(b),c))}),void 0!==b.attr(c)&&d.push(b),d},transferChildNodes:function(a,b){for(
744
+ // clear out target
745
+ b.innerHTML="";a.childNodes.length>0;)b.appendChild(a.childNodes[0]);return b},splitNodes:function(b,c,d,e,f,g){if(!e&&isNaN(g))throw new Error("taDOM.splitNodes requires a splitNode or splitIndex");for(var h=document.createDocumentFragment(),i=document.createDocumentFragment(),j=0;b.length>0&&(isNaN(g)||g!==j)&&b[0]!==e;)h.appendChild(b[0]),// this removes from the nodes array (if proper childNodes object.
746
+ j++;for(!isNaN(f)&&f>=0&&b[0]&&(h.appendChild(document.createTextNode(b[0].nodeValue.substring(0,f))),b[0].nodeValue=b[0].nodeValue.substring(f));b.length>0;)i.appendChild(b[0]);a.transferChildNodes(h,c),a.transferChildNodes(i,d)},transferNodeAttributes:function(a,b){for(var c=0;c<a.attributes.length;c++)b.setAttribute(a.attributes[c].name,a.attributes[c].value);return b}};return a}),angular.module("textAngular.validators",[]).directive("taMaxText",function(){return{restrict:"A",require:"ngModel",link:function(a,b,c,d){var e=parseInt(a.$eval(c.taMaxText));if(isNaN(e))throw"Max text must be an integer";c.$observe("taMaxText",function(a){if(e=parseInt(a),isNaN(e))throw"Max text must be an integer";d.$dirty&&d.$validate()}),d.$validators.taMaxText=function(a){var b=angular.element("<div/>");return b.html(a),b.text().length<=e}}}}).directive("taMinText",function(){return{restrict:"A",require:"ngModel",link:function(a,b,c,d){var e=parseInt(a.$eval(c.taMinText));if(isNaN(e))throw"Min text must be an integer";c.$observe("taMinText",function(a){if(e=parseInt(a),isNaN(e))throw"Min text must be an integer";d.$dirty&&d.$validate()}),d.$validators.taMinText=function(a){var b=angular.element("<div/>");return b.html(a),!b.text().length||b.text().length>=e}}}}),angular.module("textAngular.taBind",["textAngular.factories","textAngular.DOM"]).service("_taBlankTest",[function(){return function(a){
747
+ // we radically restructure this code.
748
+ // what was here before was incredibly fragile.
749
+ // What we do now is to check that the html is non-blank visually
750
+ // which we check by looking at html->text
751
+ if(!a)return!0;
752
+ // find first non-tag match - ie start of string or after tag that is not whitespace
753
+ // var t0 = performance.now();
754
+ // Takes a small fraction of a mSec to do this...
755
+ var b=d(a);
756
+ // var t1 = performance.now();
757
+ // console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to generate:');
758
+ // var t1 = performance.now();
759
+ // console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to generate:');
760
+ return""===b&&!/<img[^>]+>/.test(a)}}]).directive("taButton",[function(){return{link:function(a,b,c){b.attr("unselectable","on"),b.on("mousedown",function(a,b){/* istanbul ignore else: this is for catching the jqLite testing*/
761
+ // this prevents focusout from firing on the editor when clicking toolbar buttons
762
+ return b&&angular.extend(a,b),a.preventDefault(),!1})}}}]).directive("taBind",["taSanitize","$timeout","$document","taFixChrome","taBrowserTag","taSelection","taSelectableElements","taApplyCustomRenderers","taOptions","_taBlankTest","$parse","taDOM","textAngularManager",function(b,c,d,e,f,h,j,l,o,p,q,r,s){
763
+ // Uses for this are textarea or input with ng-model and ta-bind='text'
764
+ // OR any non-form element with contenteditable="contenteditable" ta-bind="html|text" ng-model
765
+ return{priority:2,// So we override validators correctly
766
+ require:["ngModel","?ngModelOptions"],link:function(f,r,u,v){function w(a){var b;return V.forEach(function(c){if(c.keyCode===a.keyCode){var d=(a.metaKey?N:0)+(a.ctrlKey?M:0)+(a.shiftKey?P:0)+(a.altKey?O:0);if(c.forbiddenModifiers&d)return;c.mustHaveModifiers.every(function(a){return d&a})&&(b=c.specialKey)}}),b}var x,y,z,A,B=v[0],C=v[1]||{},D=void 0!==r.attr("contenteditable")&&r.attr("contenteditable"),E=D||"textarea"===r[0].tagName.toLowerCase()||"input"===r[0].tagName.toLowerCase(),F=!1,G=!1,H=!1,I=u.taUnsafeSanitizer||o.disableSanitizer,J=u.taKeepStyles||o.keepStyles,K=/^(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,L=/^(8|13|32|46|59|61|107|109|173|186|187|188|189|190|191|192|219|220|221|222)$/i,M=1,N=2,O=4,P=8,Q=13,R=16,S=9,T=37,U=39,V=[
767
+ // ctrl/command + z
768
+ {specialKey:"UndoKey",forbiddenModifiers:O+P,mustHaveModifiers:[N+M],keyCode:90},
769
+ // ctrl/command + shift + z
770
+ {specialKey:"RedoKey",forbiddenModifiers:O,mustHaveModifiers:[N+M,P],keyCode:90},
771
+ // ctrl/command + y
772
+ {specialKey:"RedoKey",forbiddenModifiers:O+P,mustHaveModifiers:[N+M],keyCode:89},
773
+ // TabKey
774
+ {specialKey:"TabKey",forbiddenModifiers:N+P+O+M,mustHaveModifiers:[],keyCode:S},
775
+ // shift + TabKey
776
+ {specialKey:"ShiftTabKey",forbiddenModifiers:N+O+M,mustHaveModifiers:[P],keyCode:S}];
777
+ // set the default to be a paragraph value
778
+ void 0===u.taDefaultWrap&&(u.taDefaultWrap="p"),/* istanbul ignore next: ie specific test */
779
+ ""===u.taDefaultWrap?(z="",A=void 0===g.ie?"<div><br></div>":g.ie>=11?"<p><br></p>":g.ie<=8?"<P>&nbsp;</P>":"<p>&nbsp;</p>"):(z=void 0===g.ie||g.ie>=11?"br"===u.taDefaultWrap.toLowerCase()?"<BR><BR>":"<"+u.taDefaultWrap+"><br></"+u.taDefaultWrap+">":g.ie<=8?"<"+u.taDefaultWrap.toUpperCase()+"></"+u.taDefaultWrap.toUpperCase()+">":"<"+u.taDefaultWrap+"></"+u.taDefaultWrap+">",A=void 0===g.ie||g.ie>=11?"br"===u.taDefaultWrap.toLowerCase()?"<br><br>":"<"+u.taDefaultWrap+"><br></"+u.taDefaultWrap+">":g.ie<=8?"<"+u.taDefaultWrap.toUpperCase()+">&nbsp;</"+u.taDefaultWrap.toUpperCase()+">":"<"+u.taDefaultWrap+">&nbsp;</"+u.taDefaultWrap+">"),/* istanbul ignore else */
780
+ C.$options||(C.$options={});// ng-model-options support
781
+ var W=function(a){if(p(a))return a;var b=angular.element("<div>"+a+"</div>");
782
+ //console.log('domTest.children().length():', domTest.children().length);
783
+ //console.log('_ensureContentWrapped', domTest.children());
784
+ //console.log(value, attrs.taDefaultWrap);
785
+ if(0===b.children().length)
786
+ // if we have a <br> and the attrs.taDefaultWrap is a <p> we need to remove the <br>
787
+ //value = value.replace(/<br>/i, '');
788
+ a="<"+u.taDefaultWrap+">"+a+"</"+u.taDefaultWrap+">";else{var c,d=b[0].childNodes,e=!1;for(c=0;c<d.length&&!(e=d[c].nodeName.toLowerCase().match(i));c++);if(e)for(a="",c=0;c<d.length;c++){var f=d[c],g=f.nodeName.toLowerCase();
789
+ //console.log('node#:', i, 'name:', nodeName);
790
+ if("#comment"===g)a+="<!--"+f.nodeValue+"-->";else if("#text"===g){
791
+ // determine if this is all whitespace, if so, we will leave it as it is.
792
+ // otherwise, we will wrap it as it is
793
+ var h=f.textContent;
794
+ // not pure white space so wrap in <p>...</p> or whatever attrs.taDefaultWrap is set to.
795
+ a+=h.trim()?"<"+u.taDefaultWrap+">"+h+"</"+u.taDefaultWrap+">":h}else if(g.match(i))a+=f.outerHTML;else{/* istanbul ignore next: Doesn't seem to trigger on tests */
796
+ var j=f.outerHTML||f.nodeValue;/* istanbul ignore else: Doesn't seem to trigger on tests, is tested though */
797
+ a+=""!==j.trim()?"<"+u.taDefaultWrap+">"+j+"</"+u.taDefaultWrap+">":j}}else a="<"+u.taDefaultWrap+">"+a+"</"+u.taDefaultWrap+">"}
798
+ //console.log(value);
799
+ return a};u.taPaste&&(y=q(u.taPaste)),r.addClass("ta-bind");var X;f["$undoManager"+(u.id||"")]=B.$undoManager={_stack:[],_index:0,_max:1e3,push:function(a){return"undefined"==typeof a||null===a||"undefined"!=typeof this.current()&&null!==this.current()&&a===this.current()?a:(this._index<this._stack.length-1&&(this._stack=this._stack.slice(0,this._index+1)),this._stack.push(a),X&&c.cancel(X),this._stack.length>this._max&&this._stack.shift(),this._index=this._stack.length-1,a)},undo:function(){return this.setToIndex(this._index-1)},redo:function(){return this.setToIndex(this._index+1)},setToIndex:function(a){if(!(a<0||a>this._stack.length-1))return this._index=a,this.current()},current:function(){return this._stack[this._index]}};
800
+ // in here we are undoing the converts used elsewhere to prevent the < > and & being displayed when they shouldn't in the code.
801
+ var Y,Z=function(){if(D)return r[0].innerHTML;if(E)return r.val();throw"textAngular Error: attempting to update non-editable taBind"},$=function(a){
802
+ // emit the element-select event, pass the element
803
+ return f.$emit("ta-element-select",this),a.preventDefault(),!1},_=f["reApplyOnSelectorHandlers"+(u.id||"")]=function(){/* istanbul ignore else */
804
+ F||angular.forEach(j,function(a){
805
+ // check we don't apply the handler twice
806
+ r.find(a).off("click",$).on("click",$)})},aa=function(a,b,c){H=c||!1,"undefined"!=typeof b&&null!==b||(b=D),// if not contentEditable then the native undo/redo is fine
807
+ "undefined"!=typeof a&&null!==a||(a=Z()),p(a)?(
808
+ // this avoids us from tripping the ng-pristine flag if we click in and out with out typing
809
+ ""!==B.$viewValue&&B.$setViewValue(""),b&&""!==B.$undoManager.current()&&B.$undoManager.push("")):(_(),B.$viewValue!==a&&(B.$setViewValue(a),b&&B.$undoManager.push(a))),B.$render()},ba=function(a){r[0].innerHTML=a},ca=f["$undoTaBind"+(u.id||"")]=function(){/* istanbul ignore else: can't really test it due to all changes being ignored as well in readonly */
810
+ if(!F&&D){var a=B.$undoManager.undo();"undefined"!=typeof a&&null!==a&&(ba(a),aa(a,!1),Y&&c.cancel(Y),Y=c(function(){r[0].focus(),h.setSelectionToElementEnd(r[0])},1))}},da=f["$redoTaBind"+(u.id||"")]=function(){/* istanbul ignore else: can't really test it due to all changes being ignored as well in readonly */
811
+ if(!F&&D){var a=B.$undoManager.redo();"undefined"!=typeof a&&null!==a&&(ba(a),aa(a,!1),/* istanbul ignore next */
812
+ Y&&c.cancel(Y),Y=c(function(){r[0].focus(),h.setSelectionToElementEnd(r[0])},1))}};
813
+ //used for updating when inserting wrapped elements
814
+ f["updateTaBind"+(u.id||"")]=function(){F||aa(void 0,void 0,!0)};
815
+ // catch DOM XSS via taSanitize
816
+ // Sanitizing both ways is identical
817
+ var ea=function(a){return B.$oldViewValue=b(e(a,J),B.$oldViewValue,I)};
818
+ //this code is used to update the models when data is entered/deleted
819
+ if(
820
+ // trigger the validation calls
821
+ r.attr("required")&&(B.$validators.required=function(a,b){return!p(a||b)}),
822
+ // parsers trigger from the above keyup function or any other time that the viewValue is updated and parses it for storage in the ngModel
823
+ B.$parsers.push(ea),B.$parsers.unshift(W),
824
+ // because textAngular is bi-directional (which is awesome) we need to also sanitize values going in from the server
825
+ B.$formatters.push(ea),B.$formatters.unshift(W),B.$formatters.unshift(function(a){return B.$undoManager.push(a||"")}),E)if(f.events={},D){
826
+ // all the code specific to contenteditable divs
827
+ var fa=!1,ga=function(a){var d=void 0!==a&&a.match(/content=["']*OneNote.File/i);/* istanbul ignore else: don't care if nothing pasted */
828
+ //console.log(text);
829
+ if(a&&a.trim().length){
830
+ // test paste from word/microsoft product
831
+ if(a.match(/class=["']*Mso(Normal|List)/i)||a.match(/content=["']*Word.Document/i)||a.match(/content=["']*OneNote.File/i)){var e=a.match(/<!--StartFragment-->([\s\S]*?)<!--EndFragment-->/i);e=e?e[1]:a,e=e.replace(/<o:p>[\s\S]*?<\/o:p>/gi,"").replace(/class=(["']|)MsoNormal(["']|)/gi,"");var g=angular.element("<div>"+e+"</div>"),i=angular.element("<div></div>"),j={element:null,lastIndent:[],lastLi:null,isUl:!1};j.lastIndent.peek=function(){var a=this.length;if(a>0)return this[a-1]};for(var k=function(a){j.isUl=a,j.element=angular.element(a?"<ul>":"<ol>"),j.lastIndent=[],j.lastIndent.peek=function(){var a=this.length;if(a>0)return this[a-1]},j.lastLevelMatch=null},l=0;l<=g[0].childNodes.length;l++)if(g[0].childNodes[l]&&"#text"!==g[0].childNodes[l].nodeName){var m=g[0].childNodes[l].tagName.toLowerCase();if("p"===m||"ul"===m||"h1"===m||"h2"===m||"h3"===m||"h4"===m||"h5"===m||"h6"===m||"table"===m){var n=angular.element(g[0].childNodes[l]),o=(n.attr("class")||"").match(/MsoList(Bullet|Number|Paragraph)(CxSp(First|Middle|Last)|)/i);if(o){if(n[0].childNodes.length<2||n[0].childNodes[1].childNodes.length<1)continue;var p="bullet"===o[1].toLowerCase()||"number"!==o[1].toLowerCase()&&!(/^[^0-9a-z<]*[0-9a-z]+[^0-9a-z<>]</i.test(n[0].childNodes[1].innerHTML)||/^[^0-9a-z<]*[0-9a-z]+[^0-9a-z<>]</i.test(n[0].childNodes[1].childNodes[0].innerHTML)),q=(n.attr("style")||"").match(/margin-left:([\-\.0-9]*)/i),s=parseFloat(q?q[1]:0),t=(n.attr("style")||"").match(/mso-list:l([0-9]+) level([0-9]+) lfo[0-9+]($|;)/i);if(
832
+ // prefers the mso-list syntax
833
+ t&&t[2]&&(s=parseInt(t[2])),t&&(!j.lastLevelMatch||t[1]!==j.lastLevelMatch[1])||!o[3]||"first"===o[3].toLowerCase()||null===j.lastIndent.peek()||j.isUl!==p&&j.lastIndent.peek()===s)k(p),i.append(j.element);else if(null!=j.lastIndent.peek()&&j.lastIndent.peek()<s)j.element=angular.element(p?"<ul>":"<ol>"),j.lastLi.append(j.element);else if(null!=j.lastIndent.peek()&&j.lastIndent.peek()>s){for(;null!=j.lastIndent.peek()&&j.lastIndent.peek()>s;)if("li"!==j.element.parent()[0].tagName.toLowerCase()){if(!/[uo]l/i.test(j.element.parent()[0].tagName.toLowerCase()))// else it's it should be a sibling
834
+ break;j.element=j.element.parent(),j.lastIndent.pop()}else j.element=j.element.parent();j.isUl="ul"===j.element[0].tagName.toLowerCase(),p!==j.isUl&&(k(p),i.append(j.element))}j.lastLevelMatch=t,s!==j.lastIndent.peek()&&j.lastIndent.push(s),j.lastLi=angular.element("<li>"),j.element.append(j.lastLi),j.lastLi.html(n.html().replace(/<!(--|)\[if !supportLists\](--|)>[\s\S]*?<!(--|)\[endif\](--|)>/gi,"")),n.remove()}else k(!1),i.append(n)}}var u=function(a){a=angular.element(a);for(var b=a[0].childNodes.length-1;b>=0;b--)a.after(a[0].childNodes[b]);a.remove()};angular.forEach(i.find("span"),function(a){a.removeAttribute("lang"),a.attributes.length<=0&&u(a)}),angular.forEach(i.find("font"),u),a=i.html(),d&&(a=i.html()||g.html()),
835
+ // LF characters instead of spaces in some spots and they are replaced by '/n', so we need to just swap them to spaces
836
+ a=a.replace(/\n/g," ")}else{if(
837
+ // remove unnecessary chrome insert
838
+ a=a.replace(/<(|\/)meta[^>]*?>/gi,""),a.match(/<[^>]*?(ta-bind)[^>]*?>/)){
839
+ // entire text-angular or ta-bind has been pasted, REMOVE AT ONCE!!
840
+ if(a.match(/<[^>]*?(text-angular)[^>]*?>/)){var v=angular.element("<div>"+a+"</div>");v.find("textarea").remove();for(var w=0;w<binds.length;w++){for(var x=binds[w][0].parentNode.parentNode,z=0;z<binds[w][0].childNodes.length;z++)x.parentNode.insertBefore(binds[w][0].childNodes[z],x);x.parentNode.removeChild(x)}a=v.html().replace('<br class="Apple-interchange-newline">',"")}}else a.match(/^<span/)&&(
841
+ // in case of pasting only a span - chrome paste, remove them. THis is just some wierd formatting
842
+ // if we remove the '<span class="Apple-converted-space"> </span>' here we destroy the spacing
843
+ // on paste from even ourselves!
844
+ a.match(/<span class=(\"Apple-converted-space\"|\'Apple-converted-space\')>.<\/span>/gi)||(a=a.replace(/<(|\/)span[^>]*?>/gi,"")));
845
+ // Webkit on Apple tags
846
+ a=a.replace(/<br class="Apple-interchange-newline"[^>]*?>/gi,"").replace(/<span class="Apple-converted-space">( |&nbsp;)<\/span>/gi,"&nbsp;")}/<li(\s.*)?>/i.test(a)&&/(<ul(\s.*)?>|<ol(\s.*)?>).*<li(\s.*)?>/i.test(a)===!1&&(
847
+ // insert missing parent of li element
848
+ a=a.replace(/<li(\s.*)?>.*<\/li(\s.*)?>/i,"<ul>$&</ul>")),
849
+ // parse whitespace from plaintext input, starting with preceding spaces that get stripped on paste
850
+ a=a.replace(/^[ |\u00A0]+/gm,function(a){for(var b="",c=0;c<a.length;c++)b+="&nbsp;";return b}).replace(/\n|\r\n|\r/g,"<br />").replace(/\t/g,"&nbsp;&nbsp;&nbsp;&nbsp;"),y&&(a=y(f,{$html:a})||a),
851
+ // turn span vertical-align:super into <sup></sup>
852
+ a=a.replace(/<span style=("|')([^<]*?)vertical-align\s*:\s*super;?([^>]*?)("|')>([^<]+?)<\/span>/g,"<sup style='$2$3'>$5</sup>"),a=b(a,"",I),
853
+ //console.log('DONE\n', text);
854
+ h.insertHtml(a,r[0]),c(function(){B.$setViewValue(Z()),fa=!1,r.removeClass("processing-paste")},0)}else fa=!1,r.removeClass("processing-paste")};r.on("paste",f.events.paste=function(b,e){if(/* istanbul ignore else: this is for catching the jqLite testing*/
855
+ e&&angular.extend(b,e),F||fa)return b.stopPropagation(),b.preventDefault(),!1;
856
+ // Code adapted from http://stackoverflow.com/questions/2176861/javascript-get-clipboard-data-on-paste-event-cross-browser/6804718#6804718
857
+ fa=!0,r.addClass("processing-paste");var f,g=(b.originalEvent||b).clipboardData;/* istanbul ignore next: Handle legacy IE paste */
858
+ if(!g&&window.clipboardData&&window.clipboardData.getData)return f=window.clipboardData.getData("Text"),ga(f),b.stopPropagation(),b.preventDefault(),!1;if(g&&g.getData&&g.types.length>0){for(var h="",i=0;i<g.types.length;i++)h+=" "+g.types[i];/* istanbul ignore next: browser tests */
859
+ return/text\/html/i.test(h)?f=g.getData("text/html"):/text\/plain/i.test(h)&&(f=g.getData("text/plain")),ga(f),b.stopPropagation(),b.preventDefault(),!1}// Everything else - empty editdiv and allow browser to paste content into it, then cleanup
860
+ var j=a.saveSelection(),k=angular.element('<div class="ta-hidden-input" contenteditable="true"></div>');d.find("body").append(k),k[0].focus(),c(function(){
861
+ // restore selection
862
+ a.restoreSelection(j),ga(k[0].innerHTML),r[0].focus(),k.remove()},0)}),r.on("cut",f.events.cut=function(a){
863
+ // timeout to next is needed as otherwise the paste/cut event has not finished actually changing the display
864
+ F?a.preventDefault():c(function(){B.$setViewValue(Z())},0)}),r.on("keydown",f.events.keydown=function(a,b){/* istanbul ignore else: this is for catching the jqLite testing*/
865
+ b&&angular.extend(a,b),a.keyCode===R?h.setStateShiftKey(!0):h.setStateShiftKey(!1),a.specialKey=w(a);var c;/* istanbul ignore else: readonly check */
866
+ if(/* istanbul ignore next: difficult to test */
867
+ o.keyMappings.forEach(function(b){a.specialKey===b.commandKeyCode&&(
868
+ // taOptions has remapped this binding... so
869
+ // we disable our own
870
+ a.specialKey=void 0),b.testForKey(a)&&(c=b.commandKeyCode),"UndoKey"!==b.commandKeyCode&&"RedoKey"!==b.commandKeyCode||b.enablePropagation||a.preventDefault()}),/* istanbul ignore next: difficult to test */
871
+ "undefined"!=typeof c&&(a.specialKey=c),/* istanbul ignore next: difficult to test as can't seem to select */
872
+ "undefined"==typeof a.specialKey||"UndoKey"===a.specialKey&&"RedoKey"===a.specialKey||(a.preventDefault(),s.sendKeyCommand(f,a)),!(F||("UndoKey"===a.specialKey&&(ca(),a.preventDefault()),"RedoKey"===a.specialKey&&(da(),a.preventDefault()),a.keyCode!==Q||a.shiftKey||a.ctrlKey||a.metaKey||a.altKey))){var d,e=function(a,b){for(var c=0;c<a.length;c++)if(a[c]===b)return!0;return!1},g=h.getSelectionElement();
873
+ // shifted to nodeName here from tagName since it is more widely supported see: http://stackoverflow.com/questions/4878484/difference-between-tagname-and-nodename
874
+ if(!g.nodeName.match(k))return;var i=angular.element(z),j=["blockquote","ul","ol"];if(e(j,g.parentNode.tagName.toLowerCase())){if(/^<br(|\/)>$/i.test(g.innerHTML.trim())&&!g.nextSibling){
875
+ // if last element is blank, pull element outside.
876
+ d=angular.element(g);var l=d.parent();l.after(i),d.remove(),0===l.children().length&&l.remove(),h.setSelectionToElementStart(i[0]),a.preventDefault()}/^<[^>]+><br(|\/)><\/[^>]+>$/i.test(g.innerHTML.trim())&&(d=angular.element(g),d.after(i),d.remove(),h.setSelectionToElementStart(i[0]),a.preventDefault())}}});var ha;r.on("keyup",f.events.keyup=function(a,b){// clear the ShiftKey state
877
+ /* istanbul ignore next: FF specific bug fix */
878
+ if(/* istanbul ignore else: this is for catching the jqLite testing*/
879
+ b&&angular.extend(a,b),h.setStateShiftKey(!1),a.keyCode===S){var d=h.getSelection();return void(d.start.element===r[0]&&r.children().length&&h.setSelectionToElementStart(r.children()[0]))}if(
880
+ // we do this here during the 'keyup' so that the browser has already moved the slection by one character...
881
+ a.keyCode!==T||a.shiftKey||h.updateLeftArrowKey(r),
882
+ // we do this here during the 'keyup' so that the browser has already moved the slection by one character...
883
+ a.keyCode!==U||a.shiftKey||h.updateRightArrowKey(r),X&&c.cancel(X),!F&&!K.test(a.keyCode))/* istanbul ignore next: Ignore any _ENTER_KEYCODE that has ctrlKey, metaKey or alKey */
884
+ if(a.keyCode===Q&&(a.ctrlKey||a.metaKey||a.altKey));else{
885
+ // if enter - insert new taDefaultWrap, if shift+enter insert <br/>
886
+ if(""!==z&&"<BR><BR>"!==z&&a.keyCode===Q&&!a.ctrlKey&&!a.metaKey&&!a.altKey){for(var e=h.getSelectionElement();!e.nodeName.match(k)&&e!==r[0];)e=e.parentNode;if(a.shiftKey){
887
+ // shift + Enter
888
+ var f=e.tagName.toLowerCase();
889
+ //console.log('Shift+Enter', selection.tagName, attrs.taDefaultWrap, selection.innerHTML.trim());
890
+ // For an LI: We see: LI p ....<br><br>
891
+ // For a P: We see: P p ....<br><br>
892
+ // on Safari, the browser ignores the Shift+Enter and acts just as an Enter Key
893
+ // For an LI: We see: LI p <br>
894
+ // For a P: We see: P p <br>
895
+ if((f===u.taDefaultWrap||"li"===f||"pre"===f||"div"===f)&&!/.+<br><br>/.test(e.innerHTML.trim())){var g=e.previousSibling;
896
+ //console.log('wrong....', ps);
897
+ // we need to remove this selection and fix the previousSibling up...
898
+ g&&(g.innerHTML=g.innerHTML+"<br><br>",angular.element(e).remove(),h.setSelectionToElementEnd(g))}}else
899
+ // new paragraph, br should be caught correctly
900
+ // shifted to nodeName here from tagName since it is more widely supported see: http://stackoverflow.com/questions/4878484/difference-between-tagname-and-nodename
901
+ //console.log('Enter', selection.nodeName, attrs.taDefaultWrap, selection.innerHTML.trim());
902
+ if(e.tagName.toLowerCase()!==u.taDefaultWrap&&"li"!==e.nodeName.toLowerCase()&&(""===e.innerHTML.trim()||"<br>"===e.innerHTML.trim())){
903
+ // Chrome starts with a <div><br></div> after an EnterKey
904
+ // so we replace this with the _defaultVal
905
+ var i=angular.element(z);angular.element(e).replaceWith(i),h.setSelectionToElementStart(i[0])}}var j=Z();""===z||""!==j.trim()&&"<br>"!==j.trim()?"<"!==j.substring(0,1)&&""!==u.taDefaultWrap:(ba(z),h.setSelectionToElementStart(r.children()[0]));var l=x!==a.keyCode&&L.test(a.keyCode);ha&&c.cancel(ha),ha=c(function(){aa(j,l,!0)},C.$options.debounce||400),l||(X=c(function(){B.$undoManager.push(j)},250)),x=a.keyCode}});
906
+ // when there is a change from a spelling correction in the browser, the only
907
+ // change that is seen is a 'input' and the $watch('html') sees nothing... So
908
+ // we added this element.on('input') to catch this change and call the _setViewValue()
909
+ // so the ngModel is updated and all works as it should.
910
+ var ia;
911
+ // Placeholders not supported on ie 8 and below
912
+ if(r.on("input",function(){Z()!==B.$viewValue&&(
913
+ // we wait a time now to allow the natural $watch('html') to handle this change
914
+ // and then after a 1 second delay, if there is still a difference we will do the
915
+ // _setViewValue() call.
916
+ /* istanbul ignore if: can't test */
917
+ ia&&c.cancel(ia),/* istanbul ignore next: cant' test? */
918
+ ia=c(function(){var b=a.saveSelection(),c=Z();c!==B.$viewValue&&
919
+ //console.log('_setViewValue');
920
+ //console.log('old:', ngModel.$viewValue);
921
+ //console.log('new:', _val);
922
+ aa(c,!0),
923
+ // if the savedSelection marker is gone at this point, we cannot restore the selection!!!
924
+ //console.log('rangy.restoreSelection', ngModel.$viewValue.length, _savedSelection);
925
+ 0!==B.$viewValue.length&&a.restoreSelection(b)},1e3))}),r.on("blur",f.events.blur=function(){G=!1,/* istanbul ignore else: if readonly don't update model */
926
+ F?(H=!0,// don't redo the whole thing, just check the placeholder logic
927
+ B.$render()):aa(void 0,void 0,!0)}),u.placeholder&&(g.ie>8||void 0===g.ie)){var ja;if(!u.id)throw"textAngular Error: An unique ID is required for placeholders to work";ja=m("#"+u.id+".placeholder-text:before",'content: "'+u.placeholder+'"'),f.$on("$destroy",function(){n(ja)})}r.on("focus",f.events.focus=function(){G=!0,r.removeClass("placeholder-text"),_()}),r.on("mouseup",f.events.mouseup=function(){var a=h.getSelection();a&&a.start.element===r[0]&&r.children().length&&h.setSelectionToElementStart(r.children()[0])}),
928
+ // prevent propagation on mousedown in editor, see #206
929
+ r.on("mousedown",f.events.mousedown=function(a,b){/* istanbul ignore else: this is for catching the jqLite testing*/
930
+ b&&angular.extend(a,b),a.stopPropagation()})}else{
931
+ // if a textarea or input just add in change and blur handlers, everything else is done by angulars input directive
932
+ r.on("change blur",f.events.change=f.events.blur=function(){F||B.$setViewValue(Z())}),r.on("keydown",f.events.keydown=function(a,b){
933
+ // Reference to http://stackoverflow.com/questions/6140632/how-to-handle-tab-in-textarea
934
+ /* istanbul ignore else: otherwise normal functionality */
935
+ if(/* istanbul ignore else: this is for catching the jqLite testing*/
936
+ b&&angular.extend(a,b),a.keyCode===S){// tab was pressed
937
+ // get caret position/selection
938
+ var c=this.selectionStart,d=this.selectionEnd,e=r.val();if(a.shiftKey){
939
+ // find \t
940
+ var f=e.lastIndexOf("\n",c),g=e.lastIndexOf("\t",c);g!==-1&&g>=f&&(
941
+ // set textarea value to: text before caret + tab + text after caret
942
+ r.val(e.substring(0,g)+e.substring(g+1)),
943
+ // put caret at right position again (add one for the tab)
944
+ this.selectionStart=this.selectionEnd=c-1)}else
945
+ // set textarea value to: text before caret + tab + text after caret
946
+ r.val(e.substring(0,c)+"\t"+e.substring(d)),
947
+ // put caret at right position again (add one for the tab)
948
+ this.selectionStart=this.selectionEnd=c+1;
949
+ // prevent the focus lose
950
+ a.preventDefault()}});var ka=function(a,b){for(var c="",d=0;d<b;d++)c+=a;return c},la=function(a,b,c){for(var d=0;d<a.length;d++)b.call(c,d,a[d])},ma=function(a,b){var c="",d=a.childNodes;
951
+ // tab out and add the <ul> or <ol> html piece
952
+ // now add on the </ol> or </ul> piece
953
+ return b++,c+=ka("\t",b-1)+a.outerHTML.substring(0,4),la(d,function(a,d){/* istanbul ignore next: browser catch */
954
+ var e=d.nodeName.toLowerCase();/* istanbul ignore next: not tested, and this was original code -- so not wanting to possibly cause an issue, leaving it... */
955
+ return"#comment"===e?void(c+="<!--"+d.nodeValue+"-->"):"#text"===e?void(c+=d.textContent):void(d.outerHTML&&(c+="ul"===e||"ol"===e?"\n"+ma(d,b):"\n"+ka("\t",b)+d.outerHTML))}),c+="\n"+ka("\t",b-1)+a.outerHTML.substring(a.outerHTML.lastIndexOf("<"))};
956
+ // handle formating of something like:
957
+ // <ol><!--First comment-->
958
+ // <li>Test Line 1<!--comment test list 1--></li>
959
+ // <ul><!--comment ul-->
960
+ // <li>Nested Line 1</li>
961
+ // <!--comment between nested lines--><li>Nested Line 2</li>
962
+ // </ul>
963
+ // <li>Test Line 3</li>
964
+ // </ol>
965
+ B.$formatters.unshift(function(a){
966
+ // tabulate the HTML so it looks nicer
967
+ //
968
+ // first get a list of the nodes...
969
+ // we do this by using the element parser...
970
+ //
971
+ // doing this -- which is simpiler -- breaks our tests...
972
+ //var _nodes=angular.element(htmlValue);
973
+ var b=angular.element("<div>"+a+"</div>")[0].childNodes;
974
+ // do the reformatting of the layout...
975
+ return b.length>0&&(a="",la(b,function(b,c){var d=c.nodeName.toLowerCase();/* istanbul ignore next: not tested, and this was original code -- so not wanting to possibly cause an issue, leaving it... */
976
+ // we aready have some content, so drop to a new line
977
+ // okay a set of list stuff we want to reformat in a nested way
978
+ return"#comment"===d?void(a+="<!--"+c.nodeValue+"-->"):"#text"===d?void(a+=c.textContent):void(c.outerHTML&&(a.length>0&&(a+="\n"),a+="ul"===d||"ol"===d?""+ma(c,0):""+c.outerHTML))})),a})}var na,oa=function(a,b){
979
+ // emit the drop event, pass the element, preventing should be done elsewhere
980
+ if(/* istanbul ignore else: this is for catching the jqLite testing*/
981
+ b&&angular.extend(a,b),!t&&!F){t=!0;var d;d=a.originalEvent?a.originalEvent.dataTransfer:a.dataTransfer,f.$emit("ta-drop-event",this,a,d),c(function(){t=!1,aa(void 0,void 0,!0)},100)}},pa=!1;
982
+ // changes to the model variable from outside the html/text inputs
983
+ B.$render=function(){/* istanbul ignore if: Catches rogue renders, hard to replicate in tests */
984
+ if(!pa){pa=!0;
985
+ // catch model being null or undefined
986
+ var a=B.$viewValue||"";
987
+ // if the editor isn't focused it needs to be updated, otherwise it's receiving user input
988
+ H||(/* istanbul ignore else: in other cases we don't care */
989
+ D&&G&&(
990
+ // update while focussed
991
+ r.removeClass("placeholder-text"),/* istanbul ignore next: don't know how to test this */
992
+ na&&c.cancel(na),na=c(function(){/* istanbul ignore if: Can't be bothered testing this... */
993
+ G||(r[0].focus(),h.setSelectionToElementEnd(r.children()[r.children().length-1])),na=void 0},1)),D?(
994
+ // blank
995
+ ba(
996
+ // WYSIWYG Mode
997
+ u.placeholder?""===a?z:a:""===a?z:a),
998
+ // if in WYSIWYG and readOnly we kill the use of links by clicking
999
+ F?r.off("drop",oa):(_(),r.on("drop",oa))):"textarea"!==r[0].tagName.toLowerCase()&&"input"!==r[0].tagName.toLowerCase()?
1000
+ // make sure the end user can SEE the html code as a display. This is a read-only display element
1001
+ ba(l(a)):
1002
+ // only for input and textarea inputs
1003
+ r.val(a)),D&&u.placeholder&&(""===a?G?r.removeClass("placeholder-text"):r.addClass("placeholder-text"):r.removeClass("placeholder-text")),pa=H=!1}},u.taReadonly&&(
1004
+ //set initial value
1005
+ F=f.$eval(u.taReadonly),F?(r.addClass("ta-readonly"),
1006
+ // we changed to readOnly mode (taReadonly='true')
1007
+ "textarea"!==r[0].tagName.toLowerCase()&&"input"!==r[0].tagName.toLowerCase()||r.attr("disabled","disabled"),void 0!==r.attr("contenteditable")&&r.attr("contenteditable")&&r.removeAttr("contenteditable")):(r.removeClass("ta-readonly"),
1008
+ // we changed to NOT readOnly mode (taReadonly='false')
1009
+ "textarea"===r[0].tagName.toLowerCase()||"input"===r[0].tagName.toLowerCase()?r.removeAttr("disabled"):D&&r.attr("contenteditable","true")),
1010
+ // taReadonly only has an effect if the taBind element is an input or textarea or has contenteditable='true' on it.
1011
+ // Otherwise it is readonly by default
1012
+ f.$watch(u.taReadonly,function(a,b){b!==a&&(a?(r.addClass("ta-readonly"),
1013
+ // we changed to readOnly mode (taReadonly='true')
1014
+ "textarea"!==r[0].tagName.toLowerCase()&&"input"!==r[0].tagName.toLowerCase()||r.attr("disabled","disabled"),void 0!==r.attr("contenteditable")&&r.attr("contenteditable")&&r.removeAttr("contenteditable"),
1015
+ // turn ON selector click handlers
1016
+ angular.forEach(j,function(a){r.find(a).on("click",$)}),r.off("drop",oa)):(r.removeClass("ta-readonly"),
1017
+ // we changed to NOT readOnly mode (taReadonly='false')
1018
+ "textarea"===r[0].tagName.toLowerCase()||"input"===r[0].tagName.toLowerCase()?r.removeAttr("disabled"):D&&r.attr("contenteditable","true"),
1019
+ // remove the selector click handlers
1020
+ angular.forEach(j,function(a){r.find(a).off("click",$)}),r.on("drop",oa)),F=a)})),
1021
+ // Initialise the selectableElements
1022
+ // if in WYSIWYG and readOnly we kill the use of links by clicking
1023
+ D&&!F&&(angular.forEach(j,function(a){r.find(a).on("click",$)}),r.on("drop",oa))}}}]);
1024
+ // this global var is used to prevent multiple fires of the drop event. Needs to be global to the textAngular file.
1025
+ var t=!1,u=angular.module("textAngular",["ngSanitize","textAngularSetup","textAngular.factories","textAngular.DOM","textAngular.validators","textAngular.taBind"]);//This makes ngSanitize required
1026
+ return u.config([function(){
1027
+ // clear taTools variable. Just catches testing and any other time that this config may run multiple times...
1028
+ angular.forEach(e,function(a,b){delete e[b]})}]),u.directive("textAngular",["$compile","$timeout","taOptions","taSelection","taExecCommand","textAngularManager","$document","$animate","$log","$q","$parse",function(b,c,d,e,f,g,h,i,j,k,l){return{require:"?ngModel",scope:{},restrict:"EA",priority:2,// So we override validators correctly
1029
+ link:function(m,n,o,p){
1030
+ // all these vars should not be accessable outside this directive
1031
+ var q,r,s,t,u,v,w,x,y,z,A,B,C=o.serial?o.serial:Math.floor(1e16*Math.random());m._name=o.name?o.name:"textAngularEditor"+C;var D=function(a,b,d){c(function(){a.one(b,d)},100)};if(y=f(o.taDefaultWrap),
1032
+ // get the settings from the defaults and add our specific functions that need to be on the scope
1033
+ angular.extend(m,angular.copy(d),{
1034
+ // wraps the selection in the provided tag / execCommand function. Should only be called in WYSIWYG mode.
1035
+ wrapSelection:function(a,b,c){
1036
+ // we restore the saved selection that was saved when focus was lost
1037
+ /* NOT FUNCTIONAL YET */
1038
+ /* textAngularManager.restoreFocusSelection(scope._name, scope); */
1039
+ "undo"===a.toLowerCase()?m["$undoTaBindtaTextElement"+C]():"redo"===a.toLowerCase()?m["$redoTaBindtaTextElement"+C]():(
1040
+ // catch errors like FF erroring when you try to force an undo with nothing done
1041
+ y(a,!1,b,m.defaultTagAttributes),c&&
1042
+ // re-apply the selectable tool events
1043
+ m["reApplyOnSelectorHandlerstaTextElement"+C](),
1044
+ // refocus on the shown display element, this fixes a display bug when using :focus styles to outline the box.
1045
+ // You still have focus on the text/html input it just doesn't show up
1046
+ m.displayElements.text[0].focus())},showHtml:m.$eval(o.taShowHtml)||!1}),
1047
+ // setup the options from the optional attributes
1048
+ o.taFocussedClass&&(m.classes.focussed=o.taFocussedClass),o.taTextEditorClass&&(m.classes.textEditor=o.taTextEditorClass),o.taHtmlEditorClass&&(m.classes.htmlEditor=o.taHtmlEditorClass),o.taDefaultTagAttributes)try{
1049
+ // TODO: This should use angular.merge to enhance functionality once angular 1.4 is required
1050
+ angular.extend(m.defaultTagAttributes,angular.fromJson(o.taDefaultTagAttributes))}catch(a){j.error(a)}
1051
+ // optional setup functions
1052
+ o.taTextEditorSetup&&(m.setup.textEditorSetup=m.$parent.$eval(o.taTextEditorSetup)),o.taHtmlEditorSetup&&(m.setup.htmlEditorSetup=m.$parent.$eval(o.taHtmlEditorSetup)),
1053
+ // optional fileDropHandler function
1054
+ o.taFileDrop?m.fileDropHandler=m.$parent.$eval(o.taFileDrop):m.fileDropHandler=m.defaultFileDropHandler,w=n[0].innerHTML,
1055
+ // clear the original content
1056
+ n[0].innerHTML="",
1057
+ // Setup the HTML elements as variable references for use later
1058
+ m.displayElements={
1059
+ // we still need the hidden input even with a textarea as the textarea may have invalid/old input in it,
1060
+ // wheras the input will ALLWAYS have the correct value.
1061
+ forminput:angular.element("<input type='hidden' tabindex='-1' style='display: none;'>"),html:angular.element("<textarea></textarea>"),text:angular.element("<div></div>"),
1062
+ // other toolbased elements
1063
+ scrollWindow:angular.element("<div class='ta-scroll-window'></div>"),popover:angular.element('<div class="popover fade bottom" style="max-width: none; width: 305px;"></div>'),popoverArrow:angular.element('<div class="arrow"></div>'),popoverContainer:angular.element('<div class="popover-content"></div>'),resize:{overlay:angular.element('<div class="ta-resizer-handle-overlay"></div>'),background:angular.element('<div class="ta-resizer-handle-background"></div>'),anchors:[angular.element('<div class="ta-resizer-handle-corner ta-resizer-handle-corner-tl"></div>'),angular.element('<div class="ta-resizer-handle-corner ta-resizer-handle-corner-tr"></div>'),angular.element('<div class="ta-resizer-handle-corner ta-resizer-handle-corner-bl"></div>'),angular.element('<div class="ta-resizer-handle-corner ta-resizer-handle-corner-br"></div>')],info:angular.element('<div class="ta-resizer-handle-info"></div>')}},
1064
+ // Setup the popover
1065
+ m.displayElements.popover.append(m.displayElements.popoverArrow),m.displayElements.popover.append(m.displayElements.popoverContainer),m.displayElements.scrollWindow.append(m.displayElements.popover),m.displayElements.popover.on("mousedown",function(a,b){/* istanbul ignore else: this is for catching the jqLite testing*/
1066
+ // this prevents focusout from firing on the editor when clicking anything in the popover
1067
+ return b&&angular.extend(a,b),a.preventDefault(),!1}),/* istanbul ignore next: popover resize and scroll events handled */
1068
+ m.handlePopoverEvents=function(){"block"===m.displayElements.popover.css("display")&&(B&&c.cancel(B),B=c(function(){
1069
+ //console.log('resize', scope.displayElements.popover.css('display'));
1070
+ m.reflowPopover(m.resizeElement),m.reflowResizeOverlay(m.resizeElement)},100))},/* istanbul ignore next: browser resize check */
1071
+ angular.element(window).on("resize",m.handlePopoverEvents),/* istanbul ignore next: browser scroll check */
1072
+ angular.element(window).on("scroll",m.handlePopoverEvents);
1073
+ // we want to know if a given node has a scrollbar!
1074
+ // credit to lotif on http://stackoverflow.com/questions/4880381/check-whether-html-element-has-scrollbars
1075
+ var E=function(a){var b,c={vertical:!1,horizontal:!1};try{if(b=window.getComputedStyle(a),null===b)return c}catch(a){/* istanbul ignore next: error handler */
1076
+ return c}var d=b["overflow-y"],e=b["overflow-x"];return{vertical:("scroll"===d||"auto"===d)&&/* istanbul ignore next: not tested */
1077
+ a.scrollHeight>a.clientHeight,horizontal:("scroll"===e||"auto"===e)&&/* istanbul ignore next: not tested */
1078
+ a.scrollWidth>a.clientWidth}};
1079
+ // getScrollTop
1080
+ //
1081
+ // we structure this so that it can climb the parents of the _el and when it finds
1082
+ // one with scrollbars, it adds an EventListener, so that no matter how the
1083
+ // DOM is structured in the user APP, if there is a scrollbar not as part of the
1084
+ // ta-scroll-window, we will still capture the 'scroll' events...
1085
+ // and handle the scroll event properly and do the resize, etc.
1086
+ //
1087
+ m.getScrollTop=function(a,b){var c=a.scrollTop;/* istanbul ignore next: triggered only if has scrollbar and scrolled */
1088
+ /* istanbul ignore next: triggered only if has scrollbar */
1089
+ // remove element eventListener
1090
+ /* istanbul ignore next: triggered only if has scrollbar and scrolled */
1091
+ /* istanbul ignore else: catches only if no scroll */
1092
+ return"undefined"==typeof c&&(c=0),b&&E(a).vertical&&(a.removeEventListener("scroll",m._scrollListener,!1),a.addEventListener("scroll",m._scrollListener,!1)),0!==c?{node:a.nodeName,top:c}:a.parentNode?m.getScrollTop(a.parentNode,b):{node:"<none>",top:0}},
1093
+ // define the popover show and hide functions
1094
+ m.showPopover=function(a){m.getScrollTop(m.displayElements.scrollWindow[0],!0),m.displayElements.popover.css("display","block"),
1095
+ // we must use a $timeout here, or the css change to the
1096
+ // displayElements.resize.overlay will not take!!!
1097
+ // WHY???
1098
+ c(function(){m.displayElements.resize.overlay.css("display","block")}),m.resizeElement=a,m.reflowPopover(a),i.addClass(m.displayElements.popover,"in"),D(h.find("body"),"click keyup",function(){m.hidePopover()})},/* istanbul ignore next: browser scroll event handler */
1099
+ m._scrollListener=function(a,b){m.handlePopoverEvents()},m.reflowPopover=function(a){var b=m.getScrollTop(m.displayElements.scrollWindow[0],!1),c=a[0].offsetTop-b.top;
1100
+ //var spaceBelowImage = scope.displayElements.text[0].offsetHeight - _el[0].offsetHeight - spaceAboveImage;
1101
+ //console.log(spaceAboveImage, spaceBelowImage);
1102
+ /* istanbul ignore if: catches only if near bottom of editor */
1103
+ c<51?(m.displayElements.popover.css("top",a[0].offsetTop+a[0].offsetHeight+m.displayElements.scrollWindow[0].scrollTop+"px"),m.displayElements.popover.removeClass("top").addClass("bottom")):(m.displayElements.popover.css("top",a[0].offsetTop-54+m.displayElements.scrollWindow[0].scrollTop+"px"),m.displayElements.popover.removeClass("bottom").addClass("top"));var d=m.displayElements.text[0].offsetWidth-m.displayElements.popover[0].offsetWidth,e=a[0].offsetLeft+a[0].offsetWidth/2-m.displayElements.popover[0].offsetWidth/2,f=Math.max(0,Math.min(d,e)),g=Math.min(e,Math.max(0,e-d))-11;f+=window.scrollX,g-=window.scrollX,m.displayElements.popover.css("left",f+"px"),m.displayElements.popoverArrow.css("margin-left",g+"px")},m.hidePopover=function(){m.displayElements.popover.css("display","none"),m.displayElements.popoverContainer.attr("style",""),m.displayElements.popoverContainer.attr("class","popover-content"),m.displayElements.popover.removeClass("in"),m.displayElements.resize.overlay.css("display","none")},
1104
+ // setup the resize overlay
1105
+ m.displayElements.resize.overlay.append(m.displayElements.resize.background),angular.forEach(m.displayElements.resize.anchors,function(a){m.displayElements.resize.overlay.append(a)}),m.displayElements.resize.overlay.append(m.displayElements.resize.info),m.displayElements.scrollWindow.append(m.displayElements.resize.overlay),
1106
+ // A click event on the resize.background will now shift the focus to the editor
1107
+ /* istanbul ignore next: click on the resize.background to focus back to editor */
1108
+ m.displayElements.resize.background.on("click",function(a){m.displayElements.text[0].focus()}),
1109
+ // define the show and hide events
1110
+ m.reflowResizeOverlay=function(a){a=angular.element(a)[0],m.displayElements.resize.overlay.css({display:"block",left:a.offsetLeft-5+"px",top:a.offsetTop-5+"px",width:a.offsetWidth+10+"px",height:a.offsetHeight+10+"px"}),m.displayElements.resize.info.text(a.offsetWidth+" x "+a.offsetHeight)},/* istanbul ignore next: pretty sure phantomjs won't test this */
1111
+ m.showResizeOverlay=function(a){var b=h.find("body");z=function(c){var d={width:parseInt(a.attr("width")),height:parseInt(a.attr("height")),x:c.clientX,y:c.clientY};(void 0===d.width||isNaN(d.width))&&(d.width=a[0].offsetWidth),(void 0===d.height||isNaN(d.height))&&(d.height=a[0].offsetHeight),m.hidePopover();var e=d.height/d.width,f=function(b){function c(a){return Math.round(Math.max(0,a))}
1112
+ // calculate new size
1113
+ var f={x:Math.max(0,d.width+(b.clientX-d.x)),y:Math.max(0,d.height+(b.clientY-d.y))},g=void 0!==o.taResizeForceAspectRatio,h=o.taResizeMaintainAspectRatio,i=g||h&&!b.shiftKey;if(i){var j=f.y/f.x;f.x=e>j?f.x:f.y/e,f.y=e>j?f.x*e:f.y}var k=angular.element(a);k.css("height",c(f.y)+"px"),k.css("width",c(f.x)+"px"),
1114
+ // reflow the popover tooltip
1115
+ m.reflowResizeOverlay(a)};b.on("mousemove",f),D(b,"mouseup",function(a){a.preventDefault(),a.stopPropagation(),b.off("mousemove",f),
1116
+ // at this point, we need to force the model to update! since the css has changed!
1117
+ // this fixes bug: #862 - we now hide the popover -- as this seems more consitent.
1118
+ // there are still issues under firefox, the window does not repaint. -- not sure
1119
+ // how best to resolve this, but clicking anywhere works.
1120
+ m.$apply(function(){m.hidePopover(),m.updateTaBindtaTextElement()},100)}),c.stopPropagation(),c.preventDefault()},m.displayElements.resize.anchors[3].off("mousedown"),m.displayElements.resize.anchors[3].on("mousedown",z),m.reflowResizeOverlay(a),D(b,"click",function(){m.hideResizeOverlay()})},/* istanbul ignore next: pretty sure phantomjs won't test this */
1121
+ m.hideResizeOverlay=function(){m.displayElements.resize.anchors[3].off("mousedown",z),m.displayElements.resize.overlay.css("display","none")},
1122
+ // allow for insertion of custom directives on the textarea and div
1123
+ m.setup.htmlEditorSetup(m.displayElements.html),m.setup.textEditorSetup(m.displayElements.text),m.displayElements.html.attr({id:"taHtmlElement"+C,"ng-show":"showHtml","ta-bind":"ta-bind","ng-model":"html","ng-model-options":n.attr("ng-model-options")}),m.displayElements.text.attr({id:"taTextElement"+C,contentEditable:"true","ta-bind":"ta-bind","ng-model":"html","ng-model-options":n.attr("ng-model-options")}),m.displayElements.scrollWindow.attr({"ng-hide":"showHtml"}),o.taDefaultWrap&&
1124
+ // taDefaultWrap is only applied to the text and not the html view
1125
+ m.displayElements.text.attr("ta-default-wrap",o.taDefaultWrap),o.taUnsafeSanitizer&&(m.displayElements.text.attr("ta-unsafe-sanitizer",o.taUnsafeSanitizer),m.displayElements.html.attr("ta-unsafe-sanitizer",o.taUnsafeSanitizer)),o.taKeepStyles&&(m.displayElements.text.attr("ta-keep-styles",o.taKeepStyles),m.displayElements.html.attr("ta-keep-styles",o.taKeepStyles)),
1126
+ // add the main elements to the origional element
1127
+ m.displayElements.scrollWindow.append(m.displayElements.text),n.append(m.displayElements.scrollWindow),n.append(m.displayElements.html),m.displayElements.forminput.attr("name",m._name),n.append(m.displayElements.forminput),o.tabindex&&(n.removeAttr("tabindex"),m.displayElements.text.attr("tabindex",o.tabindex),m.displayElements.html.attr("tabindex",o.tabindex)),o.placeholder&&(m.displayElements.text.attr("placeholder",o.placeholder),m.displayElements.html.attr("placeholder",o.placeholder)),o.taDisabled&&(m.displayElements.text.attr("ta-readonly","disabled"),m.displayElements.html.attr("ta-readonly","disabled"),m.disabled=m.$parent.$eval(o.taDisabled),m.$parent.$watch(o.taDisabled,function(a){m.disabled=a,m.disabled?n.addClass(m.classes.disabled):n.removeClass(m.classes.disabled)})),o.taPaste&&(m._pasteHandler=function(a){return l(o.taPaste)(m.$parent,{$html:a})},m.displayElements.text.attr("ta-paste","_pasteHandler($html)")),
1128
+ // compile the scope with the text and html elements only - if we do this with the main element it causes a compile loop
1129
+ b(m.displayElements.scrollWindow)(m),b(m.displayElements.html)(m),m.updateTaBindtaTextElement=m["updateTaBindtaTextElement"+C],m.updateTaBindtaHtmlElement=m["updateTaBindtaHtmlElement"+C],
1130
+ // add the classes manually last
1131
+ n.addClass("ta-root"),m.displayElements.scrollWindow.addClass("ta-text ta-editor "+m.classes.textEditor),m.displayElements.html.addClass("ta-html ta-editor "+m.classes.htmlEditor);var F=function(a,b){/* istanbul ignore next: this is only here because of a bug in rangy where rangy.saveSelection() has cleared the state */
1132
+ b!==h[0].queryCommandState(a)&&h[0].execCommand(a,!1,null)};
1133
+ // used in the toolbar actions
1134
+ m._actionRunning=!1;var G=!1;
1135
+ // changes to the model variable from outside the html/text inputs
1136
+ // if no ngModel, then the only input is from inside text-angular
1137
+ if(m.startAction=function(){var b=!1,c=!1,d=!1,e=!1;
1138
+ //console.log('B', $document[0].queryCommandState('bold'), 'I', $document[0].queryCommandState('italic'), '_', $document[0].queryCommandState('underline'), 'S', $document[0].queryCommandState('strikeThrough') );
1139
+ //console.log('B', _beforeStateBold, 'I', _beforeStateItalic, '_', _beforeStateUnderline, 'S', _beforeStateStrikethough);
1140
+ // if rangy library is loaded return a function to reload the current selection
1141
+ // rangy.saveSelection() clear the state of bold, italic, underline, strikethrough
1142
+ // so we reset them here....!!!
1143
+ // this fixes bugs #423, #1129, #1105, #693 which are actually rangy bugs!
1144
+ return m._actionRunning=!0,b=h[0].queryCommandState("bold"),c=h[0].queryCommandState("italic"),d=h[0].queryCommandState("underline"),e=h[0].queryCommandState("strikeThrough"),G=a.saveSelection(),F("bold",b),F("italic",c),F("underline",d),F("strikeThrough",e),function(){G&&a.restoreSelection(G)}},m.endAction=function(){m._actionRunning=!1,G&&(m.showHtml?m.displayElements.html[0].focus():m.displayElements.text[0].focus(),
1145
+ // rangy.restoreSelection(_savedSelection);
1146
+ a.removeMarkers(G)),G=!1,m.updateSelectedStyles(),
1147
+ // only update if in text or WYSIWYG mode
1148
+ m.showHtml||m["updateTaBindtaTextElement"+C]()},
1149
+ // note that focusout > focusin is called everytime we click a button - except bad support: http://www.quirksmode.org/dom/events/blurfocus.html
1150
+ // cascades to displayElements.text and displayElements.html automatically.
1151
+ u=function(a){m.focussed=!0,n.addClass(m.classes.focussed),/******* NOT FUNCTIONAL YET
1152
+ if (e.target.id === 'taTextElement' + _serial) {
1153
+ console.log('_focusin taTextElement');
1154
+ // we only do this if NOT focussed
1155
+ textAngularManager.restoreFocusSelection(scope._name);
1156
+ }
1157
+ *******/
1158
+ x.focus(),n.triggerHandler("focus"),
1159
+ // we call editorScope.updateSelectedStyles() here because we want the toolbar to be focussed
1160
+ // as soon as we have focus. Otherwise this only happens on mousedown or keydown etc...
1161
+ /* istanbul ignore else: don't run if already running */
1162
+ m.updateSelectedStyles&&!m._bUpdateSelectedStyles&&
1163
+ // we don't set editorScope._bUpdateSelectedStyles here, because we do not want the
1164
+ // updateSelectedStyles() to run twice which it will do after 200 msec if we have
1165
+ // set editorScope._bUpdateSelectedStyles
1166
+ //
1167
+ // WOW, normally I would do a scope.$apply here, but this causes ERRORs when doing tests!
1168
+ c(function(){m.updateSelectedStyles()},0)},m.displayElements.html.on("focus",u),m.displayElements.text.on("focus",u),v=function(a){/****************** NOT FUNCTIONAL YET
1169
+ try {
1170
+ var _s = rangy.getSelection();
1171
+ if (_s) {
1172
+ // we save the selection when we loose focus so that if do a wrapSelection, the
1173
+ // apropriate selection in the editor is restored before action.
1174
+ var _savedFocusRange = rangy.saveRange(_s.getRangeAt(0));
1175
+ textAngularManager.saveFocusSelection(scope._name, _savedFocusRange);
1176
+ }
1177
+ } catch(error) { }
1178
+ *****************/
1179
+ // if we are NOT runnig an action and have NOT focussed again on the text etc then fire the blur events
1180
+ // to prevent multiple apply error defer to next seems to work.
1181
+ return m._actionRunning||h[0].activeElement===m.displayElements.html[0]||h[0].activeElement===m.displayElements.text[0]||(n.removeClass(m.classes.focussed),x.unfocus(),c(function(){m._bUpdateSelectedStyles=!1,n.triggerHandler("blur"),m.focussed=!1},0)),a.preventDefault(),!1},m.displayElements.html.on("blur",v),m.displayElements.text.on("blur",v),m.displayElements.text.on("paste",function(a){n.triggerHandler("paste",a)}),
1182
+ // Setup the default toolbar tools, this way allows the user to add new tools like plugins.
1183
+ // This is on the editor for future proofing if we find a better way to do this.
1184
+ m.queryFormatBlockState=function(a){
1185
+ // $document[0].queryCommandValue('formatBlock') errors in Firefox if we call this when focussed on the textarea
1186
+ return!m.showHtml&&a.toLowerCase()===h[0].queryCommandValue("formatBlock").toLowerCase()},m.queryCommandState=function(a){
1187
+ // $document[0].queryCommandValue('formatBlock') errors in Firefox if we call this when focussed on the textarea
1188
+ return m.showHtml?"":h[0].queryCommandState(a)},m.switchView=function(){m.showHtml=!m.showHtml,i.enabled(!1,m.displayElements.html),i.enabled(!1,m.displayElements.text),
1189
+ //Show the HTML view
1190
+ /* istanbul ignore next: ngModel exists check */
1191
+ /* THIS is not the correct thing to do, here....
1192
+ The ngModel is correct, but it is not formatted as the user as done it...
1193
+ var _model;
1194
+ if (ngModel) {
1195
+ _model = ngModel.$viewValue;
1196
+ } else {
1197
+ _model = scope.html;
1198
+ }
1199
+ var _html = scope.displayElements.html[0].value;
1200
+ if (getDomFromHtml(_html).childElementCount !== getDomFromHtml(_model).childElementCount) {
1201
+ // the model and the html do not agree
1202
+ // they can get out of sync and when they do, we correct that here...
1203
+ scope.displayElements.html.val(_model);
1204
+ }
1205
+ */
1206
+ m.showHtml?
1207
+ //defer until the element is visible
1208
+ c(function(){
1209
+ // [0] dereferences the DOM object from the angular.element
1210
+ return i.enabled(!0,m.displayElements.html),i.enabled(!0,m.displayElements.text),m.displayElements.html[0].focus()},100):
1211
+ //Show the WYSIWYG view
1212
+ //defer until the element is visible
1213
+ c(function(){
1214
+ // [0] dereferences the DOM object from the angular.element
1215
+ return i.enabled(!0,m.displayElements.html),i.enabled(!0,m.displayElements.text),m.displayElements.text[0].focus()},100)},o.ngModel){var H=!0;p.$render=function(){if(H){
1216
+ // we need this firstRun to set the originalContents otherwise it gets overrided by the setting of ngModel to undefined from NaN
1217
+ H=!1;
1218
+ // if view value is null or undefined initially and there was original content, set to the original content
1219
+ var a=m.$parent.$eval(o.ngModel);void 0!==a&&null!==a||!w||""===w||
1220
+ // on passing through to taBind it will be sanitised
1221
+ p.$setViewValue(w)}m.displayElements.forminput.val(p.$viewValue),
1222
+ // if the editors aren't focused they need to be updated, otherwise they are doing the updating
1223
+ m.html=p.$viewValue||""},
1224
+ // trigger the validation calls
1225
+ n.attr("required")&&(p.$validators.required=function(a,b){var c=a||b;return!(!c||""===c.trim())})}else
1226
+ // if no ngModel then update from the contents of the origional html.
1227
+ m.displayElements.forminput.val(w),m.html=w;if(
1228
+ // changes from taBind back up to here
1229
+ m.$watch("html",function(a,b){a!==b&&(o.ngModel&&p.$viewValue!==a&&p.$setViewValue(a),m.displayElements.forminput.val(a))}),o.taTargetToolbars)x=g.registerEditor(m._name,m,o.taTargetToolbars.split(","));else{var I=angular.element('<div text-angular-toolbar name="textAngularToolbar'+C+'">');
1230
+ // passthrough init of toolbar options
1231
+ o.taToolbar&&I.attr("ta-toolbar",o.taToolbar),o.taToolbarClass&&I.attr("ta-toolbar-class",o.taToolbarClass),o.taToolbarGroupClass&&I.attr("ta-toolbar-group-class",o.taToolbarGroupClass),o.taToolbarButtonClass&&I.attr("ta-toolbar-button-class",o.taToolbarButtonClass),o.taToolbarActiveButtonClass&&I.attr("ta-toolbar-active-button-class",o.taToolbarActiveButtonClass),o.taFocussedClass&&I.attr("ta-focussed-class",o.taFocussedClass),n.prepend(I),b(I)(m.$parent),x=g.registerEditor(m._name,m,["textAngularToolbar"+C])}m.$on("$destroy",function(){g.unregisterEditor(m._name),angular.element(window).off("blur"),angular.element(window).off("resize",m.handlePopoverEvents),angular.element(window).off("scroll",m.handlePopoverEvents)}),
1232
+ // catch element select event and pass to toolbar tools
1233
+ m.$on("ta-element-select",function(a,b){x.triggerElementSelect(a,b)&&m["reApplyOnSelectorHandlerstaTextElement"+C]()}),/******************* no working fully
1234
+ var distanceFromPoint = function (px, py, x, y) {
1235
+ return Math.sqrt((px-x)*(px-x)+(py-y)*(py-y));
1236
+ };
1237
+ // because each object is a rectangle and we have a single point,
1238
+ // we need to give priority if the point is inside the rectangle
1239
+ var getPositionDistance = function(el, x, y) {
1240
+ var range = document.createRange();
1241
+ range.selectNode(el);
1242
+ var rect = range.getBoundingClientRect();
1243
+ console.log(el, rect);
1244
+ range.detach();
1245
+ var bcr = rect;
1246
+ // top left
1247
+ var d1 = distanceFromPoint(bcr.left, bcr.top, x, y);
1248
+ // bottom left
1249
+ var d2 = distanceFromPoint(bcr.left, bcr.bottom, x, y);
1250
+ // top right
1251
+ var d3 = distanceFromPoint(bcr.right, bcr.top, x, y);
1252
+ // bottom right
1253
+ var d4 = distanceFromPoint(bcr.right, bcr.bottom, x, y);
1254
+ return Math.min(d1, d2, d3, d4);
1255
+ };
1256
+ var findClosest = function(el, minElement, maxDistance, x, y) {
1257
+ var _d=0;
1258
+ for (var i = 0; i < el.childNodes.length; i++) {
1259
+ var _n = el.childNodes[i];
1260
+ if (!_n.childNodes.length) {
1261
+ _d = getPositionDistance(_n, x, y);
1262
+ //console.log(_n, _n.childNodes, _d);
1263
+ if (_d < maxDistance) {
1264
+ maxDistance = _d;
1265
+ minElement = _n;
1266
+ }
1267
+ }
1268
+ var res = findClosest(_n, minElement, maxDistance, x, y);
1269
+ if (res.max < maxDistance) {
1270
+ maxDistance = res.max;
1271
+ minElement = res.min;
1272
+ }
1273
+ }
1274
+ return { max: maxDistance, min: minElement };
1275
+ };
1276
+ var getClosestElement = function (el, x, y) {
1277
+ return findClosest(el, null, 12341234124, x, y);
1278
+ };
1279
+ ****************/
1280
+ m.$on("ta-drop-event",function(a,b,d,f){f&&f.files&&f.files.length>0?(m.displayElements.text[0].focus(),
1281
+ // we must set the location of the drop!
1282
+ //console.log(dropEvent.clientX, dropEvent.clientY, dropEvent.target);
1283
+ e.setSelectionToElementEnd(d.target),angular.forEach(f.files,function(a){
1284
+ // taking advantage of boolean execution, if the fileDropHandler returns true, nothing else after it is executed
1285
+ // If it is false then execute the defaultFileDropHandler if the fileDropHandler is NOT the default one
1286
+ // Once one of these has been executed wrap the result as a promise, if undefined or variable update the taBind, else we should wait for the promise
1287
+ try{k.when(m.fileDropHandler(a,m.wrapSelection)||m.fileDropHandler!==m.defaultFileDropHandler&&k.when(m.defaultFileDropHandler(a,m.wrapSelection))).then(function(){m["updateTaBindtaTextElement"+C]()})}catch(a){j.error(a)}}),d.preventDefault(),d.stopPropagation()):c(function(){m["updateTaBindtaTextElement"+C]()},0)}),
1288
+ // the following is for applying the active states to the tools that support it
1289
+ m._bUpdateSelectedStyles=!1,/* istanbul ignore next: browser window/tab leave check */
1290
+ angular.element(window).on("blur",function(){m._bUpdateSelectedStyles=!1,m.focussed=!1}),
1291
+ // loop through all the tools polling their activeState function if it exists
1292
+ m.updateSelectedStyles=function(){var a;/* istanbul ignore next: This check is to ensure multiple timeouts don't exist */
1293
+ A&&c.cancel(A),
1294
+ // test if the common element ISN'T the root ta-text node
1295
+ void 0!==(a=e.getSelectionElement())&&a.parentNode!==m.displayElements.text[0]?x.updateSelectedStyles(angular.element(a)):x.updateSelectedStyles(),
1296
+ // used to update the active state when a key is held down, ie the left arrow
1297
+ /* istanbul ignore else: browser only check */
1298
+ m._bUpdateSelectedStyles&&(A=c(m.updateSelectedStyles,200))},
1299
+ // start updating on keydown
1300
+ q=function(){/* istanbul ignore next: ie catch */
1301
+ /* istanbul ignore next: ie catch */
1302
+ /* istanbul ignore else: don't run if already running */
1303
+ return m.focussed?void(m._bUpdateSelectedStyles||(m._bUpdateSelectedStyles=!0,m.$apply(function(){m.updateSelectedStyles()}))):void(m._bUpdateSelectedStyles=!1)},m.displayElements.html.on("keydown",q),m.displayElements.text.on("keydown",q),
1304
+ // stop updating on key up and update the display/model
1305
+ r=function(){m._bUpdateSelectedStyles=!1},m.displayElements.html.on("keyup",r),m.displayElements.text.on("keyup",r),
1306
+ // stop updating on key up and update the display/model
1307
+ s=function(a,b){
1308
+ // bug fix for Firefox. If we are selecting a <a> already, any characters will
1309
+ // be added within the <a> which is bad!
1310
+ /* istanbul ignore next: don't see how to test this... */
1311
+ if(e.getSelection){var c=e.getSelection();
1312
+ // in a weird case (can't reproduce) taSelection.getSelectionElement() can be undefined!!
1313
+ // this comes from range.commonAncestorContainer;
1314
+ // so I check for this here which fixes the error case
1315
+ e.getSelectionElement()&&"a"===e.getSelectionElement().nodeName.toLowerCase()&&(
1316
+ // check and see if we are at the edge of the <a>
1317
+ 3===c.start.element.nodeType&&c.start.element.textContent.length===c.end.offset&&
1318
+ // we are at the end of the <a>!!!
1319
+ // so move the selection to after the <a>!!
1320
+ e.setSelectionAfterElement(e.getSelectionElement()),3===c.start.element.nodeType&&0===c.start.offset&&
1321
+ // we are at the start of the <a>!!!
1322
+ // so move the selection before the <a>!!
1323
+ e.setSelectionBeforeElement(e.getSelectionElement()))}/* istanbul ignore else: this is for catching the jqLite testing*/
1324
+ b&&angular.extend(a,b),m.$apply(function(){if(x.sendKeyCommand(a))/* istanbul ignore else: don't run if already running */
1325
+ return m._bUpdateSelectedStyles||m.updateSelectedStyles(),a.preventDefault(),!1})},m.displayElements.html.on("keypress",s),m.displayElements.text.on("keypress",s),
1326
+ // update the toolbar active states when we click somewhere in the text/html boxed
1327
+ t=function(){
1328
+ // ensure only one execution of updateSelectedStyles()
1329
+ m._bUpdateSelectedStyles=!1,
1330
+ // for some reason, unless we do a $timeout here, after a _mouseup when the line is
1331
+ // highlighted, and instead use a scope.$apply(function(){ scope.updateSelectedStyles(); });
1332
+ // doesn't work properly, so we replaced this with:
1333
+ /* istanbul ignore next: not tested */
1334
+ c(function(){m.updateSelectedStyles()},0)},m.displayElements.html.on("mouseup",t),m.displayElements.text.on("mouseup",t)}}}]),u.service("textAngularManager",["taToolExecuteAction","taTools","taRegisterTool","$interval","$rootScope","$log",function(a,b,c,d,e,g){
1335
+ // this service is used to manage all textAngular editors and toolbars.
1336
+ // All publicly published functions that modify/need to access the toolbar or editor scopes should be in here
1337
+ // these contain references to all the editors and toolbars that have been initialised in this app
1338
+ var h,i={},j={},k=0,l=function(a){angular.forEach(j,function(b){b.editorFunctions.updateSelectedStyles(a)})},m=50,n=function(){k=Date.now(),/* istanbul ignore next: setup a one time updateStyles() */
1339
+ h=d(function(){l(),h=void 0},m,1)};/* istanbul ignore next: make sure clean up on destroy */
1340
+ e.$on("destroy",function(){h&&(d.cancel(h),h=void 0)});var o=function(){Math.abs(Date.now()-k)>m&&
1341
+ // we have already triggered the updateStyles a long time back... so setup it again...
1342
+ n()};
1343
+ // when we focus into a toolbar, we need to set the TOOLBAR's $parent to be the toolbars it's linked to.
1344
+ // We also need to set the tools to be updated to be the toolbars...
1345
+ return{
1346
+ // register an editor and the toolbars that it is affected by
1347
+ registerEditor:function(c,d,e){
1348
+ // NOTE: name === editorScope._name
1349
+ // targetToolbars is an [] of 'toolbar name's
1350
+ // targetToolbars are optional, we don't require a toolbar to function
1351
+ if(!c||""===c)throw"textAngular Error: An editor requires a name";if(!d)throw"textAngular Error: An editor requires a scope";if(j[c])throw'textAngular Error: An Editor with name "'+c+'" already exists';return j[c]={scope:d,toolbars:e,
1352
+ // toolbarScopes used by this editor
1353
+ toolbarScopes:[],_registerToolbarScope:function(a){
1354
+ // add to the list late
1355
+ this.toolbars.indexOf(a.name)>=0&&
1356
+ // if this toolbarScope is being used by this editor we add it as one of the scopes
1357
+ this.toolbarScopes.push(a)},
1358
+ // this is a suite of functions the editor should use to update all it's linked toolbars
1359
+ editorFunctions:{disable:function(){
1360
+ // disable all linked toolbars
1361
+ angular.forEach(j[c].toolbarScopes,function(a){a.disabled=!0})},enable:function(){
1362
+ // enable all linked toolbars
1363
+ angular.forEach(j[c].toolbarScopes,function(a){a.disabled=!1})},focus:function(){
1364
+ // this should be called when the editor is focussed
1365
+ angular.forEach(j[c].toolbarScopes,function(a){a._parent=d,a.disabled=!1,a.focussed=!0}),d.focussed=!0},unfocus:function(){
1366
+ // this should be called when the editor becomes unfocussed
1367
+ angular.forEach(j[c].toolbarScopes,function(a){a.disabled=!0,a.focussed=!1}),d.focussed=!1},updateSelectedStyles:function(a){
1368
+ // update the active state of all buttons on liked toolbars
1369
+ angular.forEach(j[c].toolbarScopes,function(b){angular.forEach(b.tools,function(c){c.activeState&&(b._parent=d,
1370
+ // selectedElement may be undefined if nothing selected
1371
+ c.active=c.activeState(a))})})},sendKeyCommand:function(e){
1372
+ // we return true if we applied an action, false otherwise
1373
+ var f=!1;return(e.ctrlKey||e.metaKey||e.specialKey)&&angular.forEach(b,function(b,g){if(b.commandKeyCode&&(b.commandKeyCode===e.which||b.commandKeyCode===e.specialKey))for(var h=0;h<j[c].toolbarScopes.length;h++)if(void 0!==j[c].toolbarScopes[h].tools[g]){a.call(j[c].toolbarScopes[h].tools[g],d),f=!0;break}}),f},triggerElementSelect:function(a,e){
1374
+ // search through the taTools to see if a match for the tag is made.
1375
+ // if there is, see if the tool is on a registered toolbar and not disabled.
1376
+ // NOTE: This can trigger on MULTIPLE tools simultaneously.
1377
+ var f=function(a,b){for(var c=!0,d=0;d<b.length;d++)c=c&&a.attr(b[d]);return c},g=[],h={},i=!1;e=angular.element(e);
1378
+ // get all valid tools by element name, keep track if one matches the
1379
+ var k=!1;
1380
+ // Run the actions on the first visible filtered tool only
1381
+ if(angular.forEach(b,function(a,b){a.onElementSelect&&a.onElementSelect.element&&a.onElementSelect.element.toLowerCase()===e[0].tagName.toLowerCase()&&(!a.onElementSelect.filter||a.onElementSelect.filter(e))&&(
1382
+ // this should only end up true if the element matches the only attributes
1383
+ k=k||angular.isArray(a.onElementSelect.onlyWithAttrs)&&f(e,a.onElementSelect.onlyWithAttrs),a.onElementSelect.onlyWithAttrs&&!f(e,a.onElementSelect.onlyWithAttrs)||(h[b]=a))}),
1384
+ // if we matched attributes to filter on, then filter, else continue
1385
+ k?(angular.forEach(h,function(a,b){a.onElementSelect.onlyWithAttrs&&f(e,a.onElementSelect.onlyWithAttrs)&&g.push({name:b,tool:a})}),
1386
+ // sort most specific (most attrs to find) first
1387
+ g.sort(function(a,b){return b.tool.onElementSelect.onlyWithAttrs.length-a.tool.onElementSelect.onlyWithAttrs.length})):angular.forEach(h,function(a,b){g.push({name:b,tool:a})}),g.length>0)for(var l=0;l<g.length;l++){for(var m=g[l].tool,n=g[l].name,o=0;o<j[c].toolbarScopes.length;o++)if(void 0!==j[c].toolbarScopes[o].tools[n]){m.onElementSelect.action.call(j[c].toolbarScopes[o].tools[n],a,e,d),i=!0;break}if(i)break}return i}}},angular.forEach(e,function(a){i[a]&&j[c].toolbarScopes.push(i[a])}),o(),j[c].editorFunctions},
1388
+ // retrieve editor by name, largely used by testing suites only
1389
+ retrieveEditor:function(a){return j[a]},unregisterEditor:function(a){delete j[a],o()},
1390
+ // registers a toolbar such that it can be linked to editors
1391
+ registerToolbar:function(a){if(!a)throw"textAngular Error: A toolbar requires a scope";if(!a.name||""===a.name)throw"textAngular Error: A toolbar requires a name";if(i[a.name])throw'textAngular Error: A toolbar with name "'+a.name+'" already exists';i[a.name]=a,
1392
+ // walk all the editors and connect this toolbarScope to the editors.... if we need to. This way, it does
1393
+ // not matter if we register the editors after the toolbars or not
1394
+ // Note the editor will ignore this toolbarScope if it is not connected to it...
1395
+ angular.forEach(j,function(b){b._registerToolbarScope(a)}),o()},
1396
+ // retrieve toolbar by name, largely used by testing suites only
1397
+ retrieveToolbar:function(a){return i[a]},
1398
+ // retrieve toolbars by editor name, largely used by testing suites only
1399
+ retrieveToolbarsViaEditor:function(a){var b=[],c=this;return angular.forEach(this.retrieveEditor(a).toolbars,function(a){b.push(c.retrieveToolbar(a))}),b},unregisterToolbar:function(a){delete i[a],o()},
1400
+ // functions for updating the toolbar buttons display
1401
+ updateToolsDisplay:function(a){
1402
+ // pass a partial struct of the taTools, this allows us to update the tools on the fly, will not change the defaults.
1403
+ var b=this;angular.forEach(a,function(a,c){b.updateToolDisplay(c,a)})},
1404
+ // this function resets all toolbars to their default tool definitions
1405
+ resetToolsDisplay:function(){var a=this;angular.forEach(b,function(b,c){a.resetToolDisplay(c)}),o()},
1406
+ // update a tool on all toolbars
1407
+ updateToolDisplay:function(a,b){var c=this;angular.forEach(i,function(d,e){c.updateToolbarToolDisplay(e,a,b)}),o()},
1408
+ // resets a tool to the default/starting state on all toolbars
1409
+ resetToolDisplay:function(a){var b=this;angular.forEach(i,function(c,d){b.resetToolbarToolDisplay(d,a)}),o()},
1410
+ // update a tool on a specific toolbar
1411
+ updateToolbarToolDisplay:function(a,b,c){if(!i[a])throw'textAngular Error: No Toolbar with name "'+a+'" exists';i[a].updateToolDisplay(b,c)},
1412
+ // reset a tool on a specific toolbar to it's default starting value
1413
+ resetToolbarToolDisplay:function(a,c){if(!i[a])throw'textAngular Error: No Toolbar with name "'+a+'" exists';i[a].updateToolDisplay(c,b[c],!0)},
1414
+ // removes a tool from all toolbars and it's definition
1415
+ removeTool:function(a){delete b[a],angular.forEach(i,function(b){delete b.tools[a];for(var c=0;c<b.toolbar.length;c++){for(var d,e=0;e<b.toolbar[c].length;e++){if(b.toolbar[c][e]===a){d={group:c,index:e};break}if(void 0!==d)break}void 0!==d&&(b.toolbar[d.group].slice(d.index,1),b._$element.children().eq(d.group).children().eq(d.index).remove())}}),o()},
1416
+ // toolkey, toolDefinition are required. If group is not specified will pick the last group, if index isnt defined will append to group
1417
+ addTool:function(a,b,d,e){c(a,b),angular.forEach(i,function(c){c.addTool(a,b,d,e)}),o()},
1418
+ // adds a Tool but only to one toolbar not all
1419
+ addToolToToolbar:function(a,b,d,e,f){c(a,b),i[d].addTool(a,b,e,f),o()},
1420
+ // this is used when externally the html of an editor has been changed and textAngular needs to be notified to update the model.
1421
+ // this will call a $digest if not already happening
1422
+ refreshEditor:function(a){if(!j[a])throw'textAngular Error: No Editor with name "'+a+'" exists';j[a].scope.updateTaBindtaTextElement(),/* istanbul ignore else: phase catch */
1423
+ j[a].scope.$$phase||j[a].scope.$digest(),o()},
1424
+ // this is used by taBind to send a key command in response to a special key event
1425
+ sendKeyCommand:function(a,b){var c=j[a._name];/* istanbul ignore else: if nothing to do, do nothing */
1426
+ if(c&&c.editorFunctions.sendKeyCommand(b))/* istanbul ignore else: don't run if already running */
1427
+ return a._bUpdateSelectedStyles||a.updateSelectedStyles(),b.preventDefault(),!1},
1428
+ //
1429
+ // When a toolbar and tools are created, it isn't until there is a key event or mouse event
1430
+ // that the updateSelectedStyles() is called behind the scenes.
1431
+ // This function forces an update through the existing editors to help the application make sure
1432
+ // the inital state is correct.
1433
+ //
1434
+ updateStyles:l,
1435
+ // return the current version of textAngular in use to the user
1436
+ getVersion:function(){return f},
1437
+ // for testing
1438
+ getToolbarScopes:function(){var a=[];return angular.forEach(j,function(b){a=a.concat(b.toolbarScopes)}),a}}}]),u.directive("textAngularToolbar",["$compile","textAngularManager","taOptions","taTools","taToolExecuteAction","$window",function(a,b,c,d,e,f){return{scope:{name:"@"},restrict:"EA",link:function(g,h,i){if(!g.name||""===g.name)throw"textAngular Error: A toolbar requires a name";angular.extend(g,angular.copy(c)),i.taToolbar&&(g.toolbar=g.$parent.$eval(i.taToolbar)),i.taToolbarClass&&(g.classes.toolbar=i.taToolbarClass),i.taToolbarGroupClass&&(g.classes.toolbarGroup=i.taToolbarGroupClass),i.taToolbarButtonClass&&(g.classes.toolbarButton=i.taToolbarButtonClass),i.taToolbarActiveButtonClass&&(g.classes.toolbarButtonActive=i.taToolbarActiveButtonClass),i.taFocussedClass&&(g.classes.focussed=i.taFocussedClass),g.disabled=!0,g.focussed=!1,g._$element=h,h[0].innerHTML="",h.addClass("ta-toolbar "+g.classes.toolbar),g.$watch("focussed",function(){g.focussed?h.addClass(g.classes.focussed):h.removeClass(g.classes.focussed)});var j=function(b,c){var d;if(d=b&&b.display?angular.element(b.display):angular.element("<button type='button'>"),b&&b.class?d.addClass(b.class):d.addClass(g.classes.toolbarButton),d.attr("name",c.name),
1439
+ // important to not take focus from the main text/html entry
1440
+ d.attr("ta-button","ta-button"),d.attr("ng-disabled","isDisabled()"),d.attr("tabindex","-1"),d.attr("ng-click","executeAction()"),d.attr("ng-class","displayActiveToolClass(active)"),b&&b.tooltiptext&&d.attr("title",b.tooltiptext),b&&!b.display&&!c._display&&(
1441
+ // first clear out the current contents if any
1442
+ d[0].innerHTML="",
1443
+ // add the buttonText
1444
+ b.buttontext&&(d[0].innerHTML=b.buttontext),b.iconclass)){var e=angular.element("<i>"),f=d[0].innerHTML;e.addClass(b.iconclass),d[0].innerHTML="",d.append(e),f&&""!==f&&d.append("&nbsp;"+f)}return c._lastToolDefinition=angular.copy(b),a(d)(c)};
1445
+ // Keep a reference for updating the active states later
1446
+ g.tools={},
1447
+ // create the tools in the toolbar
1448
+ // default functions and values to prevent errors in testing and on init
1449
+ g._parent={disabled:!0,showHtml:!1,queryFormatBlockState:function(){return!1},queryCommandState:function(){return!1}};var k={$window:f,$editor:function(){
1450
+ // dynamically gets the editor as it is set
1451
+ return g._parent},isDisabled:function(){
1452
+ // view selection button is always enabled since it doesn not depend on a selction!
1453
+ // view selection button is always enabled since it doesn not depend on a selction!
1454
+ // this bracket is important as without it it just returns the first bracket and ignores the rest
1455
+ // when the button's disabled function/value evaluates to true
1456
+ // all buttons except the HTML Switch button should be disabled in the showHtml (RAW html) mode
1457
+ // if the toolbar is disabled
1458
+ // if the current editor is disabled
1459
+ return("html"!==this.name||!g._parent.startAction)&&("function"!=typeof this.$eval("disabled")&&this.$eval("disabled")||this.$eval("disabled()")||"html"!==this.name&&this.$editor().showHtml||this.$parent.disabled||this.$editor().disabled)},displayActiveToolClass:function(a){return a?g.classes.toolbarButtonActive:""},executeAction:e};angular.forEach(g.toolbar,function(a){
1460
+ // setup the toolbar group
1461
+ var b=angular.element("<div>");b.addClass(g.classes.toolbarGroup),angular.forEach(a,function(a){
1462
+ // init and add the tools to the group
1463
+ // a tool name (key name from taTools struct)
1464
+ //creates a child scope of the main angularText scope and then extends the childScope with the functions of this particular tool
1465
+ // reference to the scope and element kept
1466
+ g.tools[a]=angular.extend(g.$new(!0),d[a],k,{name:a}),g.tools[a].$element=j(d[a],g.tools[a]),
1467
+ // append the tool compiled with the childScope to the group element
1468
+ b.append(g.tools[a].$element)}),
1469
+ // append the group to the toolbar
1470
+ h.append(b)}),
1471
+ // update a tool
1472
+ // if a value is set to null, remove from the display
1473
+ // when forceNew is set to true it will ignore all previous settings, used to reset to taTools definition
1474
+ // to reset to defaults pass in taTools[key] as _newTool and forceNew as true, ie `updateToolDisplay(key, taTools[key], true);`
1475
+ g.updateToolDisplay=function(a,b,c){var d=g.tools[a];if(d){if(
1476
+ // get the last toolDefinition, then override with the new definition
1477
+ d._lastToolDefinition&&!c&&(b=angular.extend({},d._lastToolDefinition,b)),null===b.buttontext&&null===b.iconclass&&null===b.display)throw'textAngular Error: Tool Definition for updating "'+a+'" does not have a valid display/iconclass/buttontext value';
1478
+ // if tool is defined on this toolbar, update/redo the tool
1479
+ null===b.buttontext&&delete b.buttontext,null===b.iconclass&&delete b.iconclass,null===b.display&&delete b.display;var e=j(b,d);d.$element.replaceWith(e),d.$element=e}},
1480
+ // we assume here that all values passed are valid and correct
1481
+ g.addTool=function(a,b,c,e){g.tools[a]=angular.extend(g.$new(!0),d[a],k,{name:a}),g.tools[a].$element=j(d[a],g.tools[a]);var f;void 0===c&&(c=g.toolbar.length-1),f=angular.element(h.children()[c]),void 0===e?(f.append(g.tools[a].$element),g.toolbar[c][g.toolbar[c].length-1]=a):(f.children().eq(e).after(g.tools[a].$element),g.toolbar[c][e]=a)},b.registerToolbar(g),g.$on("$destroy",function(){b.unregisterToolbar(g.name)})}}}]),u.directive("textAngularVersion",["textAngularManager",function(a){var b=a.getVersion();return{restrict:"EA",link:function(a,c,d){c.html(b)}}}]),u.name});