mongo_browser 0.1.3 → 0.2.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data/.gitignore +3 -0
  2. data/.travis.yml +3 -3
  3. data/README.md +1 -25
  4. data/Rakefile +0 -1
  5. data/app/assets/images/background.png +0 -0
  6. data/app/assets/javascripts/app/controllers/alerts.js.coffee +12 -0
  7. data/app/assets/javascripts/app/controllers/breadcrumbs.js.coffee +25 -0
  8. data/app/assets/javascripts/app/controllers/collections.js.coffee +40 -0
  9. data/app/assets/javascripts/app/controllers/databases.js.coffee +39 -0
  10. data/app/assets/javascripts/app/controllers/documents.js.coffee +49 -0
  11. data/app/assets/javascripts/app/controllers/main.js.coffee +10 -0
  12. data/app/assets/javascripts/app/controllers/server_info.js.coffee +14 -0
  13. data/app/assets/javascripts/app/controllers.js.coffee +2 -0
  14. data/app/assets/javascripts/app/directives.js.coffee +20 -0
  15. data/app/assets/javascripts/app/filters.js.coffee +48 -0
  16. data/app/assets/javascripts/app/modules/dialogs.js.coffee +29 -0
  17. data/app/assets/javascripts/app/modules/pager.js.coffee +87 -0
  18. data/app/assets/javascripts/app/modules/table_filter.js.coffee +27 -0
  19. data/app/assets/javascripts/app/resources.js.coffee +22 -0
  20. data/app/assets/javascripts/app/services.js.coffee +36 -0
  21. data/app/assets/javascripts/app.js.coffee +8 -0
  22. data/app/assets/javascripts/application.js.coffee +35 -6
  23. data/app/assets/javascripts/templates/.gitkeep +0 -0
  24. data/app/assets/javascripts/templates.js.coffee +1 -0
  25. data/app/assets/javascripts/vendor.js.coffee +5 -0
  26. data/app/assets/stylesheets/application.css.scss +46 -3
  27. data/app/assets/templates/collections.html +53 -0
  28. data/app/assets/templates/databases.html +32 -0
  29. data/app/assets/templates/documents.html +45 -0
  30. data/app/assets/templates/pager.html +13 -0
  31. data/app/{views/server_info.erb → assets/templates/server_info.html} +5 -7
  32. data/app/assets/templates/table_filter.html +10 -0
  33. data/bin/mongo_browser +8 -45
  34. data/config-e2e.ru +20 -0
  35. data/grunt.js +36 -0
  36. data/lib/mongo_browser/application.rb +143 -64
  37. data/lib/mongo_browser/middleware/sprockets_base.rb +11 -0
  38. data/lib/mongo_browser/middleware/sprockets_sinatra.rb +3 -7
  39. data/lib/mongo_browser/middleware/sprockets_specs.rb +18 -0
  40. data/lib/mongo_browser/models/collection.rb +67 -0
  41. data/lib/mongo_browser/models/database.rb +52 -0
  42. data/lib/mongo_browser/models/document.rb +17 -0
  43. data/lib/mongo_browser/models/pager.rb +30 -0
  44. data/lib/mongo_browser/models/server.rb +51 -0
  45. data/lib/mongo_browser/version.rb +1 -1
  46. data/lib/mongo_browser.rb +10 -5
  47. data/mongo_browser.gemspec +4 -11
  48. data/public/index.html +47 -0
  49. data/script/ci_all +21 -0
  50. data/script/ci_e2e +11 -0
  51. data/script/ci_javascripts +4 -0
  52. data/script/ci_rspec +3 -0
  53. data/spec/features/collections_list_spec.rb +6 -49
  54. data/spec/features/documents_list_spec.rb +15 -14
  55. data/spec/features/server_info_spec.rb +3 -3
  56. data/spec/javascripts/app/controllers/alerts_spec.js.coffee +36 -0
  57. data/spec/javascripts/app/controllers/breadcrumbs_spec.js.coffee +28 -0
  58. data/spec/javascripts/app/controllers/collections_spec.js.coffee +78 -0
  59. data/spec/javascripts/app/controllers/databases_spec.js.coffee +55 -0
  60. data/spec/javascripts/app/controllers/documents_spec.js.coffee +62 -0
  61. data/spec/javascripts/app/controllers/main_spec.js.coffee +20 -0
  62. data/spec/javascripts/app/controllers/server_info_spec.js.coffee +21 -0
  63. data/spec/javascripts/app/directives_spec.js.coffee +58 -0
  64. data/spec/javascripts/app/filters_spec.js.coffee +99 -0
  65. data/spec/javascripts/app/modules/dialogs_spec.js.coffee +51 -0
  66. data/spec/javascripts/app/modules/pager_spec.js.coffee +104 -0
  67. data/spec/javascripts/app/modules/table_filter_spec.js.coffee +76 -0
  68. data/spec/javascripts/app/services_spec.js.coffee +83 -0
  69. data/spec/javascripts/config/testacular-e2e.conf.js +19 -0
  70. data/spec/javascripts/config/testacular.conf.js +43 -0
  71. data/spec/javascripts/e2e/collections_scenario.js.coffee +49 -0
  72. data/spec/javascripts/e2e/databases_scenario.js.coffee +74 -0
  73. data/spec/javascripts/e2e/documents_scenario.js.coffee +18 -0
  74. data/spec/javascripts/e2e/server_info_scenario.js.coffee +12 -0
  75. data/spec/javascripts/helpers/matchers.js.coffee +5 -0
  76. data/spec/javascripts/helpers/mocks.js.coffee +7 -0
  77. data/spec/javascripts/helpers_e2e/app_element.js.coffee +6 -0
  78. data/spec/javascripts/lib/angular-mocks.js +1740 -0
  79. data/spec/javascripts/lib/angular-scenario.js +26147 -0
  80. data/spec/javascripts/lib/jasmine-html.js +681 -0
  81. data/spec/javascripts/lib/jasmine.css +82 -0
  82. data/spec/javascripts/lib/jasmine.js +2600 -0
  83. data/spec/javascripts/runner.html +54 -0
  84. data/spec/javascripts/runner_e2e.html +10 -0
  85. data/spec/javascripts/spec.js.coffee +2 -0
  86. data/spec/javascripts/spec_e2e.js.coffee +2 -0
  87. data/spec/lib/models/collection_spec.rb +80 -0
  88. data/spec/lib/models/database_spec.rb +75 -0
  89. data/spec/lib/models/document_spec.rb +17 -0
  90. data/spec/lib/models/pager_spec.rb +64 -0
  91. data/spec/lib/models/server_spec.rb +76 -0
  92. data/spec/lib/mongo_browser_spec.rb +1 -1
  93. data/spec/spec_helper.rb +2 -19
  94. data/spec/support/feature_example_group.rb +8 -3
  95. data/spec/support/fixtures/databases.json +8 -0
  96. data/spec/support/fixtures.rb +20 -3
  97. data/spec/support/matchers/have_flash_message.rb +1 -1
  98. data/spec/support/mongod.rb +37 -21
  99. data/spec/support/mongodb.conf +47 -0
  100. data/vendor/assets/javascripts/angular/angular-bootstrap.js +166 -0
  101. data/vendor/assets/javascripts/angular/angular-resource.js +435 -0
  102. data/vendor/assets/javascripts/angular/angular-sanitize.js +535 -0
  103. data/vendor/assets/javascripts/angular/angular.js +14531 -0
  104. data/vendor/assets/javascripts/underscore.js +1200 -0
  105. metadata +136 -148
  106. data/app/assets/javascripts/app/table_filter.js.coffee +0 -50
  107. data/app/assets/javascripts/ujs.js.coffee +0 -23
  108. data/app/views/collections/index.erb +0 -59
  109. data/app/views/databases/index.erb +0 -29
  110. data/app/views/documents/index.erb +0 -61
  111. data/app/views/layout/_flash_messages.erb +0 -10
  112. data/app/views/layout/_navbar.erb +0 -22
  113. data/app/views/layout.erb +0 -20
  114. data/app/views/shared/_filter.erb +0 -14
  115. data/features/mongo_browser.feature +0 -18
  116. data/features/step_definitions/mongo_browser_steps.rb +0 -1
  117. data/features/support/env.rb +0 -18
  118. data/spec/features/databases_list_spec.rb +0 -65
