Skip to content
Browse files

merge pull request #4 from qubemove/angular-1.0.2

  • Loading branch information...
2 parents d0c5c2e + 780f33f commit 3d6a04db5e07f50dd940568beeb2e7e92374830a @ludicast committed
View
2 Gemfile
@@ -1,6 +1,6 @@
source "http://rubygems.org"
-gem "rails", "~> 3.1.1"
+gem "rails"
gem "sqlite3-ruby", :require => "sqlite3"
gem 'sass'
gem 'coffee-script'
View
3 angular-rails.gemspec
@@ -15,8 +15,7 @@ Gem::Specification.new do |s|
s.files = Dir["{app,config,db,lib,vendor}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.md"]
s.test_files = Dir["test/**/*"]
-
- s.add_dependency "rails", '>= 3.1.0'
+ s.add_dependency "rails"
s.add_dependency "coffee-script", '~> 2.2.0'
end
View
428 vendor/assets/javascripts/angular-resource.js
@@ -0,0 +1,428 @@
+/**
+ * @license AngularJS v1.0.2
+ * (c) 2010-2012 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+(function(window, angular, undefined) {
+'use strict';
+
+/**
+ * @ngdoc overview
+ * @name ngResource
+ * @description
+ */
+
+ /**
+ * @ngdoc object
+ * @name ngResource.$resource
+ * @requires $http
+ *
+ * @description
+ * A factory which creates a resource object that lets you interact with
+ * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
+ *
+ * The returned resource object has action methods which provide high-level behaviors without
+ * the need to interact with the low level {@link ng.$http $http} service.
+ *
+ * @param {string} url A parameterized URL template with parameters prefixed by `:` as in
+ * `/user/:username`.
+ *
+ * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
+ * `actions` methods.
+ *
+ * Each key value in the parameter object is first bound to url template if present and then any
+ * excess keys are appended to the url search query after the `?`.
+ *
+ * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
+ * URL `/path/greet?salutation=Hello`.
+ *
+ * If the parameter value is prefixed with `@` then the value of that parameter is extracted from
+ * the data object (useful for non-GET operations).
+ *
+ * @param {Object.<Object>=} actions Hash with declaration of custom action that should extend the
+ * default set of resource actions. The declaration should be created in the following format:
+ *
+ * {action1: {method:?, params:?, isArray:?},
+ * action2: {method:?, params:?, isArray:?},
+ * ...}
+ *
+ * Where:
+ *
+ * - `action` – {string} – The name of action. This name becomes the name of the method on your
+ * resource object.
+ * - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
+ * and `JSONP`
+ * - `params` – {object=} – Optional set of pre-bound parameters for this action.
+ * - isArray – {boolean=} – If true then the returned object for this action is an array, see
+ * `returns` section.
+ *
+ * @returns {Object} A resource "class" object with methods for the default set of resource actions
+ * optionally extended with custom `actions`. The default set contains these actions:
+ *
+ * { 'get': {method:'GET'},
+ * 'save': {method:'POST'},
+ * 'query': {method:'GET', isArray:true},
+ * 'remove': {method:'DELETE'},
+ * 'delete': {method:'DELETE'} };
+ *
+ * Calling these methods invoke an {@link ng.$http} with the specified http method,
+ * destination and parameters. When the data is returned from the server then the object is an
+ * instance of the resource class `save`, `remove` and `delete` actions are available on it as
+ * methods with the `$` prefix. This allows you to easily perform CRUD operations (create, read,
+ * update, delete) on server-side data like this:
+ * <pre>
+ var User = $resource('/user/:userId', {userId:'@id'});
+ var user = User.get({userId:123}, function() {
+ user.abc = true;
+ user.$save();
+ });
+ </pre>
+ *
+ * It is important to realize that invoking a $resource object method immediately returns an
+ * empty reference (object or array depending on `isArray`). Once the data is returned from the
+ * server the existing reference is populated with the actual data. This is a useful trick since
+ * usually the resource is assigned to a model which is then rendered by the view. Having an empty
+ * object results in no rendering, once the data arrives from the server then the object is
+ * populated with the data and the view automatically re-renders itself showing the new data. This
+ * means that in most case one never has to write a callback function for the action methods.
+ *
+ * The action methods on the class object or instance object can be invoked with the following
+ * parameters:
+ *
+ * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
+ * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
+ * - non-GET instance actions: `instance.$action([parameters], [success], [error])`
+ *
+ *
+ * @example
+ *
+ * # Credit card resource
+ *
+ * <pre>
+ // Define CreditCard class
+ var CreditCard = $resource('/user/:userId/card/:cardId',
+ {userId:123, cardId:'@id'}, {
+ charge: {method:'POST', params:{charge:true}}
+ });
+
+ // We can retrieve a collection from the server
+ var cards = CreditCard.query(function() {
+ // GET: /user/123/card
+ // server returns: [ {id:456, number:'1234', name:'Smith'} ];
+
+ var card = cards[0];
+ // each item is an instance of CreditCard
+ expect(card instanceof CreditCard).toEqual(true);
+ card.name = "J. Smith";
+ // non GET methods are mapped onto the instances
+ card.$save();
+ // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
+ // server returns: {id:456, number:'1234', name: 'J. Smith'};
+
+ // our custom method is mapped as well.
+ card.$charge({amount:9.99});
+ // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
+ });
+
+ // we can create an instance as well
+ var newCard = new CreditCard({number:'0123'});
+ newCard.name = "Mike Smith";
+ newCard.$save();
+ // POST: /user/123/card {number:'0123', name:'Mike Smith'}
+ // server returns: {id:789, number:'01234', name: 'Mike Smith'};
+ expect(newCard.id).toEqual(789);
+ * </pre>
+ *
+ * The object returned from this function execution is a resource "class" which has "static" method
+ * for each action in the definition.
+ *
+ * Calling these methods invoke `$http` on the `url` template with the given `method` and `params`.
+ * When the data is returned from the server then the object is an instance of the resource type and
+ * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
+ * operations (create, read, update, delete) on server-side data.
+
+ <pre>
+ var User = $resource('/user/:userId', {userId:'@id'});
+ var user = User.get({userId:123}, function() {
+ user.abc = true;
+ user.$save();
+ });
+ </pre>
+ *
+ * It's worth noting that the success callback for `get`, `query` and other method gets passed
+ * in the response that came from the server as well as $http header getter function, so one
+ * could rewrite the above example and get access to http headers as:
+ *
+ <pre>
+ var User = $resource('/user/:userId', {userId:'@id'});
+ User.get({userId:123}, function(u, getResponseHeaders){
+ u.abc = true;
+ u.$save(function(u, putResponseHeaders) {
+ //u => saved user object
+ //putResponseHeaders => $http header getter
+ });
+ });
+ </pre>
+
+ * # Buzz client
+
+ Let's look at what a buzz client created with the `$resource` service looks like:
+ <doc:example>
+ <doc:source jsfiddle="false">
+ <script>
+ function BuzzController($resource) {
+ this.userId = 'googlebuzz';
+ this.Activity = $resource(
+ 'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
+ {alt:'json', callback:'JSON_CALLBACK'},
+ {get:{method:'JSONP', params:{visibility:'@self'}}, replies: {method:'JSONP', params:{visibility:'@self', comments:'@comments'}}}
+ );
+ }
+
+ BuzzController.prototype = {
+ fetch: function() {
+ this.activities = this.Activity.get({userId:this.userId});
+ },
+ expandReplies: function(activity) {
+ activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id});
+ }
+ };
+ BuzzController.$inject = ['$resource'];
+ </script>
+
+ <div ng-controller="BuzzController">
+ <input ng-model="userId"/>
+ <button ng-click="fetch()">fetch</button>
+ <hr/>
+ <div ng-repeat="item in activities.data.items">
+ <h1 style="font-size: 15px;">
+ <img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
+ <a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
+ <a href ng-click="expandReplies(item)" style="float: right;">Expand replies: {{item.links.replies[0].count}}</a>
+ </h1>
+ {{item.object.content | html}}
+ <div ng-repeat="reply in item.replies.data.items" style="margin-left: 20px;">
+ <img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
+ <a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>: {{reply.content | html}}
+ </div>
+ </div>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ </doc:scenario>
+ </doc:example>
+ */
+angular.module('ngResource', ['ng']).
+ factory('$resource', ['$http', '$parse', function($http, $parse) {
+ var DEFAULT_ACTIONS = {
+ 'get': {method:'GET'},
+ 'save': {method:'POST'},
+ 'query': {method:'GET', isArray:true},
+ 'remove': {method:'DELETE'},
+ 'delete': {method:'DELETE'}
+ };
+ var noop = angular.noop,
+ forEach = angular.forEach,
+ extend = angular.extend,
+ copy = angular.copy,
+ isFunction = angular.isFunction,
+ getter = function(obj, path) {
+ return $parse(path)(obj);
+ };
+
+ /**
+ * We need our custom mehtod because encodeURIComponent is too agressive and doesn't follow
+ * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
+ * segments:
+ * segment = *pchar
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
+ * pct-encoded = "%" HEXDIG HEXDIG
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+ * / "*" / "+" / "," / ";" / "="
+ */
+ function encodeUriSegment(val) {
+ return encodeUriQuery(val, true).
+ replace(/%26/gi, '&').
+ replace(/%3D/gi, '=').
+ replace(/%2B/gi, '+');
+ }
+
+
+ /**
+ * This method is intended for encoding *key* or *value* parts of query component. We need a custom
+ * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be
+ * encoded per http://tools.ietf.org/html/rfc3986:
+ * query = *( pchar / "/" / "?" )
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ * pct-encoded = "%" HEXDIG HEXDIG
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+ * / "*" / "+" / "," / ";" / "="
+ */
+ function encodeUriQuery(val, pctEncodeSpaces) {
+ return encodeURIComponent(val).
+ replace(/%40/gi, '@').
+ replace(/%3A/gi, ':').
+ replace(/%24/g, '$').
+ replace(/%2C/gi, ',').
+ replace((pctEncodeSpaces ? null : /%20/g), '+');
+ }
+
+ function Route(template, defaults) {
+ this.template = template = template + '#';
+ this.defaults = defaults || {};
+ var urlParams = this.urlParams = {};
+ forEach(template.split(/\W/), function(param){
+ if (param && template.match(new RegExp("[^\\\\]:" + param + "\\W"))) {
+ urlParams[param] = true;
+ }
+ });
+ this.template = template.replace(/\\:/g, ':');
+ }
+
+ Route.prototype = {
+ url: function(params) {
+ var self = this,
+ url = this.template,
+ encodedVal;
+
+ params = params || {};
+ forEach(this.urlParams, function(_, urlParam){
+ encodedVal = encodeUriSegment(params[urlParam] || self.defaults[urlParam] || "");
+ url = url.replace(new RegExp(":" + urlParam + "(\\W)"), encodedVal + "$1");
+ });
+ url = url.replace(/\/?#$/, '');
+ var query = [];
+ forEach(params, function(value, key){
+ if (!self.urlParams[key]) {
+ query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value));
+ }
+ });
+ query.sort();
+ url = url.replace(/\/*$/, '');
+ return url + (query.length ? '?' + query.join('&') : '');
+ }
+ };
+
+
+ function ResourceFactory(url, paramDefaults, actions) {
+ var route = new Route(url);
+
+ actions = extend({}, DEFAULT_ACTIONS, actions);
+
+ function extractParams(data){
+ var ids = {};
+ forEach(paramDefaults || {}, function(value, key){
+ ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
+ });
+ return ids;
+ }
+
+ function Resource(value){
+ copy(value || {}, this);
+ }
+
+ forEach(actions, function(action, name) {
+ var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH';
+ Resource[name] = function(a1, a2, a3, a4) {
+ var params = {};
+ var data;
+ var success = noop;
+ var error = null;
+ switch(arguments.length) {
+ case 4:
+ error = a4;
+ success = a3;
+ //fallthrough
+ case 3:
+ case 2:
+ if (isFunction(a2)) {
+ if (isFunction(a1)) {
+ success = a1;
+ error = a2;
+ break;
+ }
+
+ success = a2;
+ error = a3;
+ //fallthrough
+ } else {
+ params = a1;
+ data = a2;
+ success = a3;
+ break;
+ }
+ case 1:
+ if (isFunction(a1)) success = a1;
+ else if (hasBody) data = a1;
+ else params = a1;
+ break;
+ case 0: break;
+ default:
+ throw "Expected between 0-4 arguments [params, data, success, error], got " +
+ arguments.length + " arguments.";
+ }
+
+ var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
+ $http({
+ method: action.method,
+ url: route.url(extend({}, extractParams(data), action.params || {}, params)),
+ data: data
+ }).then(function(response) {
+ var data = response.data;
+
+ if (data) {
+ if (action.isArray) {
+ value.length = 0;
+ forEach(data, function(item) {
+ value.push(new Resource(item));
+ });
+ } else {
+ copy(data, value);
+ }
+ }
+ (success||noop)(value, response.headers);
+ }, error);
+
+ return value;
+ };
+
+
+ Resource.bind = function(additionalParamDefaults){
+ return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
+ };
+
+
+ Resource.prototype['$' + name] = function(a1, a2, a3) {
+ var params = extractParams(this),
+ success = noop,
+ error;
+
+ switch(arguments.length) {
+ case 3: params = a1; success = a2; error = a3; break;
+ case 2:
+ case 1:
+ if (isFunction(a1)) {
+ success = a1;
+ error = a2;
+ } else {
+ params = a1;
+ success = a2 || noop;
+ }
+ case 0: break;
+ default:
+ throw "Expected between 1-3 arguments [params, success, error], got " +
+ arguments.length + " arguments.";
+ }
+ var data = hasBody ? this : undefined;
+ Resource[name].call(this, params, data, success, error);
+ };
+ });
+ return Resource;
+ }
+
+ return ResourceFactory;
+ }]);
+
+})(window, window.angular);
View
10 vendor/assets/javascripts/angular-resource.min.js
@@ -0,0 +1,10 @@
+/*
+ AngularJS v1.0.2
+ (c) 2010-2012 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(A,f,u){'use strict';f.module("ngResource",["ng"]).factory("$resource",["$http","$parse",function(v,w){function g(b,c){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(c?null:/%20/g,"+")}function l(b,c){this.template=b+="#";this.defaults=c||{};var a=this.urlParams={};j(b.split(/\W/),function(c){c&&b.match(RegExp("[^\\\\]:"+c+"\\W"))&&(a[c]=!0)});this.template=b.replace(/\\:/g,":")}function s(b,c,a){function f(d){var b=
+{};j(c||{},function(a,x){var m;a.charAt&&a.charAt(0)=="@"?(m=a.substr(1),m=w(m)(d)):m=a;b[x]=m});return b}function e(a){t(a||{},this)}var y=new l(b),a=r({},z,a);j(a,function(d,g){var l=d.method=="POST"||d.method=="PUT"||d.method=="PATCH";e[g]=function(a,b,c,g){var i={},h,k=o,p=null;switch(arguments.length){case 4:p=g,k=c;case 3:case 2:if(q(b)){if(q(a)){k=a;p=b;break}k=b;p=c}else{i=a;h=b;k=c;break}case 1:q(a)?k=a:l?h=a:i=a;break;case 0:break;default:throw"Expected between 0-4 arguments [params, data, success, error], got "+
+arguments.length+" arguments.";}var n=this instanceof e?this:d.isArray?[]:new e(h);v({method:d.method,url:y.url(r({},f(h),d.params||{},i)),data:h}).then(function(a){var b=a.data;if(b)d.isArray?(n.length=0,j(b,function(a){n.push(new e(a))})):t(b,n);(k||o)(n,a.headers)},p);return n};e.bind=function(d){return s(b,r({},c,d),a)};e.prototype["$"+g]=function(a,b,d){var c=f(this),i=o,h;switch(arguments.length){case 3:c=a;i=b;h=d;break;case 2:case 1:q(a)?(i=a,h=b):(c=a,i=b||o);case 0:break;default:throw"Expected between 1-3 arguments [params, success, error], got "+
+arguments.length+" arguments.";}e[g].call(this,c,l?this:u,i,h)}});return e}var z={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},o=f.noop,j=f.forEach,r=f.extend,t=f.copy,q=f.isFunction;l.prototype={url:function(b){var c=this,a=this.template,f,b=b||{};j(this.urlParams,function(e,d){f=g(b[d]||c.defaults[d]||"",!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+");a=a.replace(RegExp(":"+d+"(\\W)"),f+"$1")});var a=
+a.replace(/\/?#$/,""),e=[];j(b,function(a,b){c.urlParams[b]||e.push(g(b)+"="+g(a))});e.sort();a=a.replace(/\/*$/,"");return a+(e.length?"?"+e.join("&"):"")}};return s}])})(window,window.angular);
View
535 vendor/assets/javascripts/angular-sanitize.js
@@ -0,0 +1,535 @@
+/**
+ * @license AngularJS v1.0.2
+ * (c) 2010-2012 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+(function(window, angular, undefined) {
+'use strict';
+
+/**
+ * @ngdoc overview
+ * @name ngSanitize
+ * @description
+ */
+
+/*
+ * HTML Parser By Misko Hevery (misko@hevery.com)
+ * based on: HTML Parser By John Resig (ejohn.org)
+ * Original code by Erik Arvidsson, Mozilla Public License
+ * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
+ *
+ * // Use like so:
+ * htmlParser(htmlString, {
+ * start: function(tag, attrs, unary) {},
+ * end: function(tag) {},
+ * chars: function(text) {},
+ * comment: function(text) {}
+ * });
+ *
+ */
+
+
+/**
+ * @ngdoc service
+ * @name ngSanitize.$sanitize
+ * @function
+ *
+ * @description
+ * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
+ * then serialized back to properly escaped html string. This means that no unsafe input can make
+ * it into the returned string, however, since our parser is more strict than a typical browser
+ * parser, it's possible that some obscure input, which would be recognized as valid HTML by a
+ * browser, won't make it through the sanitizer.
+ *
+ * @param {string} html Html input.
+ * @returns {string} Sanitized html.
+ *
+ * @example
+ <doc:example module="ngSanitize">
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.snippet =
+ '<p style="color:blue">an html\n' +
+ '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
+ 'snippet</p>';
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
+ <table>
+ <tr>
+ <td>Filter</td>
+ <td>Source</td>
+ <td>Rendered</td>
+ </tr>
+ <tr id="html-filter">
+ <td>html filter</td>
+ <td>
+ <pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre>
+ </td>
+ <td>
+ <div ng-bind-html="snippet"></div>
+ </td>
+ </tr>
+ <tr id="escaped-html">
+ <td>no filter</td>
+ <td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
+ <td><div ng-bind="snippet"></div></td>
+ </tr>
+ <tr id="html-unsafe-filter">
+ <td>unsafe html filter</td>
+ <td><pre>&lt;div ng-bind-html-unsafe="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
+ <td><div ng-bind-html-unsafe="snippet"></div></td>
+ </tr>
+ </table>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should sanitize the html snippet ', function() {
+ expect(using('#html-filter').element('div').html()).
+ toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
+ });
+
+ it('should escape snippet without any filter', function() {
+ expect(using('#escaped-html').element('div').html()).
+ toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
+ "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
+ "snippet&lt;/p&gt;");
+ });
+
+ it('should inline raw snippet if filtered as unsafe', function() {
+ expect(using('#html-unsafe-filter').element("div").html()).
+ toBe("<p style=\"color:blue\">an html\n" +
+ "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
+ "snippet</p>");
+ });
+
+ it('should update', function() {
+ input('snippet').enter('new <b>text</b>');
+ expect(using('#html-filter').binding('snippet')).toBe('new <b>text</b>');
+ expect(using('#escaped-html').element('div').html()).toBe("new &lt;b&gt;text&lt;/b&gt;");
+ expect(using('#html-unsafe-filter').binding("snippet")).toBe('new <b>text</b>');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var $sanitize = function(html) {
+ var buf = [];
+ htmlParser(html, htmlSanitizeWriter(buf));
+ return buf.join('');
+};
+
+
+// Regular Expressions for parsing tags and attributes
+var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
+ END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
+ ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
+ BEGIN_TAG_REGEXP = /^</,
+ BEGING_END_TAGE_REGEXP = /^<\s*\//,
+ COMMENT_REGEXP = /<!--(.*?)-->/g,
+ CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
+ URI_REGEXP = /^((ftp|https?):\/\/|mailto:|#)/,
+ NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character)
+
+
+// Good source of info about elements and attributes
+// http://dev.w3.org/html5/spec/Overview.html#semantics
+// http://simon.html5.org/html-elements
+
+// Safe Void Elements - HTML5
+// http://dev.w3.org/html5/spec/Overview.html#void-elements
+var voidElements = makeMap("area,br,col,hr,img,wbr");
+
+// Elements that you can, intentionally, leave open (and which close themselves)
+// http://dev.w3.org/html5/spec/Overview.html#optional-tags
+var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
+ optionalEndTagInlineElements = makeMap("rp,rt"),
+ optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements);
+
+// Safe Block Elements - HTML5
+var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("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"));
+
+// Inline Elements - HTML5
+var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("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"));
+
+
+// Special Elements (can contain anything)
+var specialElements = makeMap("script,style");
+
+var validElements = angular.extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements);
+
+//Attributes that have href and hence need to be sanitized
+var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
+var validAttrs = angular.extend({}, uriAttrs, makeMap(
+ 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
+ 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
+ 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
+ 'scope,scrolling,shape,span,start,summary,target,title,type,'+
+ 'valign,value,vspace,width'));
+
+function makeMap(str) {
+ var obj = {}, items = str.split(','), i;
+ for (i = 0; i < items.length; i++) obj[items[i]] = true;
+ return obj;
+}
+
+
+/**
+ * @example
+ * htmlParser(htmlString, {
+ * start: function(tag, attrs, unary) {},
+ * end: function(tag) {},
+ * chars: function(text) {},
+ * comment: function(text) {}
+ * });
+ *
+ * @param {string} html string
+ * @param {object} handler
+ */
+function htmlParser( html, handler ) {
+ var index, chars, match, stack = [], last = html;
+ stack.last = function() { return stack[ stack.length - 1 ]; };
+
+ while ( html ) {
+ chars = true;
+
+ // Make sure we're not in a script or style element
+ if ( !stack.last() || !specialElements[ stack.last() ] ) {
+
+ // Comment
+ if ( html.indexOf("<!--") === 0 ) {
+ index = html.indexOf("-->");
+
+ if ( index >= 0 ) {
+ if (handler.comment) handler.comment( html.substring( 4, index ) );
+ html = html.substring( index + 3 );
+ chars = false;
+ }
+
+ // end tag
+ } else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
+ match = html.match( END_TAG_REGEXP );
+
+ if ( match ) {
+ html = html.substring( match[0].length );
+ match[0].replace( END_TAG_REGEXP, parseEndTag );
+ chars = false;
+ }
+
+ // start tag
+ } else if ( BEGIN_TAG_REGEXP.test(html) ) {
+ match = html.match( START_TAG_REGEXP );
+
+ if ( match ) {
+ html = html.substring( match[0].length );
+ match[0].replace( START_TAG_REGEXP, parseStartTag );
+ chars = false;
+ }
+ }
+
+ if ( chars ) {
+ index = html.indexOf("<");
+
+ var text = index < 0 ? html : html.substring( 0, index );
+ html = index < 0 ? "" : html.substring( index );
+
+ if (handler.chars) handler.chars( decodeEntities(text) );
+ }
+
+ } else {
+ html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){
+ text = text.
+ replace(COMMENT_REGEXP, "$1").
+ replace(CDATA_REGEXP, "$1");
+
+ if (handler.chars) handler.chars( decodeEntities(text) );
+
+ return "";
+ });
+
+ parseEndTag( "", stack.last() );
+ }
+
+ if ( html == last ) {
+ throw "Parse Error: " + html;
+ }
+ last = html;
+ }
+
+ // Clean up any remaining tags
+ parseEndTag();
+
+ function parseStartTag( tag, tagName, rest, unary ) {
+ tagName = angular.lowercase(tagName);
+ if ( blockElements[ tagName ] ) {
+ while ( stack.last() && inlineElements[ stack.last() ] ) {
+ parseEndTag( "", stack.last() );
+ }
+ }
+
+ if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
+ parseEndTag( "", tagName );
+ }
+
+ unary = voidElements[ tagName ] || !!unary;
+
+ if ( !unary )
+ stack.push( tagName );
+
+ var attrs = {};
+
+ rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) {
+ var value = doubleQuotedValue
+ || singleQoutedValue
+ || unqoutedValue
+ || '';
+
+ attrs[name] = decodeEntities(value);
+ });
+ if (handler.start) handler.start( tagName, attrs, unary );
+ }
+
+ function parseEndTag( tag, tagName ) {
+ var pos = 0, i;
+ tagName = angular.lowercase(tagName);
+ if ( tagName )
+ // Find the closest opened tag of the same type
+ for ( pos = stack.length - 1; pos >= 0; pos-- )
+ if ( stack[ pos ] == tagName )
+ break;
+
+ if ( pos >= 0 ) {
+ // Close all the open elements, up the stack
+ for ( i = stack.length - 1; i >= pos; i-- )
+ if (handler.end) handler.end( stack[ i ] );
+
+ // Remove the open elements from the stack
+ stack.length = pos;
+ }
+ }
+}
+
+/**
+ * decodes all entities into regular string
+ * @param value
+ * @returns {string} A string with decoded entities.
+ */
+var hiddenPre=document.createElement("pre");
+function decodeEntities(value) {
+ hiddenPre.innerHTML=value.replace(/</g,"&lt;");
+ return hiddenPre.innerText || hiddenPre.textContent || '';
+}
+
+/**
+ * Escapes all potentially dangerous characters, so that the
+ * resulting string can be safely inserted into attribute or
+ * element text.
+ * @param value
+ * @returns escaped text
+ */
+function encodeEntities(value) {
+ return value.
+ replace(/&/g, '&amp;').
+ replace(NON_ALPHANUMERIC_REGEXP, function(value){
+ return '&#' + value.charCodeAt(0) + ';';
+ }).
+ replace(/</g, '&lt;').
+ replace(/>/g, '&gt;');
+}
+
+/**
+ * create an HTML/XML writer which writes to buffer
+ * @param {Array} buf use buf.jain('') to get out sanitized html string
+ * @returns {object} in the form of {
+ * start: function(tag, attrs, unary) {},
+ * end: function(tag) {},
+ * chars: function(text) {},
+ * comment: function(text) {}
+ * }
+ */
+function htmlSanitizeWriter(buf){
+ var ignore = false;
+ var out = angular.bind(buf, buf.push);
+ return {
+ start: function(tag, attrs, unary){
+ tag = angular.lowercase(tag);
+ if (!ignore && specialElements[tag]) {
+ ignore = tag;
+ }
+ if (!ignore && validElements[tag] == true) {
+ out('<');
+ out(tag);
+ angular.forEach(attrs, function(value, key){
+ var lkey=angular.lowercase(key);
+ if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
+ out(' ');
+ out(key);
+ out('="');
+ out(encodeEntities(value));
+ out('"');
+ }
+ });
+ out(unary ? '/>' : '>');
+ }
+ },
+ end: function(tag){
+ tag = angular.lowercase(tag);
+ if (!ignore && validElements[tag] == true) {
+ out('</');
+ out(tag);
+ out('>');
+ }
+ if (tag == ignore) {
+ ignore = false;
+ }
+ },
+ chars: function(chars){
+ if (!ignore) {
+ out(encodeEntities(chars));
+ }
+ }
+ };
+}
+
+
+// define ngSanitize module and register $sanitize service
+angular.module('ngSanitize', []).value('$sanitize', $sanitize);
+
+/**
+ * @ngdoc directive
+ * @name ngSanitize.directive:ngBindHtml
+ *
+ * @description
+ * Creates a binding that will sanitize the result of evaluating the `expression` with the
+ * {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element.
+ *
+ * See {@link ngSanitize.$sanitize $sanitize} docs for examples.
+ *
+ * @element ANY
+ * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
+ */
+angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) {
+ return function(scope, element, attr) {
+ element.addClass('ng-binding').data('$binding', attr.ngBindHtml);
+ scope.$watch(attr.ngBindHtml, function(value) {
+ value = $sanitize(value);
+ element.html(value || '');
+ });
+ };
+}]);
+/**
+ * @ngdoc filter
+ * @name ngSanitize.filter:linky
+ * @function
+ *
+ * @description
+ * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
+ * plain email address links.
+ *
+ * @param {string} text Input text.
+ * @returns {string} Html-linkified text.
+ *
+ * @usage
+ <span ng-bind-html="linky_expression | linky"></span>
+ *
+ * @example
+ <doc:example module="ngSanitize">
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.snippet =
+ 'Pretty text with some links:\n'+
+ 'http://angularjs.org/,\n'+
+ 'mailto:us@somewhere.org,\n'+
+ 'another@somewhere.org,\n'+
+ 'and one more: ftp://127.0.0.1/.';
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
+ <table>
+ <tr>
+ <td>Filter</td>
+ <td>Source</td>
+ <td>Rendered</td>
+ </tr>
+ <tr id="linky-filter">
+ <td>linky filter</td>
+ <td>
+ <pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
+ </td>
+ <td>
+ <div ng-bind-html="snippet | linky"></div>
+ </td>
+ </tr>
+ <tr id="escaped-html">
+ <td>no filter</td>
+ <td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
+ <td><div ng-bind="snippet"></div></td>
+ </tr>
+ </table>
+ </doc:source>
+ <doc:scenario>
+ it('should linkify the snippet with urls', function() {
+ expect(using('#linky-filter').binding('snippet | linky')).
+ toBe('Pretty text with some links:&#10;' +
+ '<a href="http://angularjs.org/">http://angularjs.org/</a>,&#10;' +
+ '<a href="mailto:us@somewhere.org">us@somewhere.org</a>,&#10;' +
+ '<a href="mailto:another@somewhere.org">another@somewhere.org</a>,&#10;' +
+ 'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
+ });
+
+ it ('should not linkify snippet without the linky filter', function() {
+ expect(using('#escaped-html').binding('snippet')).
+ toBe("Pretty text with some links:\n" +
+ "http://angularjs.org/,\n" +
+ "mailto:us@somewhere.org,\n" +
+ "another@somewhere.org,\n" +
+ "and one more: ftp://127.0.0.1/.");
+ });
+
+ it('should update', function() {
+ input('snippet').enter('new http://link.');
+ expect(using('#linky-filter').binding('snippet | linky')).
+ toBe('new <a href="http://link">http://link</a>.');
+ expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+angular.module('ngSanitize').filter('linky', function() {
+ var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,
+ MAILTO_REGEXP = /^mailto:/;
+
+ return function(text) {
+ if (!text) return text;
+ var match;
+ var raw = text;
+ var html = [];
+ // TODO(vojta): use $sanitize instead
+ var writer = htmlSanitizeWriter(html);
+ var url;
+ var i;
+ while ((match = raw.match(LINKY_URL_REGEXP))) {
+ // We can not end in these as they are sometimes found at the end of the sentence
+ url = match[0];
+ // if we did not match ftp/http/mailto then assume mailto
+ if (match[2] == match[3]) url = 'mailto:' + url;
+ i = match.index;
+ writer.chars(raw.substr(0, i));
+ writer.start('a', {href:url});
+ writer.chars(match[0].replace(MAILTO_REGEXP, ''));
+ writer.end('a');
+ raw = raw.substring(i + match[0].length);
+ }
+ writer.chars(raw);
+ return html.join('');
+ };
+});
+
+})(window, window.angular);
View
13 vendor/assets/javascripts/angular-sanitize.min.js
@@ -0,0 +1,13 @@
+/*
+ AngularJS v1.0.2
+ (c) 2010-2012 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(I,g){'use strict';function i(a){var d={},a=a.split(","),b;for(b=0;b<a.length;b++)d[a[b]]=!0;return d}function z(a,d){function b(a,b,c,h){b=g.lowercase(b);if(m[b])for(;f.last()&&n[f.last()];)e("",f.last());o[b]&&f.last()==b&&e("",b);(h=p[b]||!!h)||f.push(b);var j={};c.replace(A,function(a,b,d,e,c){j[b]=k(d||e||c||"")});d.start&&d.start(b,j,h)}function e(a,b){var e=0,c;if(b=g.lowercase(b))for(e=f.length-1;e>=0;e--)if(f[e]==b)break;if(e>=0){for(c=f.length-1;c>=e;c--)d.end&&d.end(f[c]);f.length=
+e}}var c,h,f=[],j=a;for(f.last=function(){return f[f.length-1]};a;){h=!0;if(!f.last()||!q[f.last()]){if(a.indexOf("<\!--")===0)c=a.indexOf("--\>"),c>=0&&(d.comment&&d.comment(a.substring(4,c)),a=a.substring(c+3),h=!1);else if(B.test(a)){if(c=a.match(r))a=a.substring(c[0].length),c[0].replace(r,e),h=!1}else if(C.test(a)&&(c=a.match(s)))a=a.substring(c[0].length),c[0].replace(s,b),h=!1;h&&(c=a.indexOf("<"),h=c<0?a:a.substring(0,c),a=c<0?"":a.substring(c),d.chars&&d.chars(k(h)))}else a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+
+f.last()+"[^>]*>","i"),function(b,a){a=a.replace(D,"$1").replace(E,"$1");d.chars&&d.chars(k(a));return""}),e("",f.last());if(a==j)throw"Parse Error: "+a;j=a}e()}function k(a){l.innerHTML=a.replace(/</g,"&lt;");return l.innerText||l.textContent||""}function t(a){return a.replace(/&/g,"&amp;").replace(F,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"&lt;").replace(/>/g,"&gt;")}function u(a){var d=!1,b=g.bind(a,a.push);return{start:function(a,c,h){a=g.lowercase(a);!d&&q[a]&&(d=a);!d&&v[a]==
+!0&&(b("<"),b(a),g.forEach(c,function(a,c){var e=g.lowercase(c);if(G[e]==!0&&(w[e]!==!0||a.match(H)))b(" "),b(c),b('="'),b(t(a)),b('"')}),b(h?"/>":">"))},end:function(a){a=g.lowercase(a);!d&&v[a]==!0&&(b("</"),b(a),b(">"));a==d&&(d=!1)},chars:function(a){d||b(t(a))}}}var s=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,r=/^<\s*\/\s*([\w:-]+)[^>]*>/,A=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,C=/^</,B=/^<\s*\//,D=/<\!--(.*?)--\>/g,
+E=/<!\[CDATA\[(.*?)]]\>/g,H=/^((ftp|https?):\/\/|mailto:|#)/,F=/([^\#-~| |!])/g,p=i("area,br,col,hr,img,wbr"),x=i("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),y=i("rp,rt"),o=g.extend({},y,x),m=g.extend({},x,i("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")),n=g.extend({},y,i("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")),
+q=i("script,style"),v=g.extend({},p,m,n,o),w=i("background,cite,href,longdesc,src,usemap"),G=g.extend({},w,i("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),l=document.createElement("pre");g.module("ngSanitize",[]).value("$sanitize",function(a){var d=[];
+z(a,u(d));return d.join("")});g.module("ngSanitize").directive("ngBindHtml",["$sanitize",function(a){return function(d,b,e){b.addClass("ng-binding").data("$binding",e.ngBindHtml);d.$watch(e.ngBindHtml,function(c){c=a(c);b.html(c||"")})}}]);g.module("ngSanitize").filter("linky",function(){var a=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,d=/^mailto:/;return function(b){if(!b)return b;for(var e=b,c=[],h=u(c),f,g;b=e.match(a);)f=b[0],b[2]==b[3]&&(f="mailto:"+f),g=b.index,
+h.chars(e.substr(0,g)),h.start("a",{href:f}),h.chars(b[0].replace(d,"")),h.end("a"),e=e.substring(g+b[0].length);h.chars(e);return c.join("")}})})(window,window.angular);
View
14,401 vendor/assets/javascripts/angular.js
14,401 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
289 vendor/assets/javascripts/angular.min.js
155 additions, 134 deletions not shown because the diff is too large. Please use a local Git client to view these changes.

0 comments on commit 3d6a04d

Please sign in to comment.
Something went wrong with that request. Please try again.