@@ -0,0 +1,535 @@
1
+ /**
2
+ * @license AngularJS v1.0.3
3
+ * (c) 2010-2012 Google, Inc. http://angularjs.org
4
+ * License: MIT
5
+ */
6
+ (function(window, angular, undefined) {
7
+ 'use strict';
8
+
9
+ /**
10
+ * @ngdoc overview
11
+ * @name ngSanitize
12
+ * @description
13
+ */
14
+
15
+ /*
16
+ * HTML Parser By Misko Hevery (misko@hevery.com)
17
+ * based on: HTML Parser By John Resig (ejohn.org)
18
+ * Original code by Erik Arvidsson, Mozilla Public License
19
+ * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
20
+ *
21
+ * // Use like so:
22
+ * htmlParser(htmlString, {
23
+ * start: function(tag, attrs, unary) {},
24
+ * end: function(tag) {},
25
+ * chars: function(text) {},
26
+ * comment: function(text) {}
27
+ * });
28
+ *
29
+ */
30
+
31
+
32
+ /**
33
+ * @ngdoc service
34
+ * @name ngSanitize.$sanitize
35
+ * @function
36
+ *
37
+ * @description
38
+ * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
39
+ * then serialized back to properly escaped html string. This means that no unsafe input can make
40
+ * it into the returned string, however, since our parser is more strict than a typical browser
41
+ * parser, it's possible that some obscure input, which would be recognized as valid HTML by a
42
+ * browser, won't make it through the sanitizer.
43
+ *
44
+ * @param {string} html Html input.
45
+ * @returns {string} Sanitized html.
46
+ *
47
+ * @example
48
+ <doc:example module="ngSanitize">
49
+ <doc:source>
50
+ <script>
51
+ function Ctrl($scope) {
52
+ $scope.snippet =
53
+ '<p style="color:blue">an html\n' +
54
+ '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
55
+ 'snippet</p>';
56
+ }
57
+ </script>
58
+ <div ng-controller="Ctrl">
59
+ Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
60
+ <table>
61
+ <tr>
62
+ <td>Filter</td>
63
+ <td>Source</td>
64
+ <td>Rendered</td>
65
+ </tr>
66
+ <tr id="html-filter">
67
+ <td>html filter</td>
68
+ <td>
69
+ <pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre>
70
+ </td>
71
+ <td>
72
+ <div ng-bind-html="snippet"></div>
73
+ </td>
74
+ </tr>
75
+ <tr id="escaped-html">
76
+ <td>no filter</td>
77
+ <td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
78
+ <td><div ng-bind="snippet"></div></td>
79
+ </tr>
80
+ <tr id="html-unsafe-filter">
81
+ <td>unsafe html filter</td>
82
+ <td><pre>&lt;div ng-bind-html-unsafe="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
83
+ <td><div ng-bind-html-unsafe="snippet"></div></td>
84
+ </tr>
85
+ </table>
86
+ </div>
87
+ </doc:source>
88
+ <doc:scenario>
89
+ it('should sanitize the html snippet ', function() {
90
+ expect(using('#html-filter').element('div').html()).
91
+ toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
92
+ });
93
+
94
+ it('should escape snippet without any filter', function() {
95
+ expect(using('#escaped-html').element('div').html()).
96
+ toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
97
+ "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
98
+ "snippet&lt;/p&gt;");
99
+ });
100
+
101
+ it('should inline raw snippet if filtered as unsafe', function() {
102
+ expect(using('#html-unsafe-filter').element("div").html()).
103
+ toBe("<p style=\"color:blue\">an html\n" +
104
+ "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
105
+ "snippet</p>");
106
+ });
107
+
108
+ it('should update', function() {
109
+ input('snippet').enter('new <b>text</b>');
110
+ expect(using('#html-filter').binding('snippet')).toBe('new <b>text</b>');
111
+ expect(using('#escaped-html').element('div').html()).toBe("new &lt;b&gt;text&lt;/b&gt;");
112
+ expect(using('#html-unsafe-filter').binding("snippet")).toBe('new <b>text</b>');
113
+ });
114
+ </doc:scenario>
115
+ </doc:example>
116
+ */
117
+ var $sanitize = function(html) {
118
+ var buf = [];
119
+ htmlParser(html, htmlSanitizeWriter(buf));
120
+ return buf.join('');
121
+ };
122
+
123
+
124
+ // Regular Expressions for parsing tags and attributes
125
+ var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
126
+ END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
127
+ ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
128
+ BEGIN_TAG_REGEXP = /^</,
129
+ BEGING_END_TAGE_REGEXP = /^<\s*\//,
130
+ COMMENT_REGEXP = /<!--(.*?)-->/g,
131
+ CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
132
+ URI_REGEXP = /^((ftp|https?):\/\/|mailto:|#)/,
133
+ NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character)
134
+
135
+
136
+ // Good source of info about elements and attributes
137
+ // http://dev.w3.org/html5/spec/Overview.html#semantics
138
+ // http://simon.html5.org/html-elements
139
+
140
+ // Safe Void Elements - HTML5
141
+ // http://dev.w3.org/html5/spec/Overview.html#void-elements
142
+ var voidElements = makeMap("area,br,col,hr,img,wbr");
143
+
144
+ // Elements that you can, intentionally, leave open (and which close themselves)
145
+ // http://dev.w3.org/html5/spec/Overview.html#optional-tags
146
+ var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
147
+ optionalEndTagInlineElements = makeMap("rp,rt"),
148
+ optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements);
149
+
150
+ // Safe Block Elements - HTML5
151
+ var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article,aside," +
152
+ "blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6," +
153
+ "header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
154
+
155
+ // Inline Elements - HTML5
156
+ var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b,bdi,bdo," +
157
+ "big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small," +
158
+ "span,strike,strong,sub,sup,time,tt,u,var"));
159
+
160
+
161
+ // Special Elements (can contain anything)
162
+ var specialElements = makeMap("script,style");
163
+
164
+ var validElements = angular.extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements);
165
+
166
+ //Attributes that have href and hence need to be sanitized
167
+ var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
168
+ var validAttrs = angular.extend({}, uriAttrs, makeMap(
169
+ 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
170
+ 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
171
+ 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
172
+ 'scope,scrolling,shape,span,start,summary,target,title,type,'+
173
+ 'valign,value,vspace,width'));
174
+
175
+ function makeMap(str) {
176
+ var obj = {}, items = str.split(','), i;
177
+ for (i = 0; i < items.length; i++) obj[items[i]] = true;
178
+ return obj;
179
+ }
180
+
181
+
182
+ /**
183
+ * @example
184
+ * htmlParser(htmlString, {
185
+ * start: function(tag, attrs, unary) {},
186
+ * end: function(tag) {},
187
+ * chars: function(text) {},
188
+ * comment: function(text) {}
189
+ * });
190
+ *
191
+ * @param {string} html string
192
+ * @param {object} handler
193
+ */
194
+ function htmlParser( html, handler ) {
195
+ var index, chars, match, stack = [], last = html;
196
+ stack.last = function() { return stack[ stack.length - 1 ]; };
197
+
198
+ while ( html ) {
199
+ chars = true;
200
+
201
+ // Make sure we're not in a script or style element
202
+ if ( !stack.last() || !specialElements[ stack.last() ] ) {
203
+
204
+ // Comment
205
+ if ( html.indexOf("<!--") === 0 ) {
206
+ index = html.indexOf("-->");
207
+
208
+ if ( index >= 0 ) {
209
+ if (handler.comment) handler.comment( html.substring( 4, index ) );
210
+ html = html.substring( index + 3 );
211
+ chars = false;
212
+ }
213
+
214
+ // end tag
215
+ } else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
216
+ match = html.match( END_TAG_REGEXP );
217
+
218
+ if ( match ) {
219
+ html = html.substring( match[0].length );
220
+ match[0].replace( END_TAG_REGEXP, parseEndTag );
221
+ chars = false;
222
+ }
223
+
224
+ // start tag
225
+ } else if ( BEGIN_TAG_REGEXP.test(html) ) {
226
+ match = html.match( START_TAG_REGEXP );
227
+
228
+ if ( match ) {
229
+ html = html.substring( match[0].length );
230
+ match[0].replace( START_TAG_REGEXP, parseStartTag );
231
+ chars = false;
232
+ }
233
+ }
234
+
235
+ if ( chars ) {
236
+ index = html.indexOf("<");
237
+
238
+ var text = index < 0 ? html : html.substring( 0, index );
239
+ html = index < 0 ? "" : html.substring( index );
240
+
241
+ if (handler.chars) handler.chars( decodeEntities(text) );
242
+ }
243
+
244
+ } else {
245
+ html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){
246
+ text = text.
247
+ replace(COMMENT_REGEXP, "$1").
248
+ replace(CDATA_REGEXP, "$1");
249
+
250
+ if (handler.chars) handler.chars( decodeEntities(text) );
251
+
252
+ return "";
253
+ });
254
+
255
+ parseEndTag( "", stack.last() );
256
+ }
257
+
258
+ if ( html == last ) {
259
+ throw "Parse Error: " + html;
260
+ }
261
+ last = html;
262
+ }
263
+
264
+ // Clean up any remaining tags
265
+ parseEndTag();
266
+
267
+ function parseStartTag( tag, tagName, rest, unary ) {
268
+ tagName = angular.lowercase(tagName);
269
+ if ( blockElements[ tagName ] ) {
270
+ while ( stack.last() && inlineElements[ stack.last() ] ) {
271
+ parseEndTag( "", stack.last() );
272
+ }
273
+ }
274
+
275
+ if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
276
+ parseEndTag( "", tagName );
277
+ }
278
+
279
+ unary = voidElements[ tagName ] || !!unary;
280
+
281
+ if ( !unary )
282
+ stack.push( tagName );
283
+
284
+ var attrs = {};
285
+
286
+ rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) {
287
+ var value = doubleQuotedValue
288
+ || singleQoutedValue
289
+ || unqoutedValue
290
+ || '';
291
+
292
+ attrs[name] = decodeEntities(value);
293
+ });
294
+ if (handler.start) handler.start( tagName, attrs, unary );
295
+ }
296
+
297
+ function parseEndTag( tag, tagName ) {
298
+ var pos = 0, i;
299
+ tagName = angular.lowercase(tagName);
300
+ if ( tagName )
301
+ // Find the closest opened tag of the same type
302
+ for ( pos = stack.length - 1; pos >= 0; pos-- )
303
+ if ( stack[ pos ] == tagName )
304
+ break;
305
+
306
+ if ( pos >= 0 ) {
307
+ // Close all the open elements, up the stack
308
+ for ( i = stack.length - 1; i >= pos; i-- )
309
+ if (handler.end) handler.end( stack[ i ] );
310
+
311
+ // Remove the open elements from the stack
312
+ stack.length = pos;
313
+ }
314
+ }
315
+ }
316
+
317
+ /**
318
+ * decodes all entities into regular string
319
+ * @param value
320
+ * @returns {string} A string with decoded entities.
321
+ */
322
+ var hiddenPre=document.createElement("pre");
323
+ function decodeEntities(value) {
324
+ hiddenPre.innerHTML=value.replace(/</g,"&lt;");
325
+ return hiddenPre.innerText || hiddenPre.textContent || '';
326
+ }
327
+
328
+ /**
329
+ * Escapes all potentially dangerous characters, so that the
330
+ * resulting string can be safely inserted into attribute or
331
+ * element text.
332
+ * @param value
333
+ * @returns escaped text
334
+ */
335
+ function encodeEntities(value) {
336
+ return value.
337
+ replace(/&/g, '&amp;').
338
+ replace(NON_ALPHANUMERIC_REGEXP, function(value){
339
+ return '&#' + value.charCodeAt(0) + ';';
340
+ }).
341
+ replace(/</g, '&lt;').
342
+ replace(/>/g, '&gt;');
343
+ }
344
+
345
+ /**
346
+ * create an HTML/XML writer which writes to buffer
347
+ * @param {Array} buf use buf.jain('') to get out sanitized html string
348
+ * @returns {object} in the form of {
349
+ * start: function(tag, attrs, unary) {},
350
+ * end: function(tag) {},
351
+ * chars: function(text) {},
352
+ * comment: function(text) {}
353
+ * }
354
+ */
355
+ function htmlSanitizeWriter(buf){
356
+ var ignore = false;
357
+ var out = angular.bind(buf, buf.push);
358
+ return {
359
+ start: function(tag, attrs, unary){
360
+ tag = angular.lowercase(tag);
361
+ if (!ignore && specialElements[tag]) {
362
+ ignore = tag;
363
+ }
364
+ if (!ignore && validElements[tag] == true) {
365
+ out('<');
366
+ out(tag);
367
+ angular.forEach(attrs, function(value, key){
368
+ var lkey=angular.lowercase(key);
369
+ if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
370
+ out(' ');
371
+ out(key);
372
+ out('="');
373
+ out(encodeEntities(value));
374
+ out('"');
375
+ }
376
+ });
377
+ out(unary ? '/>' : '>');
378
+ }
379
+ },
380
+ end: function(tag){
381
+ tag = angular.lowercase(tag);
382
+ if (!ignore && validElements[tag] == true) {
383
+ out('</');
384
+ out(tag);
385
+ out('>');
386
+ }
387
+ if (tag == ignore) {
388
+ ignore = false;
389
+ }
390
+ },
391
+ chars: function(chars){
392
+ if (!ignore) {
393
+ out(encodeEntities(chars));
394
+ }
395
+ }
396
+ };
397
+ }
398
+
399
+
400
+ // define ngSanitize module and register $sanitize service
401
+ angular.module('ngSanitize', []).value('$sanitize', $sanitize);
402
+
403
+ /**
404
+ * @ngdoc directive
405
+ * @name ngSanitize.directive:ngBindHtml
406
+ *
407
+ * @description
408
+ * Creates a binding that will sanitize the result of evaluating the `expression` with the
409
+ * {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element.
410
+ *
411
+ * See {@link ngSanitize.$sanitize $sanitize} docs for examples.
412
+ *
413
+ * @element ANY
414
+ * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
415
+ */
416
+ angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) {
417
+ return function(scope, element, attr) {
418
+ element.addClass('ng-binding').data('$binding', attr.ngBindHtml);
419
+ scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) {
420
+ value = $sanitize(value);
421
+ element.html(value || '');
422
+ });
423
+ };
424
+ }]);
425
+ /**
426
+ * @ngdoc filter
427
+ * @name ngSanitize.filter:linky
428
+ * @function
429
+ *
430
+ * @description
431
+ * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
432
+ * plain email address links.
433
+ *
434
+ * @param {string} text Input text.
435
+ * @returns {string} Html-linkified text.
436
+ *
437
+ * @usage
438
+ <span ng-bind-html="linky_expression | linky"></span>
439
+ *
440
+ * @example
441
+ <doc:example module="ngSanitize">
442
+ <doc:source>
443
+ <script>
444
+ function Ctrl($scope) {
445
+ $scope.snippet =
446
+ 'Pretty text with some links:\n'+
447
+ 'http://angularjs.org/,\n'+
448
+ 'mailto:us@somewhere.org,\n'+
449
+ 'another@somewhere.org,\n'+
450
+ 'and one more: ftp://127.0.0.1/.';
451
+ }
452
+ </script>
453
+ <div ng-controller="Ctrl">
454
+ Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
455
+ <table>
456
+ <tr>
457
+ <td>Filter</td>
458
+ <td>Source</td>
459
+ <td>Rendered</td>
460
+ </tr>
461
+ <tr id="linky-filter">
462
+ <td>linky filter</td>
463
+ <td>
464
+ <pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
465
+ </td>
466
+ <td>
467
+ <div ng-bind-html="snippet | linky"></div>
468
+ </td>
469
+ </tr>
470
+ <tr id="escaped-html">
471
+ <td>no filter</td>
472
+ <td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
473
+ <td><div ng-bind="snippet"></div></td>
474
+ </tr>
475
+ </table>
476
+ </doc:source>
477
+ <doc:scenario>
478
+ it('should linkify the snippet with urls', function() {
479
+ expect(using('#linky-filter').binding('snippet | linky')).
480
+ toBe('Pretty text with some links:&#10;' +
481
+ '<a href="http://angularjs.org/">http://angularjs.org/</a>,&#10;' +
482
+ '<a href="mailto:us@somewhere.org">us@somewhere.org</a>,&#10;' +
483
+ '<a href="mailto:another@somewhere.org">another@somewhere.org</a>,&#10;' +
484
+ 'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
485
+ });
486
+
487
+ it ('should not linkify snippet without the linky filter', function() {
488
+ expect(using('#escaped-html').binding('snippet')).
489
+ toBe("Pretty text with some links:\n" +
490
+ "http://angularjs.org/,\n" +
491
+ "mailto:us@somewhere.org,\n" +
492
+ "another@somewhere.org,\n" +
493
+ "and one more: ftp://127.0.0.1/.");
494
+ });
495
+
496
+ it('should update', function() {
497
+ input('snippet').enter('new http://link.');
498
+ expect(using('#linky-filter').binding('snippet | linky')).
499
+ toBe('new <a href="http://link">http://link</a>.');
500
+ expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
501
+ });
502
+ </doc:scenario>
503
+ </doc:example>
504
+ */
505
+ angular.module('ngSanitize').filter('linky', function() {
506
+ var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,
507
+ MAILTO_REGEXP = /^mailto:/;
508
+
509
+ return function(text) {
510
+ if (!text) return text;
511
+ var match;
512
+ var raw = text;
513
+ var html = [];
514
+ // TODO(vojta): use $sanitize instead
515
+ var writer = htmlSanitizeWriter(html);
516
+ var url;
517
+ var i;
518
+ while ((match = raw.match(LINKY_URL_REGEXP))) {
519
+ // We can not end in these as they are sometimes found at the end of the sentence
520
+ url = match[0];
521
+ // if we did not match ftp/http/mailto then assume mailto
522
+ if (match[2] == match[3]) url = 'mailto:' + url;
523
+ i = match.index;
524
+ writer.chars(raw.substr(0, i));
525
+ writer.start('a', {href:url});
526
+ writer.chars(match[0].replace(MAILTO_REGEXP, ''));
527
+ writer.end('a');
528
+ raw = raw.substring(i + match[0].length);
529
+ }
530
+ writer.chars(raw);
531
+ return html.join('');
532
+ };
533
+ });
534
+
535
+ })(window, window.angular);