├── .gitignore ├── Gemfile ├── MIT-LICENSE ├── README.md ├── Rakefile ├── angularjs-rails.gemspec ├── lib ├── angularjs-rails.rb └── angularjs-rails │ ├── engine.rb │ ├── sprockets.rb │ └── version.rb ├── tasks └── angularjs-rails │ ├── stable_updater.rb │ ├── unstable_updater.rb │ └── updater.rb └── vendor └── assets └── javascripts ├── angular-animate.js ├── angular-aria.js ├── angular-cookies.js ├── angular-loader.js ├── angular-message-format.js ├── angular-messages.js ├── angular-mocks.js ├── angular-parse-ext.js ├── angular-resource.js ├── angular-route.js ├── angular-sanitize.js ├── angular-touch.js ├── angular.js └── unstable ├── angular2-polyfills.js └── angular2.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.gem 3 | *.rbc 4 | .bundle 5 | .config 6 | .yardoc 7 | Gemfile.lock 8 | InstalledFiles 9 | _yardoc 10 | coverage 11 | doc/ 12 | lib/bundler/man 13 | pkg 14 | rdoc 15 | spec/reports 16 | test/tmp 17 | test/version_tmp 18 | tmp 19 | /.idea 20 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in angularjs-rails.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Angular.JS RubyGem Copyright (c) 2012 Hirav Gandhi 2 | 3 | Angular.JS and related components Copyright (c) 2010-2012 Google Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angularjs-rails Gem Version 2 | 3 | angularjs-rails wraps the [Angular.js](http://angularjs.org) library for use in Rails 3.1 and above. Assets will minify automatically during production. 4 | 5 | **If you find this gem useful, please consider donating - your contributions will help me keep this gem updated.** 6 | 7 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=NCT7TZEFY2T9Y) 8 | 9 | 10 | ## Usage 11 | 12 | Add the following to your Gemfile: 13 | 14 | gem 'angularjs-rails' 15 | 16 | Add the following directive to your JavaScript manifest file (application.js): 17 | 18 | //= require angular 19 | 20 | If you desire to require (optional) Angular files, you may include them as well in your JavaScript manifest file (application.js). For example: 21 | 22 | //= require angular-animate 23 | //= require angular-resource 24 | 25 | To use the 'unstable' branch, add the following directive to your JavaScript manifest file (application.js): 26 | 27 | //= require unstable/angular2 28 | 29 | ## Versioning 30 | 31 | Every attempt is made to mirror the currently shipping Angular.js version number wherever possible. 32 | 33 | The major, minor, and patch version numbers will always represent the Angular.js version. 34 | 35 | ## IMPORTANT: Requesting upgrades for new Angular.js versions 36 | 37 | To request that the latest version of Angular.JS be pushed as a gem to RubyGems, please create a new issue instead of pull requests. 38 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | 4 | require_relative 'tasks/angularjs-rails/stable_updater' 5 | require_relative 'tasks/angularjs-rails/unstable_updater' 6 | 7 | desc "Update Angular JS assets" 8 | task :update_angular do 9 | AngularJS::Rails::StableUpdater.update_js! 10 | AngularJS::Rails::UnstableUpdater.update_js! 11 | end 12 | -------------------------------------------------------------------------------- /angularjs-rails.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/angularjs-rails/version', __FILE__) 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'angularjs-rails' 6 | s.version = AngularJS::Rails::VERSION 7 | s.date = Time.new.strftime("%Y-%m-%d") 8 | s.summary = 'Angular.js on Rails' 9 | s.description = 'Injects Angular.js into your asset pipeline as well as other Angular modules.' 10 | s.authors = ["Hirav Gandhi"] 11 | s.email = 'hgandhi@alumni.stanford.edu' 12 | s.files = Dir["{lib,vendor}/**/*"] + ["MIT-LICENSE", "README.md"] 13 | s.homepage = 'https://github.com/hiravgandhi/angularjs-rails/' 14 | s.license = 'MIT' 15 | 16 | s.add_development_dependency 'rake' 17 | s.add_development_dependency 'versionomy' 18 | s.add_development_dependency 'nokogiri' 19 | end 20 | -------------------------------------------------------------------------------- /lib/angularjs-rails.rb: -------------------------------------------------------------------------------- 1 | require "angularjs-rails/version" 2 | 3 | module AngularJS 4 | module Rails 5 | if defined? ::Rails::Engine 6 | require "angularjs-rails/engine" 7 | elsif defined? Sprockets 8 | require "angularjs-rails/sprockets" 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/angularjs-rails/engine.rb: -------------------------------------------------------------------------------- 1 | module AngularJS 2 | module Rails 3 | class Engine < ::Rails::Engine 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/angularjs-rails/sprockets.rb: -------------------------------------------------------------------------------- 1 | require 'sprockets' 2 | 3 | Sprockets.append_path File.expand_path("../../../vendor/assets/javascripts", __FILE__) 4 | -------------------------------------------------------------------------------- /lib/angularjs-rails/version.rb: -------------------------------------------------------------------------------- 1 | module AngularJS 2 | module Rails 3 | VERSION = "1.8.0" 4 | UNSTABLE_VERSION = "2.0.0-beta.17" 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /tasks/angularjs-rails/stable_updater.rb: -------------------------------------------------------------------------------- 1 | require_relative 'updater' 2 | 3 | module AngularJS::Rails 4 | class StableUpdater < Updater 5 | def current_gem_version 6 | Versionomy.parse(AngularJS::Rails::VERSION).convert(:rubygems) 7 | end 8 | 9 | # Currently stable versions have even minor 10 | def own_version(version) 11 | version.release_type == :final 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /tasks/angularjs-rails/unstable_updater.rb: -------------------------------------------------------------------------------- 1 | require_relative 'updater' 2 | 3 | module AngularJS::Rails 4 | class UnstableUpdater < Updater 5 | def base_path 6 | ROOT_PATH.join('unstable') 7 | end 8 | 9 | def current_gem_version 10 | Versionomy.parse(AngularJS::Rails::UNSTABLE_VERSION).convert(:rubygems) 11 | end 12 | 13 | def own_version(version) 14 | # Currently unstable versions have some minor 15 | version.release_type != :final 16 | end 17 | 18 | def version_constant_name 19 | "UNSTABLE_VERSION" 20 | end 21 | 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /tasks/angularjs-rails/updater.rb: -------------------------------------------------------------------------------- 1 | require 'nokogiri' 2 | require 'open-uri' 3 | require 'versionomy' 4 | require 'pathname' 5 | require 'tempfile' 6 | 7 | module AngularJS::Rails 8 | class Updater 9 | BASE_URL = 'https://code.angularjs.org' 10 | ROOT_PATH = Pathname.new('vendor/assets/javascripts') 11 | 12 | def initialize 13 | end 14 | 15 | def base_path 16 | ROOT_PATH 17 | end 18 | 19 | def update_js! 20 | if latest_version >= current_gem_version 21 | clean 22 | download_files 23 | update_version_to(latest_version) 24 | end 25 | end 26 | 27 | def current_gem_version 28 | raise NotImplementedError 29 | end 30 | 31 | def available_versions 32 | upstream_versions.find_all {|v| own_version(v) } 33 | end 34 | 35 | def latest_version 36 | available_versions.last 37 | end 38 | 39 | def version_constant_name 40 | "VERSION" 41 | end 42 | 43 | private 44 | 45 | def clean 46 | base_path.children.each do |f| 47 | f.delete if f.file? 48 | end 49 | end 50 | 51 | def update_version_to(version) 52 | version_file = Pathname.new('lib/angularjs-rails/version.rb') 53 | content = version_file.read 54 | version_line = content.lines.find { |l| l =~ /^\s+#{version_constant_name}/ } 55 | 56 | # version_to_convert = Versionomy.parse(version).convert(:rubygems).to_s 57 | 58 | new_version_line = version_line.gsub(/"[^"]+"/, %Q{"#{version}"}) 59 | version_file.open('w+') do |f| 60 | f.write content.gsub(version_line, new_version_line) 61 | end 62 | end 63 | 64 | def upstream_versions 65 | @upstream_versions ||= Nokogiri::HTML.parse(open(BASE_URL)). 66 | css('a').map { |e| Versionomy.parse e.text[0..-2] rescue nil }.compact.sort 67 | end 68 | 69 | def own_version(v) 70 | true 71 | end 72 | 73 | def self.update_js! 74 | self.new.update_js! 75 | end 76 | 77 | def download_files 78 | url = BASE_URL + "/" + latest_version.to_s 79 | Nokogiri::HTML.parse(open(url)). 80 | css('a'). 81 | 82 | #only angular js files 83 | map{|a| a[:href] =~ /angular[^.]*\.js/ ? a : nil }.compact. 84 | 85 | each do |a| 86 | download_file(a[:href], url) 87 | end 88 | end 89 | 90 | def download_file(file, url) 91 | full_url = url + "/" + file 92 | full_path = base_path.join(file) 93 | full_path.open('w+') do |f| 94 | f.write open(full_url).read 95 | end 96 | end 97 | end 98 | end 99 | 100 | 101 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/angular-aria.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.8.0 3 | * (c) 2010-2020 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular) {'use strict'; 7 | 8 | /** 9 | * @ngdoc module 10 | * @name ngAria 11 | * @description 12 | * 13 | * The `ngAria` module provides support for common 14 | * [ARIA](http://www.w3.org/TR/wai-aria/) 15 | * attributes that convey state or semantic information about the application for users 16 | * of assistive technologies, such as screen readers. 17 | * 18 | * ## Usage 19 | * 20 | * For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following 21 | * directives are supported: 22 | * `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, 23 | * `ngClick`, `ngDblClick`, and `ngMessages`. 24 | * 25 | * Below is a more detailed breakdown of the attributes handled by ngAria: 26 | * 27 | * | Directive | Supported Attributes | 28 | * |---------------------------------------------|-----------------------------------------------------------------------------------------------------| 29 | * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles | 30 | * | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled | 31 | * | {@link ng.directive:ngRequired ngRequired} | aria-required | 32 | * | {@link ng.directive:ngChecked ngChecked} | aria-checked | 33 | * | {@link ng.directive:ngReadonly ngReadonly} | aria-readonly | 34 | * | {@link ng.directive:ngValue ngValue} | aria-checked | 35 | * | {@link ng.directive:ngShow ngShow} | aria-hidden | 36 | * | {@link ng.directive:ngHide ngHide} | aria-hidden | 37 | * | {@link ng.directive:ngDblclick ngDblclick} | tabindex | 38 | * | {@link module:ngMessages ngMessages} | aria-live | 39 | * | {@link ng.directive:ngClick ngClick} | tabindex, keydown event, button role | 40 | * 41 | * Find out more information about each directive by reading the 42 | * {@link guide/accessibility ngAria Developer Guide}. 43 | * 44 | * ## Example 45 | * Using ngDisabled with ngAria: 46 | * ```html 47 | * 48 | * ``` 49 | * Becomes: 50 | * ```html 51 | * 52 | * ``` 53 | * 54 | * ## Disabling Specific Attributes 55 | * It is possible to disable individual attributes added by ngAria with the 56 | * {@link ngAria.$ariaProvider#config config} method. For more details, see the 57 | * {@link guide/accessibility Developer Guide}. 58 | * 59 | * ## Disabling `ngAria` on Specific Elements 60 | * It is possible to make `ngAria` ignore a specific element, by adding the `ng-aria-disable` 61 | * attribute on it. Note that only the element itself (and not its child elements) will be ignored. 62 | */ 63 | var ARIA_DISABLE_ATTR = 'ngAriaDisable'; 64 | 65 | var ngAriaModule = angular.module('ngAria', ['ng']). 66 | info({ angularVersion: '1.8.0' }). 67 | provider('$aria', $AriaProvider); 68 | 69 | /** 70 | * Internal Utilities 71 | */ 72 | var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT', 'DETAILS', 'SUMMARY']; 73 | 74 | var isNodeOneOf = function(elem, nodeTypeArray) { 75 | if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) { 76 | return true; 77 | } 78 | }; 79 | /** 80 | * @ngdoc provider 81 | * @name $ariaProvider 82 | * @this 83 | * 84 | * @description 85 | * 86 | * Used for configuring the ARIA attributes injected and managed by ngAria. 87 | * 88 | * ```js 89 | * angular.module('myApp', ['ngAria'], function config($ariaProvider) { 90 | * $ariaProvider.config({ 91 | * ariaValue: true, 92 | * tabindex: false 93 | * }); 94 | * }); 95 | *``` 96 | * 97 | * ## Dependencies 98 | * Requires the {@link ngAria} module to be installed. 99 | * 100 | */ 101 | function $AriaProvider() { 102 | var config = { 103 | ariaHidden: true, 104 | ariaChecked: true, 105 | ariaReadonly: true, 106 | ariaDisabled: true, 107 | ariaRequired: true, 108 | ariaInvalid: true, 109 | ariaValue: true, 110 | tabindex: true, 111 | bindKeydown: true, 112 | bindRoleForClick: true 113 | }; 114 | 115 | /** 116 | * @ngdoc method 117 | * @name $ariaProvider#config 118 | * 119 | * @param {object} config object to enable/disable specific ARIA attributes 120 | * 121 | * - **ariaHidden** – `{boolean}` – Enables/disables aria-hidden tags 122 | * - **ariaChecked** – `{boolean}` – Enables/disables aria-checked tags 123 | * - **ariaReadonly** – `{boolean}` – Enables/disables aria-readonly tags 124 | * - **ariaDisabled** – `{boolean}` – Enables/disables aria-disabled tags 125 | * - **ariaRequired** – `{boolean}` – Enables/disables aria-required tags 126 | * - **ariaInvalid** – `{boolean}` – Enables/disables aria-invalid tags 127 | * - **ariaValue** – `{boolean}` – Enables/disables aria-valuemin, aria-valuemax and 128 | * aria-valuenow tags 129 | * - **tabindex** – `{boolean}` – Enables/disables tabindex tags 130 | * - **bindKeydown** – `{boolean}` – Enables/disables keyboard event binding on non-interactive 131 | * elements (such as `div` or `li`) using ng-click, making them more accessible to users of 132 | * assistive technologies 133 | * - **bindRoleForClick** – `{boolean}` – Adds role=button to non-interactive elements (such as 134 | * `div` or `li`) using ng-click, making them more accessible to users of assistive 135 | * technologies 136 | * 137 | * @description 138 | * Enables/disables various ARIA attributes 139 | */ 140 | this.config = function(newConfig) { 141 | config = angular.extend(config, newConfig); 142 | }; 143 | 144 | function watchExpr(attrName, ariaAttr, nodeBlackList, negate) { 145 | return function(scope, elem, attr) { 146 | if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return; 147 | 148 | var ariaCamelName = attr.$normalize(ariaAttr); 149 | if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) { 150 | scope.$watch(attr[attrName], function(boolVal) { 151 | // ensure boolean value 152 | boolVal = negate ? !boolVal : !!boolVal; 153 | elem.attr(ariaAttr, boolVal); 154 | }); 155 | } 156 | }; 157 | } 158 | /** 159 | * @ngdoc service 160 | * @name $aria 161 | * 162 | * @description 163 | * 164 | * The $aria service contains helper methods for applying common 165 | * [ARIA](http://www.w3.org/TR/wai-aria/) attributes to HTML directives. 166 | * 167 | * ngAria injects common accessibility attributes that tell assistive technologies when HTML 168 | * elements are enabled, selected, hidden, and more. To see how this is performed with ngAria, 169 | * let's review a code snippet from ngAria itself: 170 | * 171 | *```js 172 | * ngAriaModule.directive('ngDisabled', ['$aria', function($aria) { 173 | * return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false); 174 | * }]) 175 | *``` 176 | * Shown above, the ngAria module creates a directive with the same signature as the 177 | * traditional `ng-disabled` directive. But this ngAria version is dedicated to 178 | * solely managing accessibility attributes on custom elements. The internal `$aria` service is 179 | * used to watch the boolean attribute `ngDisabled`. If it has not been explicitly set by the 180 | * developer, `aria-disabled` is injected as an attribute with its value synchronized to the 181 | * value in `ngDisabled`. 182 | * 183 | * Because ngAria hooks into the `ng-disabled` directive, developers do not have to do 184 | * anything to enable this feature. The `aria-disabled` attribute is automatically managed 185 | * simply as a silent side-effect of using `ng-disabled` with the ngAria module. 186 | * 187 | * The full list of directives that interface with ngAria: 188 | * * **ngModel** 189 | * * **ngChecked** 190 | * * **ngReadonly** 191 | * * **ngRequired** 192 | * * **ngDisabled** 193 | * * **ngValue** 194 | * * **ngShow** 195 | * * **ngHide** 196 | * * **ngClick** 197 | * * **ngDblclick** 198 | * * **ngMessages** 199 | * 200 | * Read the {@link guide/accessibility ngAria Developer Guide} for a thorough explanation of each 201 | * directive. 202 | * 203 | * 204 | * ## Dependencies 205 | * Requires the {@link ngAria} module to be installed. 206 | */ 207 | this.$get = function() { 208 | return { 209 | config: function(key) { 210 | return config[key]; 211 | }, 212 | $$watchExpr: watchExpr 213 | }; 214 | }; 215 | } 216 | 217 | 218 | ngAriaModule.directive('ngShow', ['$aria', function($aria) { 219 | return $aria.$$watchExpr('ngShow', 'aria-hidden', [], true); 220 | }]) 221 | .directive('ngHide', ['$aria', function($aria) { 222 | return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false); 223 | }]) 224 | .directive('ngValue', ['$aria', function($aria) { 225 | return $aria.$$watchExpr('ngValue', 'aria-checked', nodeBlackList, false); 226 | }]) 227 | .directive('ngChecked', ['$aria', function($aria) { 228 | return $aria.$$watchExpr('ngChecked', 'aria-checked', nodeBlackList, false); 229 | }]) 230 | .directive('ngReadonly', ['$aria', function($aria) { 231 | return $aria.$$watchExpr('ngReadonly', 'aria-readonly', nodeBlackList, false); 232 | }]) 233 | .directive('ngRequired', ['$aria', function($aria) { 234 | return $aria.$$watchExpr('ngRequired', 'aria-required', nodeBlackList, false); 235 | }]) 236 | .directive('ngModel', ['$aria', function($aria) { 237 | 238 | function shouldAttachAttr(attr, normalizedAttr, elem, allowBlacklistEls) { 239 | return $aria.config(normalizedAttr) && 240 | !elem.attr(attr) && 241 | (allowBlacklistEls || !isNodeOneOf(elem, nodeBlackList)) && 242 | (elem.attr('type') !== 'hidden' || elem[0].nodeName !== 'INPUT'); 243 | } 244 | 245 | function shouldAttachRole(role, elem) { 246 | // if element does not have role attribute 247 | // AND element type is equal to role (if custom element has a type equaling shape) <-- remove? 248 | // AND element is not in nodeBlackList 249 | return !elem.attr('role') && (elem.attr('type') === role) && !isNodeOneOf(elem, nodeBlackList); 250 | } 251 | 252 | function getShape(attr, elem) { 253 | var type = attr.type, 254 | role = attr.role; 255 | 256 | return ((type || role) === 'checkbox' || role === 'menuitemcheckbox') ? 'checkbox' : 257 | ((type || role) === 'radio' || role === 'menuitemradio') ? 'radio' : 258 | (type === 'range' || role === 'progressbar' || role === 'slider') ? 'range' : ''; 259 | } 260 | 261 | return { 262 | restrict: 'A', 263 | require: 'ngModel', 264 | priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value 265 | compile: function(elem, attr) { 266 | if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return; 267 | 268 | var shape = getShape(attr, elem); 269 | 270 | return { 271 | post: function(scope, elem, attr, ngModel) { 272 | var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem, false); 273 | 274 | function ngAriaWatchModelValue() { 275 | return ngModel.$modelValue; 276 | } 277 | 278 | function getRadioReaction(newVal) { 279 | // Strict comparison would cause a BC 280 | // eslint-disable-next-line eqeqeq 281 | var boolVal = (attr.value == ngModel.$viewValue); 282 | elem.attr('aria-checked', boolVal); 283 | } 284 | 285 | function getCheckboxReaction() { 286 | elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue)); 287 | } 288 | 289 | switch (shape) { 290 | case 'radio': 291 | case 'checkbox': 292 | if (shouldAttachRole(shape, elem)) { 293 | elem.attr('role', shape); 294 | } 295 | if (shouldAttachAttr('aria-checked', 'ariaChecked', elem, false)) { 296 | scope.$watch(ngAriaWatchModelValue, shape === 'radio' ? 297 | getRadioReaction : getCheckboxReaction); 298 | } 299 | if (needsTabIndex) { 300 | elem.attr('tabindex', 0); 301 | } 302 | break; 303 | case 'range': 304 | if (shouldAttachRole(shape, elem)) { 305 | elem.attr('role', 'slider'); 306 | } 307 | if ($aria.config('ariaValue')) { 308 | var needsAriaValuemin = !elem.attr('aria-valuemin') && 309 | (attr.hasOwnProperty('min') || attr.hasOwnProperty('ngMin')); 310 | var needsAriaValuemax = !elem.attr('aria-valuemax') && 311 | (attr.hasOwnProperty('max') || attr.hasOwnProperty('ngMax')); 312 | var needsAriaValuenow = !elem.attr('aria-valuenow'); 313 | 314 | if (needsAriaValuemin) { 315 | attr.$observe('min', function ngAriaValueMinReaction(newVal) { 316 | elem.attr('aria-valuemin', newVal); 317 | }); 318 | } 319 | if (needsAriaValuemax) { 320 | attr.$observe('max', function ngAriaValueMinReaction(newVal) { 321 | elem.attr('aria-valuemax', newVal); 322 | }); 323 | } 324 | if (needsAriaValuenow) { 325 | scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) { 326 | elem.attr('aria-valuenow', newVal); 327 | }); 328 | } 329 | } 330 | if (needsTabIndex) { 331 | elem.attr('tabindex', 0); 332 | } 333 | break; 334 | } 335 | 336 | if (!attr.hasOwnProperty('ngRequired') && ngModel.$validators.required 337 | && shouldAttachAttr('aria-required', 'ariaRequired', elem, false)) { 338 | // ngModel.$error.required is undefined on custom controls 339 | attr.$observe('required', function() { 340 | elem.attr('aria-required', !!attr['required']); 341 | }); 342 | } 343 | 344 | if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem, true)) { 345 | scope.$watch(function ngAriaInvalidWatch() { 346 | return ngModel.$invalid; 347 | }, function ngAriaInvalidReaction(newVal) { 348 | elem.attr('aria-invalid', !!newVal); 349 | }); 350 | } 351 | } 352 | }; 353 | } 354 | }; 355 | }]) 356 | .directive('ngDisabled', ['$aria', function($aria) { 357 | return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false); 358 | }]) 359 | .directive('ngMessages', function() { 360 | return { 361 | restrict: 'A', 362 | require: '?ngMessages', 363 | link: function(scope, elem, attr, ngMessages) { 364 | if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return; 365 | 366 | if (!elem.attr('aria-live')) { 367 | elem.attr('aria-live', 'assertive'); 368 | } 369 | } 370 | }; 371 | }) 372 | .directive('ngClick',['$aria', '$parse', function($aria, $parse) { 373 | return { 374 | restrict: 'A', 375 | compile: function(elem, attr) { 376 | if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return; 377 | 378 | var fn = $parse(attr.ngClick); 379 | return function(scope, elem, attr) { 380 | 381 | if (!isNodeOneOf(elem, nodeBlackList)) { 382 | 383 | if ($aria.config('bindRoleForClick') && !elem.attr('role')) { 384 | elem.attr('role', 'button'); 385 | } 386 | 387 | if ($aria.config('tabindex') && !elem.attr('tabindex')) { 388 | elem.attr('tabindex', 0); 389 | } 390 | 391 | if ($aria.config('bindKeydown') && !attr.ngKeydown && !attr.ngKeypress && !attr.ngKeyup) { 392 | elem.on('keydown', function(event) { 393 | var keyCode = event.which || event.keyCode; 394 | 395 | if (keyCode === 13 || keyCode === 32) { 396 | // If the event is triggered on a non-interactive element ... 397 | if (nodeBlackList.indexOf(event.target.nodeName) === -1 && !event.target.isContentEditable) { 398 | // ... prevent the default browser behavior (e.g. scrolling when pressing spacebar) 399 | // See https://github.com/angular/angular.js/issues/16664 400 | event.preventDefault(); 401 | } 402 | scope.$apply(callback); 403 | } 404 | 405 | function callback() { 406 | fn(scope, { $event: event }); 407 | } 408 | }); 409 | } 410 | } 411 | }; 412 | } 413 | }; 414 | }]) 415 | .directive('ngDblclick', ['$aria', function($aria) { 416 | return function(scope, elem, attr) { 417 | if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return; 418 | 419 | if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nodeBlackList)) { 420 | elem.attr('tabindex', 0); 421 | } 422 | }; 423 | }]); 424 | 425 | 426 | })(window, window.angular); 427 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/angular-cookies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.8.0 3 | * (c) 2010-2020 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular) {'use strict'; 7 | 8 | /** 9 | * @ngdoc module 10 | * @name ngCookies 11 | * @description 12 | * 13 | * The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies. 14 | * 15 | * See {@link ngCookies.$cookies `$cookies`} for usage. 16 | */ 17 | 18 | 19 | angular.module('ngCookies', ['ng']). 20 | info({ angularVersion: '1.8.0' }). 21 | /** 22 | * @ngdoc provider 23 | * @name $cookiesProvider 24 | * @description 25 | * Use `$cookiesProvider` to change the default behavior of the {@link ngCookies.$cookies $cookies} service. 26 | * */ 27 | provider('$cookies', [/** @this */function $CookiesProvider() { 28 | /** 29 | * @ngdoc property 30 | * @name $cookiesProvider#defaults 31 | * @description 32 | * 33 | * Object containing default options to pass when setting cookies. 34 | * 35 | * The object may have following properties: 36 | * 37 | * - **path** - `{string}` - The cookie will be available only for this path and its 38 | * sub-paths. By default, this is the URL that appears in your `` tag. 39 | * - **domain** - `{string}` - The cookie will be available only for this domain and 40 | * its sub-domains. For security reasons the user agent will not accept the cookie 41 | * if the current domain is not a sub-domain of this domain or equal to it. 42 | * - **expires** - `{string|Date}` - String of the form "Wdy, DD Mon YYYY HH:MM:SS GMT" 43 | * or a Date object indicating the exact date/time this cookie will expire. 44 | * - **secure** - `{boolean}` - If `true`, then the cookie will only be available through a 45 | * secured connection. 46 | * - **samesite** - `{string}` - prevents the browser from sending the cookie along with cross-site requests. 47 | * Accepts the values `lax` and `strict`. See the [OWASP Wiki](https://www.owasp.org/index.php/SameSite) 48 | * for more info. Note that as of May 2018, not all browsers support `SameSite`, 49 | * so it cannot be used as a single measure against Cross-Site-Request-Forgery (CSRF) attacks. 50 | * 51 | * Note: By default, the address that appears in your `` tag will be used as the path. 52 | * This is important so that cookies will be visible for all routes when html5mode is enabled. 53 | * 54 | * @example 55 | * 56 | * ```js 57 | * angular.module('cookiesProviderExample', ['ngCookies']) 58 | * .config(['$cookiesProvider', function($cookiesProvider) { 59 | * // Setting default options 60 | * $cookiesProvider.defaults.domain = 'foo.com'; 61 | * $cookiesProvider.defaults.secure = true; 62 | * }]); 63 | * ``` 64 | **/ 65 | var defaults = this.defaults = {}; 66 | 67 | function calcOptions(options) { 68 | return options ? angular.extend({}, defaults, options) : defaults; 69 | } 70 | 71 | /** 72 | * @ngdoc service 73 | * @name $cookies 74 | * 75 | * @description 76 | * Provides read/write access to browser's cookies. 77 | * 78 | *
79 | * Up until AngularJS 1.3, `$cookies` exposed properties that represented the 80 | * current browser cookie values. In version 1.4, this behavior has changed, and 81 | * `$cookies` now provides a standard api of getters, setters etc. 82 | *
83 | * 84 | * Requires the {@link ngCookies `ngCookies`} module to be installed. 85 | * 86 | * @example 87 | * 88 | * ```js 89 | * angular.module('cookiesExample', ['ngCookies']) 90 | * .controller('ExampleController', ['$cookies', function($cookies) { 91 | * // Retrieving a cookie 92 | * var favoriteCookie = $cookies.get('myFavorite'); 93 | * // Setting a cookie 94 | * $cookies.put('myFavorite', 'oatmeal'); 95 | * }]); 96 | * ``` 97 | */ 98 | this.$get = ['$$cookieReader', '$$cookieWriter', function($$cookieReader, $$cookieWriter) { 99 | return { 100 | /** 101 | * @ngdoc method 102 | * @name $cookies#get 103 | * 104 | * @description 105 | * Returns the value of given cookie key 106 | * 107 | * @param {string} key Id to use for lookup. 108 | * @returns {string} Raw cookie value. 109 | */ 110 | get: function(key) { 111 | return $$cookieReader()[key]; 112 | }, 113 | 114 | /** 115 | * @ngdoc method 116 | * @name $cookies#getObject 117 | * 118 | * @description 119 | * Returns the deserialized value of given cookie key 120 | * 121 | * @param {string} key Id to use for lookup. 122 | * @returns {Object} Deserialized cookie value. 123 | */ 124 | getObject: function(key) { 125 | var value = this.get(key); 126 | return value ? angular.fromJson(value) : value; 127 | }, 128 | 129 | /** 130 | * @ngdoc method 131 | * @name $cookies#getAll 132 | * 133 | * @description 134 | * Returns a key value object with all the cookies 135 | * 136 | * @returns {Object} All cookies 137 | */ 138 | getAll: function() { 139 | return $$cookieReader(); 140 | }, 141 | 142 | /** 143 | * @ngdoc method 144 | * @name $cookies#put 145 | * 146 | * @description 147 | * Sets a value for given cookie key 148 | * 149 | * @param {string} key Id for the `value`. 150 | * @param {string} value Raw value to be stored. 151 | * @param {Object=} options Options object. 152 | * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults} 153 | */ 154 | put: function(key, value, options) { 155 | $$cookieWriter(key, value, calcOptions(options)); 156 | }, 157 | 158 | /** 159 | * @ngdoc method 160 | * @name $cookies#putObject 161 | * 162 | * @description 163 | * Serializes and sets a value for given cookie key 164 | * 165 | * @param {string} key Id for the `value`. 166 | * @param {Object} value Value to be stored. 167 | * @param {Object=} options Options object. 168 | * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults} 169 | */ 170 | putObject: function(key, value, options) { 171 | this.put(key, angular.toJson(value), options); 172 | }, 173 | 174 | /** 175 | * @ngdoc method 176 | * @name $cookies#remove 177 | * 178 | * @description 179 | * Remove given cookie 180 | * 181 | * @param {string} key Id of the key-value pair to delete. 182 | * @param {Object=} options Options object. 183 | * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults} 184 | */ 185 | remove: function(key, options) { 186 | $$cookieWriter(key, undefined, calcOptions(options)); 187 | } 188 | }; 189 | }]; 190 | }]); 191 | 192 | /** 193 | * @name $$cookieWriter 194 | * @requires $document 195 | * 196 | * @description 197 | * This is a private service for writing cookies 198 | * 199 | * @param {string} name Cookie name 200 | * @param {string=} value Cookie value (if undefined, cookie will be deleted) 201 | * @param {Object=} options Object with options that need to be stored for the cookie. 202 | */ 203 | function $$CookieWriter($document, $log, $browser) { 204 | var cookiePath = $browser.baseHref(); 205 | var rawDocument = $document[0]; 206 | 207 | function buildCookieString(name, value, options) { 208 | var path, expires; 209 | options = options || {}; 210 | expires = options.expires; 211 | path = angular.isDefined(options.path) ? options.path : cookiePath; 212 | if (angular.isUndefined(value)) { 213 | expires = 'Thu, 01 Jan 1970 00:00:00 GMT'; 214 | value = ''; 215 | } 216 | if (angular.isString(expires)) { 217 | expires = new Date(expires); 218 | } 219 | 220 | var str = encodeURIComponent(name) + '=' + encodeURIComponent(value); 221 | str += path ? ';path=' + path : ''; 222 | str += options.domain ? ';domain=' + options.domain : ''; 223 | str += expires ? ';expires=' + expires.toUTCString() : ''; 224 | str += options.secure ? ';secure' : ''; 225 | str += options.samesite ? ';samesite=' + options.samesite : ''; 226 | 227 | // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: 228 | // - 300 cookies 229 | // - 20 cookies per unique domain 230 | // - 4096 bytes per cookie 231 | var cookieLength = str.length + 1; 232 | if (cookieLength > 4096) { 233 | $log.warn('Cookie \'' + name + 234 | '\' possibly not set or overflowed because it was too large (' + 235 | cookieLength + ' > 4096 bytes)!'); 236 | } 237 | 238 | return str; 239 | } 240 | 241 | return function(name, value, options) { 242 | rawDocument.cookie = buildCookieString(name, value, options); 243 | }; 244 | } 245 | 246 | $$CookieWriter.$inject = ['$document', '$log', '$browser']; 247 | 248 | angular.module('ngCookies').provider('$$cookieWriter', /** @this */ function $$CookieWriterProvider() { 249 | this.$get = $$CookieWriter; 250 | }); 251 | 252 | 253 | })(window, window.angular); 254 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/angular-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.8.0 3 | * (c) 2010-2020 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | 7 | (function() {'use strict'; 8 | // NOTE: 9 | // These functions are copied here from `src/Angular.js`, because they are needed inside the 10 | // `angular-loader.js` closure and need to be available before the main `angular.js` script has 11 | // been loaded. 12 | function isFunction(value) {return typeof value === 'function';} 13 | function isDefined(value) {return typeof value !== 'undefined';} 14 | function isNumber(value) {return typeof value === 'number';} 15 | function isObject(value) {return value !== null && typeof value === 'object';} 16 | function isScope(obj) {return obj && obj.$evalAsync && obj.$watch;} 17 | function isUndefined(value) {return typeof value === 'undefined';} 18 | function isWindow(obj) {return obj && obj.window === obj;} 19 | function sliceArgs(args, startIndex) {return Array.prototype.slice.call(args, startIndex || 0);} 20 | function toJsonReplacer(key, value) { 21 | var val = value; 22 | 23 | if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { 24 | val = undefined; 25 | } else if (isWindow(value)) { 26 | val = '$WINDOW'; 27 | } else if (value && window.document === value) { 28 | val = '$DOCUMENT'; 29 | } else if (isScope(value)) { 30 | val = '$SCOPE'; 31 | } 32 | 33 | return val; 34 | } 35 | 36 | /* exported toDebugString */ 37 | 38 | function serializeObject(obj, maxDepth) { 39 | var seen = []; 40 | 41 | // There is no direct way to stringify object until reaching a specific depth 42 | // and a very deep object can cause a performance issue, so we copy the object 43 | // based on this specific depth and then stringify it. 44 | if (isValidObjectMaxDepth(maxDepth)) { 45 | // This file is also included in `angular-loader`, so `copy()` might not always be available in 46 | // the closure. Therefore, it is lazily retrieved as `angular.copy()` when needed. 47 | obj = angular.copy(obj, null, maxDepth); 48 | } 49 | return JSON.stringify(obj, function(key, val) { 50 | val = toJsonReplacer(key, val); 51 | if (isObject(val)) { 52 | 53 | if (seen.indexOf(val) >= 0) return '...'; 54 | 55 | seen.push(val); 56 | } 57 | return val; 58 | }); 59 | } 60 | 61 | function toDebugString(obj, maxDepth) { 62 | if (typeof obj === 'function') { 63 | return obj.toString().replace(/ \{[\s\S]*$/, ''); 64 | } else if (isUndefined(obj)) { 65 | return 'undefined'; 66 | } else if (typeof obj !== 'string') { 67 | return serializeObject(obj, maxDepth); 68 | } 69 | return obj; 70 | } 71 | 72 | /* exported 73 | minErrConfig, 74 | errorHandlingConfig, 75 | isValidObjectMaxDepth 76 | */ 77 | 78 | var minErrConfig = { 79 | objectMaxDepth: 5, 80 | urlErrorParamsEnabled: true 81 | }; 82 | 83 | /** 84 | * @ngdoc function 85 | * @name angular.errorHandlingConfig 86 | * @module ng 87 | * @kind function 88 | * 89 | * @description 90 | * Configure several aspects of error handling in AngularJS if used as a setter or return the 91 | * current configuration if used as a getter. The following options are supported: 92 | * 93 | * - **objectMaxDepth**: The maximum depth to which objects are traversed when stringified for error messages. 94 | * 95 | * Omitted or undefined options will leave the corresponding configuration values unchanged. 96 | * 97 | * @param {Object=} config - The configuration object. May only contain the options that need to be 98 | * updated. Supported keys: 99 | * 100 | * * `objectMaxDepth` **{Number}** - The max depth for stringifying objects. Setting to a 101 | * non-positive or non-numeric value, removes the max depth limit. 102 | * Default: 5 103 | * 104 | * * `urlErrorParamsEnabled` **{Boolean}** - Specifies whether the generated error url will 105 | * contain the parameters of the thrown error. Disabling the parameters can be useful if the 106 | * generated error url is very long. 107 | * 108 | * Default: true. When used without argument, it returns the current value. 109 | */ 110 | function errorHandlingConfig(config) { 111 | if (isObject(config)) { 112 | if (isDefined(config.objectMaxDepth)) { 113 | minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN; 114 | } 115 | if (isDefined(config.urlErrorParamsEnabled) && isBoolean(config.urlErrorParamsEnabled)) { 116 | minErrConfig.urlErrorParamsEnabled = config.urlErrorParamsEnabled; 117 | } 118 | } else { 119 | return minErrConfig; 120 | } 121 | } 122 | 123 | /** 124 | * @private 125 | * @param {Number} maxDepth 126 | * @return {boolean} 127 | */ 128 | function isValidObjectMaxDepth(maxDepth) { 129 | return isNumber(maxDepth) && maxDepth > 0; 130 | } 131 | 132 | 133 | /** 134 | * @description 135 | * 136 | * This object provides a utility for producing rich Error messages within 137 | * AngularJS. It can be called as follows: 138 | * 139 | * var exampleMinErr = minErr('example'); 140 | * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); 141 | * 142 | * The above creates an instance of minErr in the example namespace. The 143 | * resulting error will have a namespaced error code of example.one. The 144 | * resulting error will replace {0} with the value of foo, and {1} with the 145 | * value of bar. The object is not restricted in the number of arguments it can 146 | * take. 147 | * 148 | * If fewer arguments are specified than necessary for interpolation, the extra 149 | * interpolation markers will be preserved in the final string. 150 | * 151 | * Since data will be parsed statically during a build step, some restrictions 152 | * are applied with respect to how minErr instances are created and called. 153 | * Instances should have names of the form namespaceMinErr for a minErr created 154 | * using minErr('namespace'). Error codes, namespaces and template strings 155 | * should all be static strings, not variables or general expressions. 156 | * 157 | * @param {string} module The namespace to use for the new minErr instance. 158 | * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning 159 | * error from returned function, for cases when a particular type of error is useful. 160 | * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance 161 | */ 162 | 163 | function minErr(module, ErrorConstructor) { 164 | ErrorConstructor = ErrorConstructor || Error; 165 | 166 | var url = 'https://errors.angularjs.org/1.8.0/'; 167 | var regex = url.replace('.', '\\.') + '[\\s\\S]*'; 168 | var errRegExp = new RegExp(regex, 'g'); 169 | 170 | return function() { 171 | var code = arguments[0], 172 | template = arguments[1], 173 | message = '[' + (module ? module + ':' : '') + code + '] ', 174 | templateArgs = sliceArgs(arguments, 2).map(function(arg) { 175 | return toDebugString(arg, minErrConfig.objectMaxDepth); 176 | }), 177 | paramPrefix, i; 178 | 179 | // A minErr message has two parts: the message itself and the url that contains the 180 | // encoded message. 181 | // The message's parameters can contain other error messages which also include error urls. 182 | // To prevent the messages from getting too long, we strip the error urls from the parameters. 183 | 184 | message += template.replace(/\{\d+\}/g, function(match) { 185 | var index = +match.slice(1, -1); 186 | 187 | if (index < templateArgs.length) { 188 | return templateArgs[index].replace(errRegExp, ''); 189 | } 190 | 191 | return match; 192 | }); 193 | 194 | message += '\n' + url + (module ? module + '/' : '') + code; 195 | 196 | if (minErrConfig.urlErrorParamsEnabled) { 197 | for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { 198 | message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]); 199 | } 200 | } 201 | 202 | return new ErrorConstructor(message); 203 | }; 204 | } 205 | 206 | /** 207 | * @ngdoc type 208 | * @name angular.Module 209 | * @module ng 210 | * @description 211 | * 212 | * Interface for configuring AngularJS {@link angular.module modules}. 213 | */ 214 | 215 | function setupModuleLoader(window) { 216 | 217 | var $injectorMinErr = minErr('$injector'); 218 | var ngMinErr = minErr('ng'); 219 | 220 | function ensure(obj, name, factory) { 221 | return obj[name] || (obj[name] = factory()); 222 | } 223 | 224 | var angular = ensure(window, 'angular', Object); 225 | 226 | // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 227 | angular.$$minErr = angular.$$minErr || minErr; 228 | 229 | return ensure(angular, 'module', function() { 230 | /** @type {Object.} */ 231 | var modules = {}; 232 | 233 | /** 234 | * @ngdoc function 235 | * @name angular.module 236 | * @module ng 237 | * @description 238 | * 239 | * The `angular.module` is a global place for creating, registering and retrieving AngularJS 240 | * modules. 241 | * All modules (AngularJS core or 3rd party) that should be available to an application must be 242 | * registered using this mechanism. 243 | * 244 | * Passing one argument retrieves an existing {@link angular.Module}, 245 | * whereas passing more than one argument creates a new {@link angular.Module} 246 | * 247 | * 248 | * # Module 249 | * 250 | * A module is a collection of services, directives, controllers, filters, and configuration information. 251 | * `angular.module` is used to configure the {@link auto.$injector $injector}. 252 | * 253 | * ```js 254 | * // Create a new module 255 | * var myModule = angular.module('myModule', []); 256 | * 257 | * // register a new service 258 | * myModule.value('appName', 'MyCoolApp'); 259 | * 260 | * // configure existing services inside initialization blocks. 261 | * myModule.config(['$locationProvider', function($locationProvider) { 262 | * // Configure existing providers 263 | * $locationProvider.hashPrefix('!'); 264 | * }]); 265 | * ``` 266 | * 267 | * Then you can create an injector and load your modules like this: 268 | * 269 | * ```js 270 | * var injector = angular.injector(['ng', 'myModule']) 271 | * ``` 272 | * 273 | * However it's more likely that you'll just use 274 | * {@link ng.directive:ngApp ngApp} or 275 | * {@link angular.bootstrap} to simplify this process for you. 276 | * 277 | * @param {!string} name The name of the module to create or retrieve. 278 | * @param {!Array.=} requires If specified then new module is being created. If 279 | * unspecified then the module is being retrieved for further configuration. 280 | * @param {Function=} configFn Optional configuration function for the module. Same as 281 | * {@link angular.Module#config Module#config()}. 282 | * @returns {angular.Module} new module with the {@link angular.Module} api. 283 | */ 284 | return function module(name, requires, configFn) { 285 | 286 | var info = {}; 287 | 288 | var assertNotHasOwnProperty = function(name, context) { 289 | if (name === 'hasOwnProperty') { 290 | throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 291 | } 292 | }; 293 | 294 | assertNotHasOwnProperty(name, 'module'); 295 | if (requires && modules.hasOwnProperty(name)) { 296 | modules[name] = null; 297 | } 298 | return ensure(modules, name, function() { 299 | if (!requires) { 300 | throw $injectorMinErr('nomod', 'Module \'{0}\' is not available! You either misspelled ' + 301 | 'the module name or forgot to load it. If registering a module ensure that you ' + 302 | 'specify the dependencies as the second argument.', name); 303 | } 304 | 305 | /** @type {!Array.>} */ 306 | var invokeQueue = []; 307 | 308 | /** @type {!Array.} */ 309 | var configBlocks = []; 310 | 311 | /** @type {!Array.} */ 312 | var runBlocks = []; 313 | 314 | var config = invokeLater('$injector', 'invoke', 'push', configBlocks); 315 | 316 | /** @type {angular.Module} */ 317 | var moduleInstance = { 318 | // Private state 319 | _invokeQueue: invokeQueue, 320 | _configBlocks: configBlocks, 321 | _runBlocks: runBlocks, 322 | 323 | /** 324 | * @ngdoc method 325 | * @name angular.Module#info 326 | * @module ng 327 | * 328 | * @param {Object=} info Information about the module 329 | * @returns {Object|Module} The current info object for this module if called as a getter, 330 | * or `this` if called as a setter. 331 | * 332 | * @description 333 | * Read and write custom information about this module. 334 | * For example you could put the version of the module in here. 335 | * 336 | * ```js 337 | * angular.module('myModule', []).info({ version: '1.0.0' }); 338 | * ``` 339 | * 340 | * The version could then be read back out by accessing the module elsewhere: 341 | * 342 | * ``` 343 | * var version = angular.module('myModule').info().version; 344 | * ``` 345 | * 346 | * You can also retrieve this information during runtime via the 347 | * {@link $injector#modules `$injector.modules`} property: 348 | * 349 | * ```js 350 | * var version = $injector.modules['myModule'].info().version; 351 | * ``` 352 | */ 353 | info: function(value) { 354 | if (isDefined(value)) { 355 | if (!isObject(value)) throw ngMinErr('aobj', 'Argument \'{0}\' must be an object', 'value'); 356 | info = value; 357 | return this; 358 | } 359 | return info; 360 | }, 361 | 362 | /** 363 | * @ngdoc property 364 | * @name angular.Module#requires 365 | * @module ng 366 | * 367 | * @description 368 | * Holds the list of modules which the injector will load before the current module is 369 | * loaded. 370 | */ 371 | requires: requires, 372 | 373 | /** 374 | * @ngdoc property 375 | * @name angular.Module#name 376 | * @module ng 377 | * 378 | * @description 379 | * Name of the module. 380 | */ 381 | name: name, 382 | 383 | 384 | /** 385 | * @ngdoc method 386 | * @name angular.Module#provider 387 | * @module ng 388 | * @param {string} name service name 389 | * @param {Function} providerType Construction function for creating new instance of the 390 | * service. 391 | * @description 392 | * See {@link auto.$provide#provider $provide.provider()}. 393 | */ 394 | provider: invokeLaterAndSetModuleName('$provide', 'provider'), 395 | 396 | /** 397 | * @ngdoc method 398 | * @name angular.Module#factory 399 | * @module ng 400 | * @param {string} name service name 401 | * @param {Function} providerFunction Function for creating new instance of the service. 402 | * @description 403 | * See {@link auto.$provide#factory $provide.factory()}. 404 | */ 405 | factory: invokeLaterAndSetModuleName('$provide', 'factory'), 406 | 407 | /** 408 | * @ngdoc method 409 | * @name angular.Module#service 410 | * @module ng 411 | * @param {string} name service name 412 | * @param {Function} constructor A constructor function that will be instantiated. 413 | * @description 414 | * See {@link auto.$provide#service $provide.service()}. 415 | */ 416 | service: invokeLaterAndSetModuleName('$provide', 'service'), 417 | 418 | /** 419 | * @ngdoc method 420 | * @name angular.Module#value 421 | * @module ng 422 | * @param {string} name service name 423 | * @param {*} object Service instance object. 424 | * @description 425 | * See {@link auto.$provide#value $provide.value()}. 426 | */ 427 | value: invokeLater('$provide', 'value'), 428 | 429 | /** 430 | * @ngdoc method 431 | * @name angular.Module#constant 432 | * @module ng 433 | * @param {string} name constant name 434 | * @param {*} object Constant value. 435 | * @description 436 | * Because the constants are fixed, they get applied before other provide methods. 437 | * See {@link auto.$provide#constant $provide.constant()}. 438 | */ 439 | constant: invokeLater('$provide', 'constant', 'unshift'), 440 | 441 | /** 442 | * @ngdoc method 443 | * @name angular.Module#decorator 444 | * @module ng 445 | * @param {string} name The name of the service to decorate. 446 | * @param {Function} decorFn This function will be invoked when the service needs to be 447 | * instantiated and should return the decorated service instance. 448 | * @description 449 | * See {@link auto.$provide#decorator $provide.decorator()}. 450 | */ 451 | decorator: invokeLaterAndSetModuleName('$provide', 'decorator', configBlocks), 452 | 453 | /** 454 | * @ngdoc method 455 | * @name angular.Module#animation 456 | * @module ng 457 | * @param {string} name animation name 458 | * @param {Function} animationFactory Factory function for creating new instance of an 459 | * animation. 460 | * @description 461 | * 462 | * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 463 | * 464 | * 465 | * Defines an animation hook that can be later used with 466 | * {@link $animate $animate} service and directives that use this service. 467 | * 468 | * ```js 469 | * module.animation('.animation-name', function($inject1, $inject2) { 470 | * return { 471 | * eventName : function(element, done) { 472 | * //code to run the animation 473 | * //once complete, then run done() 474 | * return function cancellationFunction(element) { 475 | * //code to cancel the animation 476 | * } 477 | * } 478 | * } 479 | * }) 480 | * ``` 481 | * 482 | * See {@link ng.$animateProvider#register $animateProvider.register()} and 483 | * {@link ngAnimate ngAnimate module} for more information. 484 | */ 485 | animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), 486 | 487 | /** 488 | * @ngdoc method 489 | * @name angular.Module#filter 490 | * @module ng 491 | * @param {string} name Filter name - this must be a valid AngularJS expression identifier 492 | * @param {Function} filterFactory Factory function for creating new instance of filter. 493 | * @description 494 | * See {@link ng.$filterProvider#register $filterProvider.register()}. 495 | * 496 | *
497 | * **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`. 498 | * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 499 | * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 500 | * (`myapp_subsection_filterx`). 501 | *
502 | */ 503 | filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), 504 | 505 | /** 506 | * @ngdoc method 507 | * @name angular.Module#controller 508 | * @module ng 509 | * @param {string|Object} name Controller name, or an object map of controllers where the 510 | * keys are the names and the values are the constructors. 511 | * @param {Function} constructor Controller constructor function. 512 | * @description 513 | * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 514 | */ 515 | controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), 516 | 517 | /** 518 | * @ngdoc method 519 | * @name angular.Module#directive 520 | * @module ng 521 | * @param {string|Object} name Directive name, or an object map of directives where the 522 | * keys are the names and the values are the factories. 523 | * @param {Function} directiveFactory Factory function for creating new instance of 524 | * directives. 525 | * @description 526 | * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 527 | */ 528 | directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), 529 | 530 | /** 531 | * @ngdoc method 532 | * @name angular.Module#component 533 | * @module ng 534 | * @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match ``), 535 | * or an object map of components where the keys are the names and the values are the component definition objects. 536 | * @param {Object} options Component definition object (a simplified 537 | * {@link ng.$compile#directive-definition-object directive definition object}) 538 | * 539 | * @description 540 | * See {@link ng.$compileProvider#component $compileProvider.component()}. 541 | */ 542 | component: invokeLaterAndSetModuleName('$compileProvider', 'component'), 543 | 544 | /** 545 | * @ngdoc method 546 | * @name angular.Module#config 547 | * @module ng 548 | * @param {Function} configFn Execute this function on module load. Useful for service 549 | * configuration. 550 | * @description 551 | * Use this method to configure services by injecting their 552 | * {@link angular.Module#provider `providers`}, e.g. for adding routes to the 553 | * {@link ngRoute.$routeProvider $routeProvider}. 554 | * 555 | * Note that you can only inject {@link angular.Module#provider `providers`} and 556 | * {@link angular.Module#constant `constants`} into this function. 557 | * 558 | * For more about how to configure services, see 559 | * {@link providers#provider-recipe Provider Recipe}. 560 | */ 561 | config: config, 562 | 563 | /** 564 | * @ngdoc method 565 | * @name angular.Module#run 566 | * @module ng 567 | * @param {Function} initializationFn Execute this function after injector creation. 568 | * Useful for application initialization. 569 | * @description 570 | * Use this method to register work which should be performed when the injector is done 571 | * loading all modules. 572 | */ 573 | run: function(block) { 574 | runBlocks.push(block); 575 | return this; 576 | } 577 | }; 578 | 579 | if (configFn) { 580 | config(configFn); 581 | } 582 | 583 | return moduleInstance; 584 | 585 | /** 586 | * @param {string} provider 587 | * @param {string} method 588 | * @param {String=} insertMethod 589 | * @returns {angular.Module} 590 | */ 591 | function invokeLater(provider, method, insertMethod, queue) { 592 | if (!queue) queue = invokeQueue; 593 | return function() { 594 | queue[insertMethod || 'push']([provider, method, arguments]); 595 | return moduleInstance; 596 | }; 597 | } 598 | 599 | /** 600 | * @param {string} provider 601 | * @param {string} method 602 | * @returns {angular.Module} 603 | */ 604 | function invokeLaterAndSetModuleName(provider, method, queue) { 605 | if (!queue) queue = invokeQueue; 606 | return function(recipeName, factoryFunction) { 607 | if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; 608 | queue.push([provider, method, arguments]); 609 | return moduleInstance; 610 | }; 611 | } 612 | }); 613 | }; 614 | }); 615 | 616 | } 617 | 618 | setupModuleLoader(window); 619 | })(window); 620 | 621 | /** 622 | * Closure compiler type information 623 | * 624 | * @typedef { { 625 | * requires: !Array., 626 | * invokeQueue: !Array.>, 627 | * 628 | * service: function(string, Function):angular.Module, 629 | * factory: function(string, Function):angular.Module, 630 | * value: function(string, *):angular.Module, 631 | * 632 | * filter: function(string, Function):angular.Module, 633 | * 634 | * init: function(Function):angular.Module 635 | * } } 636 | */ 637 | angular.Module; 638 | 639 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/angular-messages.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.8.0 3 | * (c) 2010-2020 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular) {'use strict'; 7 | 8 | var forEach; 9 | var isArray; 10 | var isString; 11 | var jqLite; 12 | 13 | /** 14 | * @ngdoc module 15 | * @name ngMessages 16 | * @description 17 | * 18 | * The `ngMessages` module provides enhanced support for displaying messages within templates 19 | * (typically within forms or when rendering message objects that return key/value data). 20 | * Instead of relying on JavaScript code and/or complex ng-if statements within your form template to 21 | * show and hide error messages specific to the state of an input field, the `ngMessages` and 22 | * `ngMessage` directives are designed to handle the complexity, inheritance and priority 23 | * sequencing based on the order of how the messages are defined in the template. 24 | * 25 | * Currently, the ngMessages module only contains the code for the `ngMessages`, `ngMessagesInclude` 26 | * `ngMessage`, `ngMessageExp` and `ngMessageDefault` directives. 27 | * 28 | * ## Usage 29 | * The `ngMessages` directive allows keys in a key/value collection to be associated with a child element 30 | * (or 'message') that will show or hide based on the truthiness of that key's value in the collection. A common use 31 | * case for `ngMessages` is to display error messages for inputs using the `$error` object exposed by the 32 | * {@link ngModel ngModel} directive. 33 | * 34 | * The child elements of the `ngMessages` directive are matched to the collection keys by a `ngMessage` or 35 | * `ngMessageExp` directive. The value of these attributes must match a key in the collection that is provided by 36 | * the `ngMessages` directive. 37 | * 38 | * Consider the following example, which illustrates a typical use case of `ngMessages`. Within the form `myForm` we 39 | * have a text input named `myField` which is bound to the scope variable `field` using the {@link ngModel ngModel} 40 | * directive. 41 | * 42 | * The `myField` field is a required input of type `email` with a maximum length of 15 characters. 43 | * 44 | * ```html 45 | *
46 | * 50 | *
51 | *
Please enter a value for this field.
52 | *
This field must be a valid email address.
53 | *
This field can be at most 15 characters long.
54 | *
55 | *
56 | * ``` 57 | * 58 | * In order to show error messages corresponding to `myField` we first create an element with an `ngMessages` attribute 59 | * set to the `$error` object owned by the `myField` input in our `myForm` form. 60 | * 61 | * Within this element we then create separate elements for each of the possible errors that `myField` could have. 62 | * The `ngMessage` attribute is used to declare which element(s) will appear for which error - for example, 63 | * setting `ng-message="required"` specifies that this particular element should be displayed when there 64 | * is no value present for the required field `myField` (because the key `required` will be `true` in the object 65 | * `myForm.myField.$error`). 66 | * 67 | * ### Message order 68 | * 69 | * By default, `ngMessages` will only display one message for a particular key/value collection at any time. If more 70 | * than one message (or error) key is currently true, then which message is shown is determined by the order of messages 71 | * in the HTML template code (messages declared first are prioritised). This mechanism means the developer does not have 72 | * to prioritize messages using custom JavaScript code. 73 | * 74 | * Given the following error object for our example (which informs us that the field `myField` currently has both the 75 | * `required` and `email` errors): 76 | * 77 | * ```javascript 78 | * 79 | * myField.$error = { required : true, email: true, maxlength: false }; 80 | * ``` 81 | * The `required` message will be displayed to the user since it appears before the `email` message in the DOM. 82 | * Once the user types a single character, the `required` message will disappear (since the field now has a value) 83 | * but the `email` message will be visible because it is still applicable. 84 | * 85 | * ### Displaying multiple messages at the same time 86 | * 87 | * While `ngMessages` will by default only display one error element at a time, the `ng-messages-multiple` attribute can 88 | * be applied to the `ngMessages` container element to cause it to display all applicable error messages at once: 89 | * 90 | * ```html 91 | * 92 | *
...
93 | * 94 | * 95 | * ... 96 | * ``` 97 | * 98 | * ## Reusing and Overriding Messages 99 | * In addition to prioritization, ngMessages also allows for including messages from a remote or an inline 100 | * template. This allows for generic collection of messages to be reused across multiple parts of an 101 | * application. 102 | * 103 | * ```html 104 | * 108 | * 109 | *
110 | *
111 | *
112 | * ``` 113 | * 114 | * However, including generic messages may not be useful enough to match all input fields, therefore, 115 | * `ngMessages` provides the ability to override messages defined in the remote template by redefining 116 | * them within the directive container. 117 | * 118 | * ```html 119 | * 120 | * 124 | * 125 | *
126 | * 135 | * 137 | *
138 | * 139 | *
You did not enter your email address
140 | * 141 | * 142 | *
Your email address is invalid
143 | * 144 | * 145 | *
146 | *
147 | *
148 | * ``` 149 | * 150 | * In the example HTML code above the message that is set on required will override the corresponding 151 | * required message defined within the remote template. Therefore, with particular input fields (such 152 | * email addresses, date fields, autocomplete inputs, etc...), specialized error messages can be applied 153 | * while more generic messages can be used to handle other, more general input errors. 154 | * 155 | * ## Dynamic Messaging 156 | * ngMessages also supports using expressions to dynamically change key values. Using arrays and 157 | * repeaters to list messages is also supported. This means that the code below will be able to 158 | * fully adapt itself and display the appropriate message when any of the expression data changes: 159 | * 160 | * ```html 161 | *
162 | * 170 | *
171 | *
You did not enter your email address
172 | *
173 | * 174 | *
{{ errorMessage.text }}
175 | *
176 | *
177 | *
178 | * ``` 179 | * 180 | * The `errorMessage.type` expression can be a string value or it can be an array so 181 | * that multiple errors can be associated with a single error message: 182 | * 183 | * ```html 184 | * 193 | *
194 | *
You did not enter your email address
195 | *
196 | * Your email must be between 5 and 100 characters long 197 | *
198 | *
199 | * ``` 200 | * 201 | * Feel free to use other structural directives such as ng-if and ng-switch to further control 202 | * what messages are active and when. Be careful, if you place ng-message on the same element 203 | * as these structural directives, AngularJS may not be able to determine if a message is active 204 | * or not. Therefore it is best to place the ng-message on a child element of the structural 205 | * directive. 206 | * 207 | * ```html 208 | *
209 | *
210 | *
Please enter something
211 | *
212 | *
213 | * ``` 214 | * 215 | * ## Animations 216 | * If the `ngAnimate` module is active within the application then the `ngMessages`, `ngMessage` and 217 | * `ngMessageExp` directives will trigger animations whenever any messages are added and removed from 218 | * the DOM by the `ngMessages` directive. 219 | * 220 | * Whenever the `ngMessages` directive contains one or more visible messages then the `.ng-active` CSS 221 | * class will be added to the element. The `.ng-inactive` CSS class will be applied when there are no 222 | * messages present. Therefore, CSS transitions and keyframes as well as JavaScript animations can 223 | * hook into the animations whenever these classes are added/removed. 224 | * 225 | * Let's say that our HTML code for our messages container looks like so: 226 | * 227 | * ```html 228 | * 232 | * ``` 233 | * 234 | * Then the CSS animation code for the message container looks like so: 235 | * 236 | * ```css 237 | * .my-messages { 238 | * transition:1s linear all; 239 | * } 240 | * .my-messages.ng-active { 241 | * // messages are visible 242 | * } 243 | * .my-messages.ng-inactive { 244 | * // messages are hidden 245 | * } 246 | * ``` 247 | * 248 | * Whenever an inner message is attached (becomes visible) or removed (becomes hidden) then the enter 249 | * and leave animation is triggered for each particular element bound to the `ngMessage` directive. 250 | * 251 | * Therefore, the CSS code for the inner messages looks like so: 252 | * 253 | * ```css 254 | * .some-message { 255 | * transition:1s linear all; 256 | * } 257 | * 258 | * .some-message.ng-enter {} 259 | * .some-message.ng-enter.ng-enter-active {} 260 | * 261 | * .some-message.ng-leave {} 262 | * .some-message.ng-leave.ng-leave-active {} 263 | * ``` 264 | * 265 | * {@link ngAnimate See the ngAnimate docs} to learn how to use JavaScript animations or to learn 266 | * more about ngAnimate. 267 | * 268 | * ## Displaying a default message 269 | * If the ngMessages renders no inner ngMessage directive (i.e. when none of the truthy 270 | * keys are matched by a defined message), then it will render a default message 271 | * using the {@link ngMessageDefault} directive. 272 | * Note that matched messages will always take precedence over unmatched messages. That means 273 | * the default message will not be displayed when another message is matched. This is also 274 | * true for `ng-messages-multiple`. 275 | * 276 | * ```html 277 | *
278 | *
This field is required
279 | *
This field is too short
280 | *
This field has an input error
281 | *
282 | * ``` 283 | * 284 | 285 | */ 286 | angular.module('ngMessages', [], function initAngularHelpers() { 287 | // Access helpers from AngularJS core. 288 | // Do it inside a `config` block to ensure `window.angular` is available. 289 | forEach = angular.forEach; 290 | isArray = angular.isArray; 291 | isString = angular.isString; 292 | jqLite = angular.element; 293 | }) 294 | .info({ angularVersion: '1.8.0' }) 295 | 296 | /** 297 | * @ngdoc directive 298 | * @module ngMessages 299 | * @name ngMessages 300 | * @restrict AE 301 | * 302 | * @description 303 | * `ngMessages` is a directive that is designed to show and hide messages based on the state 304 | * of a key/value object that it listens on. The directive itself complements error message 305 | * reporting with the `ngModel` $error object (which stores a key/value state of validation errors). 306 | * 307 | * `ngMessages` manages the state of internal messages within its container element. The internal 308 | * messages use the `ngMessage` directive and will be inserted/removed from the page depending 309 | * on if they're present within the key/value object. By default, only one message will be displayed 310 | * at a time and this depends on the prioritization of the messages within the template. (This can 311 | * be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.) 312 | * 313 | * A remote template can also be used (With {@link ngMessagesInclude}) to promote message 314 | * reusability and messages can also be overridden. 315 | * 316 | * A default message can also be displayed when no `ngMessage` directive is inserted, using the 317 | * {@link ngMessageDefault} directive. 318 | * 319 | * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`. 320 | * 321 | * @usage 322 | * ```html 323 | * 324 | * 325 | * ... 326 | * ... 327 | * ... 328 | * ... 329 | * 330 | * 331 | * 332 | * 333 | * ... 334 | * ... 335 | * ... 336 | * ... 337 | * 338 | * ``` 339 | * 340 | * @param {string} ngMessages an AngularJS expression evaluating to a key/value object 341 | * (this is typically the $error object on an ngModel instance). 342 | * @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true 343 | * 344 | * @example 345 | * 348 | * 349 | *
350 | * 359 | *
myForm.myName.$error = {{ myForm.myName.$error | json }}
360 | * 361 | *
362 | *
You did not enter a field
363 | *
Your field is too short
364 | *
Your field is too long
365 | *
This field has an input error
366 | *
367 | *
368 | *
369 | * 370 | * angular.module('ngMessagesExample', ['ngMessages']); 371 | * 372 | *
373 | */ 374 | .directive('ngMessages', ['$animate', function($animate) { 375 | var ACTIVE_CLASS = 'ng-active'; 376 | var INACTIVE_CLASS = 'ng-inactive'; 377 | 378 | return { 379 | require: 'ngMessages', 380 | restrict: 'AE', 381 | controller: ['$element', '$scope', '$attrs', function NgMessagesCtrl($element, $scope, $attrs) { 382 | var ctrl = this; 383 | var latestKey = 0; 384 | var nextAttachId = 0; 385 | 386 | this.getAttachId = function getAttachId() { return nextAttachId++; }; 387 | 388 | var messages = this.messages = {}; 389 | var renderLater, cachedCollection; 390 | 391 | this.render = function(collection) { 392 | collection = collection || {}; 393 | 394 | renderLater = false; 395 | cachedCollection = collection; 396 | 397 | // this is true if the attribute is empty or if the attribute value is truthy 398 | var multiple = isAttrTruthy($scope, $attrs.ngMessagesMultiple) || 399 | isAttrTruthy($scope, $attrs.multiple); 400 | 401 | var unmatchedMessages = []; 402 | var matchedKeys = {}; 403 | var truthyKeys = 0; 404 | var messageItem = ctrl.head; 405 | var messageFound = false; 406 | var totalMessages = 0; 407 | 408 | // we use != instead of !== to allow for both undefined and null values 409 | while (messageItem != null) { 410 | totalMessages++; 411 | var messageCtrl = messageItem.message; 412 | 413 | var messageUsed = false; 414 | if (!messageFound) { 415 | forEach(collection, function(value, key) { 416 | if (truthy(value) && !messageUsed) { 417 | truthyKeys++; 418 | 419 | if (messageCtrl.test(key)) { 420 | // this is to prevent the same error name from showing up twice 421 | if (matchedKeys[key]) return; 422 | matchedKeys[key] = true; 423 | 424 | messageUsed = true; 425 | messageCtrl.attach(); 426 | } 427 | } 428 | }); 429 | } 430 | 431 | if (messageUsed) { 432 | // unless we want to display multiple messages then we should 433 | // set a flag here to avoid displaying the next message in the list 434 | messageFound = !multiple; 435 | } else { 436 | unmatchedMessages.push(messageCtrl); 437 | } 438 | 439 | messageItem = messageItem.next; 440 | } 441 | 442 | forEach(unmatchedMessages, function(messageCtrl) { 443 | messageCtrl.detach(); 444 | }); 445 | 446 | var messageMatched = unmatchedMessages.length !== totalMessages; 447 | var attachDefault = ctrl.default && !messageMatched && truthyKeys > 0; 448 | 449 | if (attachDefault) { 450 | ctrl.default.attach(); 451 | } else if (ctrl.default) { 452 | ctrl.default.detach(); 453 | } 454 | 455 | if (messageMatched || attachDefault) { 456 | $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS); 457 | } else { 458 | $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS); 459 | } 460 | }; 461 | 462 | $scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render); 463 | 464 | this.reRender = function() { 465 | if (!renderLater) { 466 | renderLater = true; 467 | $scope.$evalAsync(function() { 468 | if (renderLater && cachedCollection) { 469 | ctrl.render(cachedCollection); 470 | } 471 | }); 472 | } 473 | }; 474 | 475 | this.register = function(comment, messageCtrl, isDefault) { 476 | if (isDefault) { 477 | ctrl.default = messageCtrl; 478 | } else { 479 | var nextKey = latestKey.toString(); 480 | messages[nextKey] = { 481 | message: messageCtrl 482 | }; 483 | insertMessageNode($element[0], comment, nextKey); 484 | comment.$$ngMessageNode = nextKey; 485 | latestKey++; 486 | } 487 | 488 | ctrl.reRender(); 489 | }; 490 | 491 | this.deregister = function(comment, isDefault) { 492 | if (isDefault) { 493 | delete ctrl.default; 494 | } else { 495 | var key = comment.$$ngMessageNode; 496 | delete comment.$$ngMessageNode; 497 | removeMessageNode($element[0], comment, key); 498 | delete messages[key]; 499 | } 500 | ctrl.reRender(); 501 | }; 502 | 503 | function findPreviousMessage(parent, comment) { 504 | var prevNode = comment; 505 | var parentLookup = []; 506 | 507 | while (prevNode && prevNode !== parent) { 508 | var prevKey = prevNode.$$ngMessageNode; 509 | if (prevKey && prevKey.length) { 510 | return messages[prevKey]; 511 | } 512 | 513 | // dive deeper into the DOM and examine its children for any ngMessage 514 | // comments that may be in an element that appears deeper in the list 515 | if (prevNode.childNodes.length && parentLookup.indexOf(prevNode) === -1) { 516 | parentLookup.push(prevNode); 517 | prevNode = prevNode.childNodes[prevNode.childNodes.length - 1]; 518 | } else if (prevNode.previousSibling) { 519 | prevNode = prevNode.previousSibling; 520 | } else { 521 | prevNode = prevNode.parentNode; 522 | parentLookup.push(prevNode); 523 | } 524 | } 525 | } 526 | 527 | function insertMessageNode(parent, comment, key) { 528 | var messageNode = messages[key]; 529 | if (!ctrl.head) { 530 | ctrl.head = messageNode; 531 | } else { 532 | var match = findPreviousMessage(parent, comment); 533 | if (match) { 534 | messageNode.next = match.next; 535 | match.next = messageNode; 536 | } else { 537 | messageNode.next = ctrl.head; 538 | ctrl.head = messageNode; 539 | } 540 | } 541 | } 542 | 543 | function removeMessageNode(parent, comment, key) { 544 | var messageNode = messages[key]; 545 | 546 | // This message node may have already been removed by a call to deregister() 547 | if (!messageNode) return; 548 | 549 | var match = findPreviousMessage(parent, comment); 550 | if (match) { 551 | match.next = messageNode.next; 552 | } else { 553 | ctrl.head = messageNode.next; 554 | } 555 | } 556 | }] 557 | }; 558 | 559 | function isAttrTruthy(scope, attr) { 560 | return (isString(attr) && attr.length === 0) || //empty attribute 561 | truthy(scope.$eval(attr)); 562 | } 563 | 564 | function truthy(val) { 565 | return isString(val) ? val.length : !!val; 566 | } 567 | }]) 568 | 569 | /** 570 | * @ngdoc directive 571 | * @name ngMessagesInclude 572 | * @restrict AE 573 | * @scope 574 | * 575 | * @description 576 | * `ngMessagesInclude` is a directive with the purpose to import existing ngMessage template 577 | * code from a remote template and place the downloaded template code into the exact spot 578 | * that the ngMessagesInclude directive is placed within the ngMessages container. This allows 579 | * for a series of pre-defined messages to be reused and also allows for the developer to 580 | * determine what messages are overridden due to the placement of the ngMessagesInclude directive. 581 | * 582 | * @usage 583 | * ```html 584 | * 585 | * 586 | * ... 587 | * 588 | * 589 | * 590 | * 591 | * ... 592 | * 593 | * ``` 594 | * 595 | * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`. 596 | * 597 | * @param {string} ngMessagesInclude|src a string value corresponding to the remote template. 598 | */ 599 | .directive('ngMessagesInclude', 600 | ['$templateRequest', '$document', '$compile', function($templateRequest, $document, $compile) { 601 | 602 | return { 603 | restrict: 'AE', 604 | require: '^^ngMessages', // we only require this for validation sake 605 | link: function($scope, element, attrs) { 606 | var src = attrs.ngMessagesInclude || attrs.src; 607 | $templateRequest(src).then(function(html) { 608 | if ($scope.$$destroyed) return; 609 | 610 | if (isString(html) && !html.trim()) { 611 | // Empty template - nothing to compile 612 | replaceElementWithMarker(element, src); 613 | } else { 614 | // Non-empty template - compile and link 615 | $compile(html)($scope, function(contents) { 616 | element.after(contents); 617 | replaceElementWithMarker(element, src); 618 | }); 619 | } 620 | }); 621 | } 622 | }; 623 | 624 | // Helpers 625 | function replaceElementWithMarker(element, src) { 626 | // A comment marker is placed for debugging purposes 627 | var comment = $compile.$$createComment ? 628 | $compile.$$createComment('ngMessagesInclude', src) : 629 | $document[0].createComment(' ngMessagesInclude: ' + src + ' '); 630 | var marker = jqLite(comment); 631 | element.after(marker); 632 | 633 | // Don't pollute the DOM anymore by keeping an empty directive element 634 | element.remove(); 635 | } 636 | }]) 637 | 638 | /** 639 | * @ngdoc directive 640 | * @name ngMessage 641 | * @restrict AE 642 | * @scope 643 | * @priority 1 644 | * 645 | * @description 646 | * `ngMessage` is a directive with the purpose to show and hide a particular message. 647 | * For `ngMessage` to operate, a parent `ngMessages` directive on a parent DOM element 648 | * must be situated since it determines which messages are visible based on the state 649 | * of the provided key/value map that `ngMessages` listens on. 650 | * 651 | * More information about using `ngMessage` can be found in the 652 | * {@link module:ngMessages `ngMessages` module documentation}. 653 | * 654 | * @usage 655 | * ```html 656 | * 657 | * 658 | * ... 659 | * ... 660 | * 661 | * 662 | * 663 | * 664 | * ... 665 | * ... 666 | * 667 | * ``` 668 | * 669 | * @param {expression} ngMessage|when a string value corresponding to the message key. 670 | */ 671 | .directive('ngMessage', ngMessageDirectiveFactory()) 672 | 673 | 674 | /** 675 | * @ngdoc directive 676 | * @name ngMessageExp 677 | * @restrict AE 678 | * @priority 1 679 | * @scope 680 | * 681 | * @description 682 | * `ngMessageExp` is the same as {@link directive:ngMessage `ngMessage`}, but instead of a static 683 | * value, it accepts an expression to be evaluated for the message key. 684 | * 685 | * @usage 686 | * ```html 687 | * 688 | * 689 | * ... 690 | * 691 | * 692 | * 693 | * 694 | * ... 695 | * 696 | * ``` 697 | * 698 | * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`. 699 | * 700 | * @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key. 701 | */ 702 | .directive('ngMessageExp', ngMessageDirectiveFactory()) 703 | 704 | /** 705 | * @ngdoc directive 706 | * @name ngMessageDefault 707 | * @restrict AE 708 | * @scope 709 | * 710 | * @description 711 | * `ngMessageDefault` is a directive with the purpose to show and hide a default message for 712 | * {@link directive:ngMessages}, when none of provided messages matches. 713 | * 714 | * More information about using `ngMessageDefault` can be found in the 715 | * {@link module:ngMessages `ngMessages` module documentation}. 716 | * 717 | * @usage 718 | * ```html 719 | * 720 | * 721 | * ... 722 | * ... 723 | * ... 724 | * 725 | * 726 | * 727 | * 728 | * ... 729 | * ... 730 | * ... 731 | * 732 | * 733 | */ 734 | .directive('ngMessageDefault', ngMessageDirectiveFactory(true)); 735 | 736 | function ngMessageDirectiveFactory(isDefault) { 737 | return ['$animate', function($animate) { 738 | return { 739 | restrict: 'AE', 740 | transclude: 'element', 741 | priority: 1, // must run before ngBind, otherwise the text is set on the comment 742 | terminal: true, 743 | require: '^^ngMessages', 744 | link: function(scope, element, attrs, ngMessagesCtrl, $transclude) { 745 | var commentNode, records, staticExp, dynamicExp; 746 | 747 | if (!isDefault) { 748 | commentNode = element[0]; 749 | staticExp = attrs.ngMessage || attrs.when; 750 | dynamicExp = attrs.ngMessageExp || attrs.whenExp; 751 | 752 | var assignRecords = function(items) { 753 | records = items 754 | ? (isArray(items) 755 | ? items 756 | : items.split(/[\s,]+/)) 757 | : null; 758 | ngMessagesCtrl.reRender(); 759 | }; 760 | 761 | if (dynamicExp) { 762 | assignRecords(scope.$eval(dynamicExp)); 763 | scope.$watchCollection(dynamicExp, assignRecords); 764 | } else { 765 | assignRecords(staticExp); 766 | } 767 | } 768 | 769 | var currentElement, messageCtrl; 770 | ngMessagesCtrl.register(commentNode, messageCtrl = { 771 | test: function(name) { 772 | return contains(records, name); 773 | }, 774 | attach: function() { 775 | if (!currentElement) { 776 | $transclude(function(elm, newScope) { 777 | $animate.enter(elm, null, element); 778 | currentElement = elm; 779 | 780 | // Each time we attach this node to a message we get a new id that we can match 781 | // when we are destroying the node later. 782 | var $$attachId = currentElement.$$attachId = ngMessagesCtrl.getAttachId(); 783 | 784 | // in the event that the element or a parent element is destroyed 785 | // by another structural directive then it's time 786 | // to deregister the message from the controller 787 | currentElement.on('$destroy', function() { 788 | // If the message element was removed via a call to `detach` then `currentElement` will be null 789 | // So this handler only handles cases where something else removed the message element. 790 | if (currentElement && currentElement.$$attachId === $$attachId) { 791 | ngMessagesCtrl.deregister(commentNode, isDefault); 792 | messageCtrl.detach(); 793 | } 794 | newScope.$destroy(); 795 | }); 796 | }); 797 | } 798 | }, 799 | detach: function() { 800 | if (currentElement) { 801 | var elm = currentElement; 802 | currentElement = null; 803 | $animate.leave(elm); 804 | } 805 | } 806 | }, isDefault); 807 | 808 | // We need to ensure that this directive deregisters itself when it no longer exists 809 | // Normally this is done when the attached element is destroyed; but if this directive 810 | // gets removed before we attach the message to the DOM there is nothing to watch 811 | // in which case we must deregister when the containing scope is destroyed. 812 | scope.$on('$destroy', function() { 813 | ngMessagesCtrl.deregister(commentNode, isDefault); 814 | }); 815 | } 816 | }; 817 | }]; 818 | 819 | function contains(collection, key) { 820 | if (collection) { 821 | return isArray(collection) 822 | ? collection.indexOf(key) >= 0 823 | : collection.hasOwnProperty(key); 824 | } 825 | } 826 | } 827 | 828 | 829 | })(window, window.angular); 830 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/angular-resource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.8.0 3 | * (c) 2010-2020 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular) {'use strict'; 7 | 8 | var $resourceMinErr = angular.$$minErr('$resource'); 9 | 10 | // Helper functions and regex to lookup a dotted path on an object 11 | // stopping at undefined/null. The path must be composed of ASCII 12 | // identifiers (just like $parse) 13 | var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/; 14 | 15 | function isValidDottedPath(path) { 16 | return (path != null && path !== '' && path !== 'hasOwnProperty' && 17 | MEMBER_NAME_REGEX.test('.' + path)); 18 | } 19 | 20 | function lookupDottedPath(obj, path) { 21 | if (!isValidDottedPath(path)) { 22 | throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); 23 | } 24 | var keys = path.split('.'); 25 | for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) { 26 | var key = keys[i]; 27 | obj = (obj !== null) ? obj[key] : undefined; 28 | } 29 | return obj; 30 | } 31 | 32 | /** 33 | * Create a shallow copy of an object and clear other fields from the destination 34 | */ 35 | function shallowClearAndCopy(src, dst) { 36 | dst = dst || {}; 37 | 38 | angular.forEach(dst, function(value, key) { 39 | delete dst[key]; 40 | }); 41 | 42 | for (var key in src) { 43 | if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { 44 | dst[key] = src[key]; 45 | } 46 | } 47 | 48 | return dst; 49 | } 50 | 51 | /** 52 | * @ngdoc module 53 | * @name ngResource 54 | * @description 55 | * 56 | * The `ngResource` module provides interaction support with RESTful services 57 | * via the $resource service. 58 | * 59 | * See {@link ngResource.$resourceProvider} and {@link ngResource.$resource} for usage. 60 | */ 61 | 62 | /** 63 | * @ngdoc provider 64 | * @name $resourceProvider 65 | * 66 | * @description 67 | * 68 | * Use `$resourceProvider` to change the default behavior of the {@link ngResource.$resource} 69 | * service. 70 | * 71 | * ## Dependencies 72 | * Requires the {@link ngResource } module to be installed. 73 | * 74 | */ 75 | 76 | /** 77 | * @ngdoc service 78 | * @name $resource 79 | * @requires $http 80 | * @requires ng.$log 81 | * @requires $q 82 | * @requires ng.$timeout 83 | * 84 | * @description 85 | * A factory which creates a resource object that lets you interact with 86 | * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. 87 | * 88 | * The returned resource object has action methods which provide high-level behaviors without 89 | * the need to interact with the low level {@link ng.$http $http} service. 90 | * 91 | * Requires the {@link ngResource `ngResource`} module to be installed. 92 | * 93 | * By default, trailing slashes will be stripped from the calculated URLs, 94 | * which can pose problems with server backends that do not expect that 95 | * behavior. This can be disabled by configuring the `$resourceProvider` like 96 | * this: 97 | * 98 | * ```js 99 | app.config(['$resourceProvider', function($resourceProvider) { 100 | // Don't strip trailing slashes from calculated URLs 101 | $resourceProvider.defaults.stripTrailingSlashes = false; 102 | }]); 103 | * ``` 104 | * 105 | * @param {string} url A parameterized URL template with parameters prefixed by `:` as in 106 | * `/user/:username`. If you are using a URL with a port number (e.g. 107 | * `http://example.com:8080/api`), it will be respected. 108 | * 109 | * If you are using a url with a suffix, just add the suffix, like this: 110 | * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` 111 | * or even `$resource('http://example.com/resource/:resource_id.:format')` 112 | * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be 113 | * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you 114 | * can escape it with `/\.`. 115 | * 116 | * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in 117 | * `actions` methods. If a parameter value is a function, it will be called every time 118 | * a param value needs to be obtained for a request (unless the param was overridden). The 119 | * function will be passed the current data value as an argument. 120 | * 121 | * Each key value in the parameter object is first bound to url template if present and then any 122 | * excess keys are appended to the url search query after the `?`. 123 | * 124 | * Given a template `/path/:verb` and parameter `{verb: 'greet', salutation: 'Hello'}` results in 125 | * URL `/path/greet?salutation=Hello`. 126 | * 127 | * If the parameter value is prefixed with `@`, then the value for that parameter will be 128 | * extracted from the corresponding property on the `data` object (provided when calling actions 129 | * with a request body). 130 | * For example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of 131 | * `someParam` will be `data.someProp`. 132 | * Note that the parameter will be ignored, when calling a "GET" action method (i.e. an action 133 | * method that does not accept a request body). 134 | * 135 | * @param {Object.=} actions Hash with declaration of custom actions that will be available 136 | * in addition to the default set of resource actions (see below). If a custom action has the same 137 | * key as a default action (e.g. `save`), then the default action will be *overwritten*, and not 138 | * extended. 139 | * 140 | * The declaration should be created in the format of {@link ng.$http#usage $http.config}: 141 | * 142 | * { 143 | * action1: {method:?, params:?, isArray:?, headers:?, ...}, 144 | * action2: {method:?, params:?, isArray:?, headers:?, ...}, 145 | * ... 146 | * } 147 | * 148 | * Where: 149 | * 150 | * - **`action`** – {string} – The name of action. This name becomes the name of the method on 151 | * your resource object. 152 | * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`, 153 | * `DELETE`, `JSONP`, etc). 154 | * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of 155 | * the parameter value is a function, it will be called every time when a param value needs to 156 | * be obtained for a request (unless the param was overridden). The function will be passed the 157 | * current data value as an argument. 158 | * - **`url`** – {string} – Action specific `url` override. The url templating is supported just 159 | * like for the resource-level urls. 160 | * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, 161 | * see `returns` section. 162 | * - **`transformRequest`** – 163 | * `{function(data, headersGetter)|Array.}` – 164 | * Transform function or an array of such functions. The transform function takes the http 165 | * request body and headers and returns its transformed (typically serialized) version. 166 | * By default, transformRequest will contain one function that checks if the request data is 167 | * an object and serializes it using `angular.toJson`. To prevent this behavior, set 168 | * `transformRequest` to an empty array: `transformRequest: []` 169 | * - **`transformResponse`** – 170 | * `{function(data, headersGetter, status)|Array.}` – 171 | * Transform function or an array of such functions. The transform function takes the HTTP 172 | * response body, headers and status and returns its transformed (typically deserialized) 173 | * version. 174 | * By default, transformResponse will contain one function that checks if the response looks 175 | * like a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, 176 | * set `transformResponse` to an empty array: `transformResponse: []` 177 | * - **`cache`** – `{boolean|Cache}` – A boolean value or object created with 178 | * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response. 179 | * See {@link $http#caching $http Caching} for more information. 180 | * - **`timeout`** – `{number}` – Timeout in milliseconds.
181 | * **Note:** In contrast to {@link ng.$http#usage $http.config}, {@link ng.$q promises} are 182 | * **not** supported in `$resource`, because the same value would be used for multiple requests. 183 | * If you are looking for a way to cancel requests, you should use the `cancellable` option. 184 | * - **`cancellable`** – `{boolean}` – If true, the request made by a "non-instance" call will be 185 | * cancelled (if not already completed) by calling `$cancelRequest()` on the call's return 186 | * value. Calling `$cancelRequest()` for a non-cancellable or an already completed/cancelled 187 | * request will have no effect. 188 | * - **`withCredentials`** – `{boolean}` – Whether to set the `withCredentials` flag on the 189 | * XHR object. See 190 | * [XMLHttpRequest.withCredentials](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials) 191 | * for more information. 192 | * - **`responseType`** – `{string}` – See 193 | * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType). 194 | * - **`interceptor`** – `{Object=}` – The interceptor object has four optional methods - 195 | * `request`, `requestError`, `response`, and `responseError`. See 196 | * {@link ng.$http#interceptors $http interceptors} for details. Note that 197 | * `request`/`requestError` interceptors are applied before calling `$http`, thus before any 198 | * global `$http` interceptors. Also, rejecting or throwing an error inside the `request` 199 | * interceptor will result in calling the `responseError` interceptor. 200 | * The resource instance or collection is available on the `resource` property of the 201 | * `http response` object passed to `response`/`responseError` interceptors. 202 | * Keep in mind that the associated promise will be resolved with the value returned by the 203 | * response interceptors. Make sure you return an appropriate value and not the `response` 204 | * object passed as input. For reference, the default `response` interceptor (which gets applied 205 | * if you don't specify a custom one) returns `response.resource`.
206 | * See {@link ngResource.$resource#using-interceptors below} for an example of using 207 | * interceptors in `$resource`. 208 | * - **`hasBody`** – `{boolean}` – If true, then the request will have a body. 209 | * If not specified, then only POST, PUT and PATCH requests will have a body. * 210 | * @param {Object} options Hash with custom settings that should extend the 211 | * default `$resourceProvider` behavior. The supported options are: 212 | * 213 | * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing 214 | * slashes from any calculated URL will be stripped. (Defaults to true.) 215 | * - **`cancellable`** – {boolean} – If true, the request made by a "non-instance" call will be 216 | * cancelled (if not already completed) by calling `$cancelRequest()` on the call's return value. 217 | * This can be overwritten per action. (Defaults to false.) 218 | * 219 | * @returns {Object} A resource "class" object with methods for the default set of resource actions 220 | * optionally extended with custom `actions`. The default set contains these actions: 221 | * ```js 222 | * { 223 | * 'get': {method: 'GET'}, 224 | * 'save': {method: 'POST'}, 225 | * 'query': {method: 'GET', isArray: true}, 226 | * 'remove': {method: 'DELETE'}, 227 | * 'delete': {method: 'DELETE'} 228 | * } 229 | * ``` 230 | * 231 | * Calling these methods invoke {@link ng.$http} with the specified http method, destination and 232 | * parameters. When the data is returned from the server then the object is an instance of the 233 | * resource class. The actions `save`, `remove` and `delete` are available on it as methods with 234 | * the `$` prefix. This allows you to easily perform CRUD operations (create, read, update, 235 | * delete) on server-side data like this: 236 | * ```js 237 | * var User = $resource('/user/:userId', {userId: '@id'}); 238 | * User.get({userId: 123}).$promise.then(function(user) { 239 | * user.abc = true; 240 | * user.$save(); 241 | * }); 242 | * ``` 243 | * 244 | * It is important to realize that invoking a `$resource` object method immediately returns an 245 | * empty reference (object or array depending on `isArray`). Once the data is returned from the 246 | * server the existing reference is populated with the actual data. This is a useful trick since 247 | * usually the resource is assigned to a model which is then rendered by the view. Having an empty 248 | * object results in no rendering, once the data arrives from the server then the object is 249 | * populated with the data and the view automatically re-renders itself showing the new data. This 250 | * means that in most cases one never has to write a callback function for the action methods. 251 | * 252 | * The action methods on the class object or instance object can be invoked with the following 253 | * parameters: 254 | * 255 | * - "class" actions without a body: `Resource.action([parameters], [success], [error])` 256 | * - "class" actions with a body: `Resource.action([parameters], postData, [success], [error])` 257 | * - instance actions: `instance.$action([parameters], [success], [error])` 258 | * 259 | * 260 | * When calling instance methods, the instance itself is used as the request body (if the action 261 | * should have a body). By default, only actions using `POST`, `PUT` or `PATCH` have request 262 | * bodies, but you can use the `hasBody` configuration option to specify whether an action 263 | * should have a body or not (regardless of its HTTP method). 264 | * 265 | * 266 | * Success callback is called with (value (Object|Array), responseHeaders (Function), 267 | * status (number), statusText (string)) arguments, where `value` is the populated resource 268 | * instance or collection object. The error callback is called with (httpResponse) argument. 269 | * 270 | * Class actions return an empty instance (with the additional properties listed below). 271 | * Instance actions return a promise for the operation. 272 | * 273 | * The Resource instances and collections have these additional properties: 274 | * 275 | * - `$promise`: The {@link ng.$q promise} of the original server interaction that created this 276 | * instance or collection. 277 | * 278 | * On success, the promise is resolved with the same resource instance or collection object, 279 | * updated with data from server. This makes it easy to use in the 280 | * {@link ngRoute.$routeProvider `resolve` section of `$routeProvider.when()`} to defer view 281 | * rendering until the resource(s) are loaded. 282 | * 283 | * On failure, the promise is rejected with the {@link ng.$http http response} object. 284 | * 285 | * If an interceptor object was provided, the promise will instead be resolved with the value 286 | * returned by the response interceptor (on success) or responceError interceptor (on failure). 287 | * 288 | * - `$resolved`: `true` after first server interaction is completed (either with success or 289 | * rejection), `false` before that. Knowing if the Resource has been resolved is useful in 290 | * data-binding. If there is a response/responseError interceptor and it returns a promise, 291 | * `$resolved` will wait for that too. 292 | * 293 | * The Resource instances and collections have these additional methods: 294 | * 295 | * - `$cancelRequest`: If there is a cancellable, pending request related to the instance or 296 | * collection, calling this method will abort the request. 297 | * 298 | * The Resource instances have these additional methods: 299 | * 300 | * - `toJSON`: It returns a simple object without any of the extra properties added as part of 301 | * the Resource API. This object can be serialized through {@link angular.toJson} safely 302 | * without attaching AngularJS-specific fields. Notice that `JSON.stringify` (and 303 | * `angular.toJson`) automatically use this method when serializing a Resource instance 304 | * (see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON%28%29_behavior)). 305 | * 306 | * @example 307 | * 308 | * ### Basic usage 309 | * 310 | ```js 311 | // Define a CreditCard class 312 | var CreditCard = $resource('/users/:userId/cards/:cardId', 313 | {userId: 123, cardId: '@id'}, { 314 | charge: {method: 'POST', params: {charge: true}} 315 | }); 316 | 317 | // We can retrieve a collection from the server 318 | var cards = CreditCard.query(); 319 | // GET: /users/123/cards 320 | // server returns: [{id: 456, number: '1234', name: 'Smith'}] 321 | 322 | // Wait for the request to complete 323 | cards.$promise.then(function() { 324 | var card = cards[0]; 325 | 326 | // Each item is an instance of CreditCard 327 | expect(card instanceof CreditCard).toEqual(true); 328 | 329 | // Non-GET methods are mapped onto the instances 330 | card.name = 'J. Smith'; 331 | card.$save(); 332 | // POST: /users/123/cards/456 {id: 456, number: '1234', name: 'J. Smith'} 333 | // server returns: {id: 456, number: '1234', name: 'J. Smith'} 334 | 335 | // Our custom method is mapped as well (since it uses POST) 336 | card.$charge({amount: 9.99}); 337 | // POST: /users/123/cards/456?amount=9.99&charge=true {id: 456, number: '1234', name: 'J. Smith'} 338 | }); 339 | 340 | // We can create an instance as well 341 | var newCard = new CreditCard({number: '0123'}); 342 | newCard.name = 'Mike Smith'; 343 | 344 | var savePromise = newCard.$save(); 345 | // POST: /users/123/cards {number: '0123', name: 'Mike Smith'} 346 | // server returns: {id: 789, number: '0123', name: 'Mike Smith'} 347 | 348 | savePromise.then(function() { 349 | // Once the promise is resolved, the created instance 350 | // is populated with the data returned by the server 351 | expect(newCard.id).toEqual(789); 352 | }); 353 | ``` 354 | * 355 | * The object returned from a call to `$resource` is a resource "class" which has one "static" 356 | * method for each action in the definition. 357 | * 358 | * Calling these methods invokes `$http` on the `url` template with the given HTTP `method`, 359 | * `params` and `headers`. 360 | * 361 | * @example 362 | * 363 | * ### Accessing the response 364 | * 365 | * When the data is returned from the server then the object is an instance of the resource type and 366 | * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD 367 | * operations (create, read, update, delete) on server-side data. 368 | * 369 | ```js 370 | var User = $resource('/users/:userId', {userId: '@id'}); 371 | User.get({userId: 123}).$promise.then(function(user) { 372 | user.abc = true; 373 | user.$save(); 374 | }); 375 | ``` 376 | * 377 | * It's worth noting that the success callback for `get`, `query` and other methods gets called with 378 | * the resource instance (populated with the data that came from the server) as well as an `$http` 379 | * header getter function, the HTTP status code and the response status text. So one could rewrite 380 | * the above example and get access to HTTP headers as follows: 381 | * 382 | ```js 383 | var User = $resource('/users/:userId', {userId: '@id'}); 384 | User.get({userId: 123}, function(user, getResponseHeaders) { 385 | user.abc = true; 386 | user.$save(function(user, putResponseHeaders) { 387 | // `user` => saved `User` object 388 | // `putResponseHeaders` => `$http` header getter 389 | }); 390 | }); 391 | ``` 392 | * 393 | * @example 394 | * 395 | * ### Creating custom actions 396 | * 397 | * In this example we create a custom method on our resource to make a PUT request: 398 | * 399 | ```js 400 | var app = angular.module('app', ['ngResource']); 401 | 402 | // Some APIs expect a PUT request in the format URL/object/ID 403 | // Here we are creating an 'update' method 404 | app.factory('Notes', ['$resource', function($resource) { 405 | return $resource('/notes/:id', {id: '@id'}, { 406 | update: {method: 'PUT'} 407 | }); 408 | }]); 409 | 410 | // In our controller we get the ID from the URL using `$location` 411 | app.controller('NotesCtrl', ['$location', 'Notes', function($location, Notes) { 412 | // First, retrieve the corresponding `Note` object from the server 413 | // (Assuming a URL of the form `.../notes?id=XYZ`) 414 | var noteId = $location.search().id; 415 | var note = Notes.get({id: noteId}); 416 | 417 | note.$promise.then(function() { 418 | note.content = 'Hello, world!'; 419 | 420 | // Now call `update` to save the changes on the server 421 | Notes.update(note); 422 | // This will PUT /notes/ID with the note object as the request payload 423 | 424 | // Since `update` is a non-GET method, it will also be available on the instance 425 | // (prefixed with `$`), so we could replace the `Note.update()` call with: 426 | //note.$update(); 427 | }); 428 | }]); 429 | ``` 430 | * 431 | * @example 432 | * 433 | * ### Cancelling requests 434 | * 435 | * If an action's configuration specifies that it is cancellable, you can cancel the request related 436 | * to an instance or collection (as long as it is a result of a "non-instance" call): 437 | * 438 | ```js 439 | // ...defining the `Hotel` resource... 440 | var Hotel = $resource('/api/hotels/:id', {id: '@id'}, { 441 | // Let's make the `query()` method cancellable 442 | query: {method: 'get', isArray: true, cancellable: true} 443 | }); 444 | 445 | // ...somewhere in the PlanVacationController... 446 | ... 447 | this.onDestinationChanged = function onDestinationChanged(destination) { 448 | // We don't care about any pending request for hotels 449 | // in a different destination any more 450 | if (this.availableHotels) { 451 | this.availableHotels.$cancelRequest(); 452 | } 453 | 454 | // Let's query for hotels in `destination` 455 | // (calls: /api/hotels?location=) 456 | this.availableHotels = Hotel.query({location: destination}); 457 | }; 458 | ``` 459 | * 460 | * @example 461 | * 462 | * ### Using interceptors 463 | * 464 | * You can use interceptors to transform the request or response, perform additional operations, and 465 | * modify the returned instance/collection. The following example, uses `request` and `response` 466 | * interceptors to augment the returned instance with additional info: 467 | * 468 | ```js 469 | var Thing = $resource('/api/things/:id', {id: '@id'}, { 470 | save: { 471 | method: 'POST', 472 | interceptor: { 473 | request: function(config) { 474 | // Before the request is sent out, store a timestamp on the request config 475 | config.requestTimestamp = Date.now(); 476 | return config; 477 | }, 478 | response: function(response) { 479 | // Get the instance from the response object 480 | var instance = response.resource; 481 | 482 | // Augment the instance with a custom `saveLatency` property, computed as the time 483 | // between sending the request and receiving the response. 484 | instance.saveLatency = Date.now() - response.config.requestTimestamp; 485 | 486 | // Return the instance 487 | return instance; 488 | } 489 | } 490 | } 491 | }); 492 | 493 | Thing.save({foo: 'bar'}).$promise.then(function(thing) { 494 | console.log('That thing was saved in ' + thing.saveLatency + 'ms.'); 495 | }); 496 | ``` 497 | * 498 | */ 499 | angular.module('ngResource', ['ng']). 500 | info({ angularVersion: '1.8.0' }). 501 | provider('$resource', function ResourceProvider() { 502 | var PROTOCOL_AND_IPV6_REGEX = /^https?:\/\/\[[^\]]*][^/]*/; 503 | 504 | var provider = this; 505 | 506 | /** 507 | * @ngdoc property 508 | * @name $resourceProvider#defaults 509 | * @description 510 | * Object containing default options used when creating `$resource` instances. 511 | * 512 | * The default values satisfy a wide range of usecases, but you may choose to overwrite any of 513 | * them to further customize your instances. The available properties are: 514 | * 515 | * - **stripTrailingSlashes** – `{boolean}` – If true, then the trailing slashes from any 516 | * calculated URL will be stripped.
517 | * (Defaults to true.) 518 | * - **cancellable** – `{boolean}` – If true, the request made by a "non-instance" call will be 519 | * cancelled (if not already completed) by calling `$cancelRequest()` on the call's return 520 | * value. For more details, see {@link ngResource.$resource}. This can be overwritten per 521 | * resource class or action.
522 | * (Defaults to false.) 523 | * - **actions** - `{Object.}` - A hash with default actions declarations. Actions are 524 | * high-level methods corresponding to RESTful actions/methods on resources. An action may 525 | * specify what HTTP method to use, what URL to hit, if the return value will be a single 526 | * object or a collection (array) of objects etc. For more details, see 527 | * {@link ngResource.$resource}. The actions can also be enhanced or overwritten per resource 528 | * class.
529 | * The default actions are: 530 | * ```js 531 | * { 532 | * get: {method: 'GET'}, 533 | * save: {method: 'POST'}, 534 | * query: {method: 'GET', isArray: true}, 535 | * remove: {method: 'DELETE'}, 536 | * delete: {method: 'DELETE'} 537 | * } 538 | * ``` 539 | * 540 | * #### Example 541 | * 542 | * For example, you can specify a new `update` action that uses the `PUT` HTTP verb: 543 | * 544 | * ```js 545 | * angular. 546 | * module('myApp'). 547 | * config(['$resourceProvider', function ($resourceProvider) { 548 | * $resourceProvider.defaults.actions.update = { 549 | * method: 'PUT' 550 | * }; 551 | * }]); 552 | * ``` 553 | * 554 | * Or you can even overwrite the whole `actions` list and specify your own: 555 | * 556 | * ```js 557 | * angular. 558 | * module('myApp'). 559 | * config(['$resourceProvider', function ($resourceProvider) { 560 | * $resourceProvider.defaults.actions = { 561 | * create: {method: 'POST'}, 562 | * get: {method: 'GET'}, 563 | * getAll: {method: 'GET', isArray:true}, 564 | * update: {method: 'PUT'}, 565 | * delete: {method: 'DELETE'} 566 | * }; 567 | * }); 568 | * ``` 569 | * 570 | */ 571 | this.defaults = { 572 | // Strip slashes by default 573 | stripTrailingSlashes: true, 574 | 575 | // Make non-instance requests cancellable (via `$cancelRequest()`) 576 | cancellable: false, 577 | 578 | // Default actions configuration 579 | actions: { 580 | 'get': {method: 'GET'}, 581 | 'save': {method: 'POST'}, 582 | 'query': {method: 'GET', isArray: true}, 583 | 'remove': {method: 'DELETE'}, 584 | 'delete': {method: 'DELETE'} 585 | } 586 | }; 587 | 588 | this.$get = ['$http', '$log', '$q', '$timeout', function($http, $log, $q, $timeout) { 589 | 590 | var noop = angular.noop, 591 | forEach = angular.forEach, 592 | extend = angular.extend, 593 | copy = angular.copy, 594 | isArray = angular.isArray, 595 | isDefined = angular.isDefined, 596 | isFunction = angular.isFunction, 597 | isNumber = angular.isNumber, 598 | encodeUriQuery = angular.$$encodeUriQuery, 599 | encodeUriSegment = angular.$$encodeUriSegment; 600 | 601 | function Route(template, defaults) { 602 | this.template = template; 603 | this.defaults = extend({}, provider.defaults, defaults); 604 | this.urlParams = {}; 605 | } 606 | 607 | Route.prototype = { 608 | setUrlParams: function(config, params, actionUrl) { 609 | var self = this, 610 | url = actionUrl || self.template, 611 | val, 612 | encodedVal, 613 | protocolAndIpv6 = ''; 614 | 615 | var urlParams = self.urlParams = Object.create(null); 616 | forEach(url.split(/\W/), function(param) { 617 | if (param === 'hasOwnProperty') { 618 | throw $resourceMinErr('badname', 'hasOwnProperty is not a valid parameter name.'); 619 | } 620 | if (!(new RegExp('^\\d+$').test(param)) && param && 621 | (new RegExp('(^|[^\\\\]):' + param + '(\\W|$)').test(url))) { 622 | urlParams[param] = { 623 | isQueryParamValue: (new RegExp('\\?.*=:' + param + '(?:\\W|$)')).test(url) 624 | }; 625 | } 626 | }); 627 | url = url.replace(/\\:/g, ':'); 628 | url = url.replace(PROTOCOL_AND_IPV6_REGEX, function(match) { 629 | protocolAndIpv6 = match; 630 | return ''; 631 | }); 632 | 633 | params = params || {}; 634 | forEach(self.urlParams, function(paramInfo, urlParam) { 635 | val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; 636 | if (isDefined(val) && val !== null) { 637 | if (paramInfo.isQueryParamValue) { 638 | encodedVal = encodeUriQuery(val, true); 639 | } else { 640 | encodedVal = encodeUriSegment(val); 641 | } 642 | url = url.replace(new RegExp(':' + urlParam + '(\\W|$)', 'g'), function(match, p1) { 643 | return encodedVal + p1; 644 | }); 645 | } else { 646 | url = url.replace(new RegExp('(/?):' + urlParam + '(\\W|$)', 'g'), function(match, 647 | leadingSlashes, tail) { 648 | if (tail.charAt(0) === '/') { 649 | return tail; 650 | } else { 651 | return leadingSlashes + tail; 652 | } 653 | }); 654 | } 655 | }); 656 | 657 | // strip trailing slashes and set the url (unless this behavior is specifically disabled) 658 | if (self.defaults.stripTrailingSlashes) { 659 | url = url.replace(/\/+$/, '') || '/'; 660 | } 661 | 662 | // Collapse `/.` if found in the last URL path segment before the query. 663 | // E.g. `http://url.com/id/.format?q=x` becomes `http://url.com/id.format?q=x`. 664 | url = url.replace(/\/\.(?=\w+($|\?))/, '.'); 665 | // Replace escaped `/\.` with `/.`. 666 | // (If `\.` comes from a param value, it will be encoded as `%5C.`.) 667 | config.url = protocolAndIpv6 + url.replace(/\/(\\|%5C)\./, '/.'); 668 | 669 | 670 | // set params - delegate param encoding to $http 671 | forEach(params, function(value, key) { 672 | if (!self.urlParams[key]) { 673 | config.params = config.params || {}; 674 | config.params[key] = value; 675 | } 676 | }); 677 | } 678 | }; 679 | 680 | 681 | function resourceFactory(url, paramDefaults, actions, options) { 682 | var route = new Route(url, options); 683 | 684 | actions = extend({}, provider.defaults.actions, actions); 685 | 686 | function extractParams(data, actionParams) { 687 | var ids = {}; 688 | actionParams = extend({}, paramDefaults, actionParams); 689 | forEach(actionParams, function(value, key) { 690 | if (isFunction(value)) { value = value(data); } 691 | ids[key] = value && value.charAt && value.charAt(0) === '@' ? 692 | lookupDottedPath(data, value.substr(1)) : value; 693 | }); 694 | return ids; 695 | } 696 | 697 | function defaultResponseInterceptor(response) { 698 | return response.resource; 699 | } 700 | 701 | function Resource(value) { 702 | shallowClearAndCopy(value || {}, this); 703 | } 704 | 705 | Resource.prototype.toJSON = function() { 706 | var data = extend({}, this); 707 | delete data.$promise; 708 | delete data.$resolved; 709 | delete data.$cancelRequest; 710 | return data; 711 | }; 712 | 713 | forEach(actions, function(action, name) { 714 | var hasBody = action.hasBody === true || (action.hasBody !== false && /^(POST|PUT|PATCH)$/i.test(action.method)); 715 | var numericTimeout = action.timeout; 716 | var cancellable = isDefined(action.cancellable) ? 717 | action.cancellable : route.defaults.cancellable; 718 | 719 | if (numericTimeout && !isNumber(numericTimeout)) { 720 | $log.debug('ngResource:\n' + 721 | ' Only numeric values are allowed as `timeout`.\n' + 722 | ' Promises are not supported in $resource, because the same value would ' + 723 | 'be used for multiple requests. If you are looking for a way to cancel ' + 724 | 'requests, you should use the `cancellable` option.'); 725 | delete action.timeout; 726 | numericTimeout = null; 727 | } 728 | 729 | Resource[name] = function(a1, a2, a3, a4) { 730 | var params = {}, data, onSuccess, onError; 731 | 732 | switch (arguments.length) { 733 | case 4: 734 | onError = a4; 735 | onSuccess = a3; 736 | // falls through 737 | case 3: 738 | case 2: 739 | if (isFunction(a2)) { 740 | if (isFunction(a1)) { 741 | onSuccess = a1; 742 | onError = a2; 743 | break; 744 | } 745 | 746 | onSuccess = a2; 747 | onError = a3; 748 | // falls through 749 | } else { 750 | params = a1; 751 | data = a2; 752 | onSuccess = a3; 753 | break; 754 | } 755 | // falls through 756 | case 1: 757 | if (isFunction(a1)) onSuccess = a1; 758 | else if (hasBody) data = a1; 759 | else params = a1; 760 | break; 761 | case 0: break; 762 | default: 763 | throw $resourceMinErr('badargs', 764 | 'Expected up to 4 arguments [params, data, success, error], got {0} arguments', 765 | arguments.length); 766 | } 767 | 768 | var isInstanceCall = this instanceof Resource; 769 | var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); 770 | var httpConfig = {}; 771 | var requestInterceptor = action.interceptor && action.interceptor.request || undefined; 772 | var requestErrorInterceptor = action.interceptor && action.interceptor.requestError || 773 | undefined; 774 | var responseInterceptor = action.interceptor && action.interceptor.response || 775 | defaultResponseInterceptor; 776 | var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || 777 | $q.reject; 778 | var successCallback = onSuccess ? function(val) { 779 | onSuccess(val, response.headers, response.status, response.statusText); 780 | } : undefined; 781 | var errorCallback = onError || undefined; 782 | var timeoutDeferred; 783 | var numericTimeoutPromise; 784 | var response; 785 | 786 | forEach(action, function(value, key) { 787 | switch (key) { 788 | default: 789 | httpConfig[key] = copy(value); 790 | break; 791 | case 'params': 792 | case 'isArray': 793 | case 'interceptor': 794 | case 'cancellable': 795 | break; 796 | } 797 | }); 798 | 799 | if (!isInstanceCall && cancellable) { 800 | timeoutDeferred = $q.defer(); 801 | httpConfig.timeout = timeoutDeferred.promise; 802 | 803 | if (numericTimeout) { 804 | numericTimeoutPromise = $timeout(timeoutDeferred.resolve, numericTimeout); 805 | } 806 | } 807 | 808 | if (hasBody) httpConfig.data = data; 809 | route.setUrlParams(httpConfig, 810 | extend({}, extractParams(data, action.params || {}), params), 811 | action.url); 812 | 813 | // Start the promise chain 814 | var promise = $q. 815 | resolve(httpConfig). 816 | then(requestInterceptor). 817 | catch(requestErrorInterceptor). 818 | then($http); 819 | 820 | promise = promise.then(function(resp) { 821 | var data = resp.data; 822 | 823 | if (data) { 824 | // Need to convert action.isArray to boolean in case it is undefined 825 | if (isArray(data) !== (!!action.isArray)) { 826 | throw $resourceMinErr('badcfg', 827 | 'Error in resource configuration for action `{0}`. Expected response to ' + 828 | 'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object', 829 | isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url); 830 | } 831 | if (action.isArray) { 832 | value.length = 0; 833 | forEach(data, function(item) { 834 | if (typeof item === 'object') { 835 | value.push(new Resource(item)); 836 | } else { 837 | // Valid JSON values may be string literals, and these should not be converted 838 | // into objects. These items will not have access to the Resource prototype 839 | // methods, but unfortunately there 840 | value.push(item); 841 | } 842 | }); 843 | } else { 844 | var promise = value.$promise; // Save the promise 845 | shallowClearAndCopy(data, value); 846 | value.$promise = promise; // Restore the promise 847 | } 848 | } 849 | 850 | resp.resource = value; 851 | response = resp; 852 | return responseInterceptor(resp); 853 | }, function(rejectionOrResponse) { 854 | rejectionOrResponse.resource = value; 855 | response = rejectionOrResponse; 856 | return responseErrorInterceptor(rejectionOrResponse); 857 | }); 858 | 859 | promise = promise['finally'](function() { 860 | value.$resolved = true; 861 | if (!isInstanceCall && cancellable) { 862 | value.$cancelRequest = noop; 863 | $timeout.cancel(numericTimeoutPromise); 864 | timeoutDeferred = numericTimeoutPromise = httpConfig.timeout = null; 865 | } 866 | }); 867 | 868 | // Run the `success`/`error` callbacks, but do not let them affect the returned promise. 869 | promise.then(successCallback, errorCallback); 870 | 871 | if (!isInstanceCall) { 872 | // we are creating instance / collection 873 | // - set the initial promise 874 | // - return the instance / collection 875 | value.$promise = promise; 876 | value.$resolved = false; 877 | if (cancellable) value.$cancelRequest = cancelRequest; 878 | 879 | return value; 880 | } 881 | 882 | // instance call 883 | return promise; 884 | 885 | function cancelRequest(value) { 886 | promise.catch(noop); 887 | if (timeoutDeferred !== null) { 888 | timeoutDeferred.resolve(value); 889 | } 890 | } 891 | }; 892 | 893 | 894 | Resource.prototype['$' + name] = function(params, success, error) { 895 | if (isFunction(params)) { 896 | error = success; success = params; params = {}; 897 | } 898 | var result = Resource[name].call(this, params, this, success, error); 899 | return result.$promise || result; 900 | }; 901 | }); 902 | 903 | return Resource; 904 | } 905 | 906 | return resourceFactory; 907 | }]; 908 | }); 909 | 910 | 911 | })(window, window.angular); 912 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/angular-sanitize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.8.0 3 | * (c) 2010-2020 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular) {'use strict'; 7 | 8 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 9 | * Any commits to this file should be reviewed with security in mind. * 10 | * Changes to this file can potentially create security vulnerabilities. * 11 | * An approval from 2 Core members with history of modifying * 12 | * this file is required. * 13 | * * 14 | * Does the change somehow allow for arbitrary javascript to be executed? * 15 | * Or allows for someone to change the prototype of built-in objects? * 16 | * Or gives undesired access to variables likes document or window? * 17 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 18 | 19 | var $sanitizeMinErr = angular.$$minErr('$sanitize'); 20 | var bind; 21 | var extend; 22 | var forEach; 23 | var isArray; 24 | var isDefined; 25 | var lowercase; 26 | var noop; 27 | var nodeContains; 28 | var htmlParser; 29 | var htmlSanitizeWriter; 30 | 31 | /** 32 | * @ngdoc module 33 | * @name ngSanitize 34 | * @description 35 | * 36 | * The `ngSanitize` module provides functionality to sanitize HTML. 37 | * 38 | * See {@link ngSanitize.$sanitize `$sanitize`} for usage. 39 | */ 40 | 41 | /** 42 | * @ngdoc service 43 | * @name $sanitize 44 | * @kind function 45 | * 46 | * @description 47 | * Sanitizes an html string by stripping all potentially dangerous tokens. 48 | * 49 | * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are 50 | * then serialized back to a properly escaped HTML string. This means that no unsafe input can make 51 | * it into the returned string. 52 | * 53 | * The whitelist for URL sanitization of attribute values is configured using the functions 54 | * `aHrefSanitizationWhitelist` and `imgSrcSanitizationWhitelist` of {@link $compileProvider}. 55 | * 56 | * The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}. 57 | * 58 | * @param {string} html HTML input. 59 | * @returns {string} Sanitized HTML. 60 | * 61 | * @example 62 | 63 | 64 | 76 |
77 | Snippet: 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 |
DirectiveHowSourceRendered
ng-bind-htmlAutomatically uses $sanitize
<div ng-bind-html="snippet">
</div>
ng-bind-htmlBypass $sanitize by explicitly trusting the dangerous value 95 |
<div ng-bind-html="deliberatelyTrustDangerousSnippet()">
 96 | </div>
97 |
ng-bindAutomatically escapes
<div ng-bind="snippet">
</div>
107 |
108 |
109 | 110 | it('should sanitize the html snippet by default', function() { 111 | expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')). 112 | toBe('

an html\nclick here\nsnippet

'); 113 | }); 114 | 115 | it('should inline raw snippet if bound to a trusted value', function() { 116 | expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')). 117 | toBe("

an html\n" + 118 | "click here\n" + 119 | "snippet

"); 120 | }); 121 | 122 | it('should escape snippet without any filter', function() { 123 | expect(element(by.css('#bind-default div')).getAttribute('innerHTML')). 124 | toBe("<p style=\"color:blue\">an html\n" + 125 | "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 126 | "snippet</p>"); 127 | }); 128 | 129 | it('should update', function() { 130 | element(by.model('snippet')).clear(); 131 | element(by.model('snippet')).sendKeys('new text'); 132 | expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')). 133 | toBe('new text'); 134 | expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).toBe( 135 | 'new text'); 136 | expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).toBe( 137 | "new <b onclick=\"alert(1)\">text</b>"); 138 | }); 139 |
140 |
141 | */ 142 | 143 | 144 | /** 145 | * @ngdoc provider 146 | * @name $sanitizeProvider 147 | * @this 148 | * 149 | * @description 150 | * Creates and configures {@link $sanitize} instance. 151 | */ 152 | function $SanitizeProvider() { 153 | var hasBeenInstantiated = false; 154 | var svgEnabled = false; 155 | 156 | this.$get = ['$$sanitizeUri', function($$sanitizeUri) { 157 | hasBeenInstantiated = true; 158 | if (svgEnabled) { 159 | extend(validElements, svgElements); 160 | } 161 | return function(html) { 162 | var buf = []; 163 | htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { 164 | return !/^unsafe:/.test($$sanitizeUri(uri, isImage)); 165 | })); 166 | return buf.join(''); 167 | }; 168 | }]; 169 | 170 | 171 | /** 172 | * @ngdoc method 173 | * @name $sanitizeProvider#enableSvg 174 | * @kind function 175 | * 176 | * @description 177 | * Enables a subset of svg to be supported by the sanitizer. 178 | * 179 | *
180 | *

By enabling this setting without taking other precautions, you might expose your 181 | * application to click-hijacking attacks. In these attacks, sanitized svg elements could be positioned 182 | * outside of the containing element and be rendered over other elements on the page (e.g. a login 183 | * link). Such behavior can then result in phishing incidents.

184 | * 185 | *

To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg 186 | * tags within the sanitized content:

187 | * 188 | *
189 | * 190 | *

191 |    *   .rootOfTheIncludedContent svg {
192 |    *     overflow: hidden !important;
193 |    *   }
194 |    *   
195 | *
196 | * 197 | * @param {boolean=} flag Enable or disable SVG support in the sanitizer. 198 | * @returns {boolean|$sanitizeProvider} Returns the currently configured value if called 199 | * without an argument or self for chaining otherwise. 200 | */ 201 | this.enableSvg = function(enableSvg) { 202 | if (isDefined(enableSvg)) { 203 | svgEnabled = enableSvg; 204 | return this; 205 | } else { 206 | return svgEnabled; 207 | } 208 | }; 209 | 210 | 211 | /** 212 | * @ngdoc method 213 | * @name $sanitizeProvider#addValidElements 214 | * @kind function 215 | * 216 | * @description 217 | * Extends the built-in lists of valid HTML/SVG elements, i.e. elements that are considered safe 218 | * and are not stripped off during sanitization. You can extend the following lists of elements: 219 | * 220 | * - `htmlElements`: A list of elements (tag names) to extend the current list of safe HTML 221 | * elements. HTML elements considered safe will not be removed during sanitization. All other 222 | * elements will be stripped off. 223 | * 224 | * - `htmlVoidElements`: This is similar to `htmlElements`, but marks the elements as 225 | * "void elements" (similar to HTML 226 | * [void elements](https://rawgit.com/w3c/html/html5.1-2/single-page.html#void-elements)). These 227 | * elements have no end tag and cannot have content. 228 | * 229 | * - `svgElements`: This is similar to `htmlElements`, but for SVG elements. This list is only 230 | * taken into account if SVG is {@link ngSanitize.$sanitizeProvider#enableSvg enabled} for 231 | * `$sanitize`. 232 | * 233 | *
234 | * This method must be called during the {@link angular.Module#config config} phase. Once the 235 | * `$sanitize` service has been instantiated, this method has no effect. 236 | *
237 | * 238 | *
239 | * Keep in mind that extending the built-in lists of elements may expose your app to XSS or 240 | * other vulnerabilities. Be very mindful of the elements you add. 241 | *
242 | * 243 | * @param {Array|Object} elements - A list of valid HTML elements or an object with one or 244 | * more of the following properties: 245 | * - **htmlElements** - `{Array}` - A list of elements to extend the current list of 246 | * HTML elements. 247 | * - **htmlVoidElements** - `{Array}` - A list of elements to extend the current list of 248 | * void HTML elements; i.e. elements that do not have an end tag. 249 | * - **svgElements** - `{Array}` - A list of elements to extend the current list of SVG 250 | * elements. The list of SVG elements is only taken into account if SVG is 251 | * {@link ngSanitize.$sanitizeProvider#enableSvg enabled} for `$sanitize`. 252 | * 253 | * Passing an array (`[...]`) is equivalent to passing `{htmlElements: [...]}`. 254 | * 255 | * @return {$sanitizeProvider} Returns self for chaining. 256 | */ 257 | this.addValidElements = function(elements) { 258 | if (!hasBeenInstantiated) { 259 | if (isArray(elements)) { 260 | elements = {htmlElements: elements}; 261 | } 262 | 263 | addElementsTo(svgElements, elements.svgElements); 264 | addElementsTo(voidElements, elements.htmlVoidElements); 265 | addElementsTo(validElements, elements.htmlVoidElements); 266 | addElementsTo(validElements, elements.htmlElements); 267 | } 268 | 269 | return this; 270 | }; 271 | 272 | 273 | /** 274 | * @ngdoc method 275 | * @name $sanitizeProvider#addValidAttrs 276 | * @kind function 277 | * 278 | * @description 279 | * Extends the built-in list of valid attributes, i.e. attributes that are considered safe and are 280 | * not stripped off during sanitization. 281 | * 282 | * **Note**: 283 | * The new attributes will not be treated as URI attributes, which means their values will not be 284 | * sanitized as URIs using `$compileProvider`'s 285 | * {@link ng.$compileProvider#aHrefSanitizationWhitelist aHrefSanitizationWhitelist} and 286 | * {@link ng.$compileProvider#imgSrcSanitizationWhitelist imgSrcSanitizationWhitelist}. 287 | * 288 | *
289 | * This method must be called during the {@link angular.Module#config config} phase. Once the 290 | * `$sanitize` service has been instantiated, this method has no effect. 291 | *
292 | * 293 | *
294 | * Keep in mind that extending the built-in list of attributes may expose your app to XSS or 295 | * other vulnerabilities. Be very mindful of the attributes you add. 296 | *
297 | * 298 | * @param {Array} attrs - A list of valid attributes. 299 | * 300 | * @returns {$sanitizeProvider} Returns self for chaining. 301 | */ 302 | this.addValidAttrs = function(attrs) { 303 | if (!hasBeenInstantiated) { 304 | extend(validAttrs, arrayToMap(attrs, true)); 305 | } 306 | return this; 307 | }; 308 | 309 | ////////////////////////////////////////////////////////////////////////////////////////////////// 310 | // Private stuff 311 | ////////////////////////////////////////////////////////////////////////////////////////////////// 312 | 313 | bind = angular.bind; 314 | extend = angular.extend; 315 | forEach = angular.forEach; 316 | isArray = angular.isArray; 317 | isDefined = angular.isDefined; 318 | lowercase = angular.$$lowercase; 319 | noop = angular.noop; 320 | 321 | htmlParser = htmlParserImpl; 322 | htmlSanitizeWriter = htmlSanitizeWriterImpl; 323 | 324 | nodeContains = window.Node.prototype.contains || /** @this */ function(arg) { 325 | // eslint-disable-next-line no-bitwise 326 | return !!(this.compareDocumentPosition(arg) & 16); 327 | }; 328 | 329 | // Regular Expressions for parsing tags and attributes 330 | var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 331 | // Match everything outside of normal chars and " (quote character) 332 | NON_ALPHANUMERIC_REGEXP = /([^#-~ |!])/g; 333 | 334 | 335 | // Good source of info about elements and attributes 336 | // http://dev.w3.org/html5/spec/Overview.html#semantics 337 | // http://simon.html5.org/html-elements 338 | 339 | // Safe Void Elements - HTML5 340 | // http://dev.w3.org/html5/spec/Overview.html#void-elements 341 | var voidElements = stringToMap('area,br,col,hr,img,wbr'); 342 | 343 | // Elements that you can, intentionally, leave open (and which close themselves) 344 | // http://dev.w3.org/html5/spec/Overview.html#optional-tags 345 | var optionalEndTagBlockElements = stringToMap('colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr'), 346 | optionalEndTagInlineElements = stringToMap('rp,rt'), 347 | optionalEndTagElements = extend({}, 348 | optionalEndTagInlineElements, 349 | optionalEndTagBlockElements); 350 | 351 | // Safe Block Elements - HTML5 352 | var blockElements = extend({}, optionalEndTagBlockElements, stringToMap('address,article,' + 353 | 'aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,' + 354 | 'h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul')); 355 | 356 | // Inline Elements - HTML5 357 | var inlineElements = extend({}, optionalEndTagInlineElements, stringToMap('a,abbr,acronym,b,' + 358 | 'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,' + 359 | 'samp,small,span,strike,strong,sub,sup,time,tt,u,var')); 360 | 361 | // SVG Elements 362 | // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements 363 | // Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted. 364 | // They can potentially allow for arbitrary javascript to be executed. See #11290 365 | var svgElements = stringToMap('circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,' + 366 | 'hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,' + 367 | 'radialGradient,rect,stop,svg,switch,text,title,tspan'); 368 | 369 | // Blocked Elements (will be stripped) 370 | var blockedElements = stringToMap('script,style'); 371 | 372 | var validElements = extend({}, 373 | voidElements, 374 | blockElements, 375 | inlineElements, 376 | optionalEndTagElements); 377 | 378 | //Attributes that have href and hence need to be sanitized 379 | var uriAttrs = stringToMap('background,cite,href,longdesc,src,xlink:href,xml:base'); 380 | 381 | var htmlAttrs = stringToMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' + 382 | 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' + 383 | 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' + 384 | 'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' + 385 | 'valign,value,vspace,width'); 386 | 387 | // SVG attributes (without "id" and "name" attributes) 388 | // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes 389 | var svgAttrs = stringToMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' + 390 | 'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' + 391 | 'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' + 392 | 'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' + 393 | 'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' + 394 | 'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' + 395 | 'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' + 396 | 'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' + 397 | 'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' + 398 | 'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' + 399 | 'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' + 400 | 'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' + 401 | 'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' + 402 | 'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' + 403 | 'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true); 404 | 405 | var validAttrs = extend({}, 406 | uriAttrs, 407 | svgAttrs, 408 | htmlAttrs); 409 | 410 | function stringToMap(str, lowercaseKeys) { 411 | return arrayToMap(str.split(','), lowercaseKeys); 412 | } 413 | 414 | function arrayToMap(items, lowercaseKeys) { 415 | var obj = {}, i; 416 | for (i = 0; i < items.length; i++) { 417 | obj[lowercaseKeys ? lowercase(items[i]) : items[i]] = true; 418 | } 419 | return obj; 420 | } 421 | 422 | function addElementsTo(elementsMap, newElements) { 423 | if (newElements && newElements.length) { 424 | extend(elementsMap, arrayToMap(newElements)); 425 | } 426 | } 427 | 428 | /** 429 | * Create an inert document that contains the dirty HTML that needs sanitizing 430 | * Depending upon browser support we use one of three strategies for doing this. 431 | * Support: Safari 10.x -> XHR strategy 432 | * Support: Firefox -> DomParser strategy 433 | */ 434 | var getInertBodyElement /* function(html: string): HTMLBodyElement */ = (function(window, document) { 435 | var inertDocument; 436 | if (document && document.implementation) { 437 | inertDocument = document.implementation.createHTMLDocument('inert'); 438 | } else { 439 | throw $sanitizeMinErr('noinert', 'Can\'t create an inert html document'); 440 | } 441 | var inertBodyElement = (inertDocument.documentElement || inertDocument.getDocumentElement()).querySelector('body'); 442 | 443 | // Check for the Safari 10.1 bug - which allows JS to run inside the SVG G element 444 | inertBodyElement.innerHTML = ''; 445 | if (!inertBodyElement.querySelector('svg')) { 446 | return getInertBodyElement_XHR; 447 | } else { 448 | // Check for the Firefox bug - which prevents the inner img JS from being sanitized 449 | inertBodyElement.innerHTML = '

'; 450 | if (inertBodyElement.querySelector('svg img')) { 451 | return getInertBodyElement_DOMParser; 452 | } else { 453 | return getInertBodyElement_InertDocument; 454 | } 455 | } 456 | 457 | function getInertBodyElement_XHR(html) { 458 | // We add this dummy element to ensure that the rest of the content is parsed as expected 459 | // e.g. leading whitespace is maintained and tags like `` do not get hoisted to the `` tag. 460 | html = '' + html; 461 | try { 462 | html = encodeURI(html); 463 | } catch (e) { 464 | return undefined; 465 | } 466 | var xhr = new window.XMLHttpRequest(); 467 | xhr.responseType = 'document'; 468 | xhr.open('GET', 'data:text/html;charset=utf-8,' + html, false); 469 | xhr.send(null); 470 | var body = xhr.response.body; 471 | body.firstChild.remove(); 472 | return body; 473 | } 474 | 475 | function getInertBodyElement_DOMParser(html) { 476 | // We add this dummy element to ensure that the rest of the content is parsed as expected 477 | // e.g. leading whitespace is maintained and tags like `` do not get hoisted to the `` tag. 478 | html = '' + html; 479 | try { 480 | var body = new window.DOMParser().parseFromString(html, 'text/html').body; 481 | body.firstChild.remove(); 482 | return body; 483 | } catch (e) { 484 | return undefined; 485 | } 486 | } 487 | 488 | function getInertBodyElement_InertDocument(html) { 489 | inertBodyElement.innerHTML = html; 490 | 491 | // Support: IE 9-11 only 492 | // strip custom-namespaced attributes on IE<=11 493 | if (document.documentMode) { 494 | stripCustomNsAttrs(inertBodyElement); 495 | } 496 | 497 | return inertBodyElement; 498 | } 499 | })(window, window.document); 500 | 501 | /** 502 | * @example 503 | * htmlParser(htmlString, { 504 | * start: function(tag, attrs) {}, 505 | * end: function(tag) {}, 506 | * chars: function(text) {}, 507 | * comment: function(text) {} 508 | * }); 509 | * 510 | * @param {string} html string 511 | * @param {object} handler 512 | */ 513 | function htmlParserImpl(html, handler) { 514 | if (html === null || html === undefined) { 515 | html = ''; 516 | } else if (typeof html !== 'string') { 517 | html = '' + html; 518 | } 519 | 520 | var inertBodyElement = getInertBodyElement(html); 521 | if (!inertBodyElement) return ''; 522 | 523 | //mXSS protection 524 | var mXSSAttempts = 5; 525 | do { 526 | if (mXSSAttempts === 0) { 527 | throw $sanitizeMinErr('uinput', 'Failed to sanitize html because the input is unstable'); 528 | } 529 | mXSSAttempts--; 530 | 531 | // trigger mXSS if it is going to happen by reading and writing the innerHTML 532 | html = inertBodyElement.innerHTML; 533 | inertBodyElement = getInertBodyElement(html); 534 | } while (html !== inertBodyElement.innerHTML); 535 | 536 | var node = inertBodyElement.firstChild; 537 | while (node) { 538 | switch (node.nodeType) { 539 | case 1: // ELEMENT_NODE 540 | handler.start(node.nodeName.toLowerCase(), attrToMap(node.attributes)); 541 | break; 542 | case 3: // TEXT NODE 543 | handler.chars(node.textContent); 544 | break; 545 | } 546 | 547 | var nextNode; 548 | if (!(nextNode = node.firstChild)) { 549 | if (node.nodeType === 1) { 550 | handler.end(node.nodeName.toLowerCase()); 551 | } 552 | nextNode = getNonDescendant('nextSibling', node); 553 | if (!nextNode) { 554 | while (nextNode == null) { 555 | node = getNonDescendant('parentNode', node); 556 | if (node === inertBodyElement) break; 557 | nextNode = getNonDescendant('nextSibling', node); 558 | if (node.nodeType === 1) { 559 | handler.end(node.nodeName.toLowerCase()); 560 | } 561 | } 562 | } 563 | } 564 | node = nextNode; 565 | } 566 | 567 | while ((node = inertBodyElement.firstChild)) { 568 | inertBodyElement.removeChild(node); 569 | } 570 | } 571 | 572 | function attrToMap(attrs) { 573 | var map = {}; 574 | for (var i = 0, ii = attrs.length; i < ii; i++) { 575 | var attr = attrs[i]; 576 | map[attr.name] = attr.value; 577 | } 578 | return map; 579 | } 580 | 581 | 582 | /** 583 | * Escapes all potentially dangerous characters, so that the 584 | * resulting string can be safely inserted into attribute or 585 | * element text. 586 | * @param value 587 | * @returns {string} escaped text 588 | */ 589 | function encodeEntities(value) { 590 | return value. 591 | replace(/&/g, '&'). 592 | replace(SURROGATE_PAIR_REGEXP, function(value) { 593 | var hi = value.charCodeAt(0); 594 | var low = value.charCodeAt(1); 595 | return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'; 596 | }). 597 | replace(NON_ALPHANUMERIC_REGEXP, function(value) { 598 | return '&#' + value.charCodeAt(0) + ';'; 599 | }). 600 | replace(//g, '>'); 602 | } 603 | 604 | /** 605 | * create an HTML/XML writer which writes to buffer 606 | * @param {Array} buf use buf.join('') to get out sanitized html string 607 | * @returns {object} in the form of { 608 | * start: function(tag, attrs) {}, 609 | * end: function(tag) {}, 610 | * chars: function(text) {}, 611 | * comment: function(text) {} 612 | * } 613 | */ 614 | function htmlSanitizeWriterImpl(buf, uriValidator) { 615 | var ignoreCurrentElement = false; 616 | var out = bind(buf, buf.push); 617 | return { 618 | start: function(tag, attrs) { 619 | tag = lowercase(tag); 620 | if (!ignoreCurrentElement && blockedElements[tag]) { 621 | ignoreCurrentElement = tag; 622 | } 623 | if (!ignoreCurrentElement && validElements[tag] === true) { 624 | out('<'); 625 | out(tag); 626 | forEach(attrs, function(value, key) { 627 | var lkey = lowercase(key); 628 | var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); 629 | if (validAttrs[lkey] === true && 630 | (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { 631 | out(' '); 632 | out(key); 633 | out('="'); 634 | out(encodeEntities(value)); 635 | out('"'); 636 | } 637 | }); 638 | out('>'); 639 | } 640 | }, 641 | end: function(tag) { 642 | tag = lowercase(tag); 643 | if (!ignoreCurrentElement && validElements[tag] === true && voidElements[tag] !== true) { 644 | out(''); 647 | } 648 | // eslint-disable-next-line eqeqeq 649 | if (tag == ignoreCurrentElement) { 650 | ignoreCurrentElement = false; 651 | } 652 | }, 653 | chars: function(chars) { 654 | if (!ignoreCurrentElement) { 655 | out(encodeEntities(chars)); 656 | } 657 | } 658 | }; 659 | } 660 | 661 | 662 | /** 663 | * When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' attribute to declare 664 | * ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). This is undesirable since we don't want 665 | * to allow any of these custom attributes. This method strips them all. 666 | * 667 | * @param node Root element to process 668 | */ 669 | function stripCustomNsAttrs(node) { 670 | while (node) { 671 | if (node.nodeType === window.Node.ELEMENT_NODE) { 672 | var attrs = node.attributes; 673 | for (var i = 0, l = attrs.length; i < l; i++) { 674 | var attrNode = attrs[i]; 675 | var attrName = attrNode.name.toLowerCase(); 676 | if (attrName === 'xmlns:ns1' || attrName.lastIndexOf('ns1:', 0) === 0) { 677 | node.removeAttributeNode(attrNode); 678 | i--; 679 | l--; 680 | } 681 | } 682 | } 683 | 684 | var nextNode = node.firstChild; 685 | if (nextNode) { 686 | stripCustomNsAttrs(nextNode); 687 | } 688 | 689 | node = getNonDescendant('nextSibling', node); 690 | } 691 | } 692 | 693 | function getNonDescendant(propName, node) { 694 | // An element is clobbered if its `propName` property points to one of its descendants 695 | var nextNode = node[propName]; 696 | if (nextNode && nodeContains.call(node, nextNode)) { 697 | throw $sanitizeMinErr('elclob', 'Failed to sanitize html because the element is clobbered: {0}', node.outerHTML || node.outerText); 698 | } 699 | return nextNode; 700 | } 701 | } 702 | 703 | function sanitizeText(chars) { 704 | var buf = []; 705 | var writer = htmlSanitizeWriter(buf, noop); 706 | writer.chars(chars); 707 | return buf.join(''); 708 | } 709 | 710 | 711 | // define ngSanitize module and register $sanitize service 712 | angular.module('ngSanitize', []) 713 | .provider('$sanitize', $SanitizeProvider) 714 | .info({ angularVersion: '1.8.0' }); 715 | 716 | /** 717 | * @ngdoc filter 718 | * @name linky 719 | * @kind function 720 | * 721 | * @description 722 | * Finds links in text input and turns them into html links. Supports `http/https/ftp/sftp/mailto` and 723 | * plain email address links. 724 | * 725 | * Requires the {@link ngSanitize `ngSanitize`} module to be installed. 726 | * 727 | * @param {string} text Input text. 728 | * @param {string} [target] Window (`_blank|_self|_parent|_top`) or named frame to open links in. 729 | * @param {object|function(url)} [attributes] Add custom attributes to the link element. 730 | * 731 | * Can be one of: 732 | * 733 | * - `object`: A map of attributes 734 | * - `function`: Takes the url as a parameter and returns a map of attributes 735 | * 736 | * If the map of attributes contains a value for `target`, it overrides the value of 737 | * the target parameter. 738 | * 739 | * 740 | * @returns {string} Html-linkified and {@link $sanitize sanitized} text. 741 | * 742 | * @usage 743 | 744 | * 745 | * @example 746 | 747 | 748 |

749 | Snippet: 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 761 | 764 | 765 | 766 | 767 | 770 | 773 | 774 | 775 | 776 | 779 | 782 | 783 | 784 | 785 | 786 | 787 | 788 |
FilterSourceRendered
linky filter 759 |
<div ng-bind-html="snippet | linky">
</div>
760 |
762 |
763 |
linky target 768 |
<div ng-bind-html="snippetWithSingleURL | linky:'_blank'">
</div>
769 |
771 |
772 |
linky custom attributes 777 |
<div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}">
</div>
778 |
780 |
781 |
no filter
<div ng-bind="snippet">
</div>
789 | 790 | 791 | angular.module('linkyExample', ['ngSanitize']) 792 | .controller('ExampleController', ['$scope', function($scope) { 793 | $scope.snippet = 794 | 'Pretty text with some links:\n' + 795 | 'http://angularjs.org/,\n' + 796 | 'mailto:us@somewhere.org,\n' + 797 | 'another@somewhere.org,\n' + 798 | 'and one more: ftp://127.0.0.1/.'; 799 | $scope.snippetWithSingleURL = 'http://angularjs.org/'; 800 | }]); 801 | 802 | 803 | it('should linkify the snippet with urls', function() { 804 | expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). 805 | toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' + 806 | 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); 807 | expect(element.all(by.css('#linky-filter a')).count()).toEqual(4); 808 | }); 809 | 810 | it('should not linkify snippet without the linky filter', function() { 811 | expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()). 812 | toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' + 813 | 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); 814 | expect(element.all(by.css('#escaped-html a')).count()).toEqual(0); 815 | }); 816 | 817 | it('should update', function() { 818 | element(by.model('snippet')).clear(); 819 | element(by.model('snippet')).sendKeys('new http://link.'); 820 | expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). 821 | toBe('new http://link.'); 822 | expect(element.all(by.css('#linky-filter a')).count()).toEqual(1); 823 | expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()) 824 | .toBe('new http://link.'); 825 | }); 826 | 827 | it('should work with the target property', function() { 828 | expect(element(by.id('linky-target')). 829 | element(by.binding("snippetWithSingleURL | linky:'_blank'")).getText()). 830 | toBe('http://angularjs.org/'); 831 | expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank'); 832 | }); 833 | 834 | it('should optionally add custom attributes', function() { 835 | expect(element(by.id('linky-custom-attributes')). 836 | element(by.binding("snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}")).getText()). 837 | toBe('http://angularjs.org/'); 838 | expect(element(by.css('#linky-custom-attributes a')).getAttribute('rel')).toEqual('nofollow'); 839 | }); 840 | 841 | 842 | */ 843 | angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { 844 | var LINKY_URL_REGEXP = 845 | /((s?ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i, 846 | MAILTO_REGEXP = /^mailto:/i; 847 | 848 | var linkyMinErr = angular.$$minErr('linky'); 849 | var isDefined = angular.isDefined; 850 | var isFunction = angular.isFunction; 851 | var isObject = angular.isObject; 852 | var isString = angular.isString; 853 | 854 | return function(text, target, attributes) { 855 | if (text == null || text === '') return text; 856 | if (!isString(text)) throw linkyMinErr('notstring', 'Expected string but received: {0}', text); 857 | 858 | var attributesFn = 859 | isFunction(attributes) ? attributes : 860 | isObject(attributes) ? function getAttributesObject() {return attributes;} : 861 | function getEmptyAttributesObject() {return {};}; 862 | 863 | var match; 864 | var raw = text; 865 | var html = []; 866 | var url; 867 | var i; 868 | while ((match = raw.match(LINKY_URL_REGEXP))) { 869 | // We can not end in these as they are sometimes found at the end of the sentence 870 | url = match[0]; 871 | // if we did not match ftp/http/www/mailto then assume mailto 872 | if (!match[2] && !match[4]) { 873 | url = (match[3] ? 'http://' : 'mailto:') + url; 874 | } 875 | i = match.index; 876 | addText(raw.substr(0, i)); 877 | addLink(url, match[0].replace(MAILTO_REGEXP, '')); 878 | raw = raw.substring(i + match[0].length); 879 | } 880 | addText(raw); 881 | return $sanitize(html.join('')); 882 | 883 | function addText(text) { 884 | if (!text) { 885 | return; 886 | } 887 | html.push(sanitizeText(text)); 888 | } 889 | 890 | function addLink(url, text) { 891 | var key, linkAttributes = attributesFn(url); 892 | html.push(''); 906 | addText(text); 907 | html.push(''); 908 | } 909 | }; 910 | }]); 911 | 912 | 913 | })(window, window.angular); 914 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/angular-touch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.8.0 3 | * (c) 2010-2020 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular) {'use strict'; 7 | 8 | /** 9 | * @ngdoc module 10 | * @name ngTouch 11 | * @description 12 | * 13 | * The `ngTouch` module provides helpers for touch-enabled devices. 14 | * The implementation is based on jQuery Mobile touch event handling 15 | * ([jquerymobile.com](http://jquerymobile.com/)). * 16 | * 17 | * See {@link ngTouch.$swipe `$swipe`} for usage. 18 | * 19 | * @deprecated 20 | * sinceVersion="1.7.0" 21 | * The ngTouch module with the {@link ngTouch.$swipe `$swipe`} service and 22 | * the {@link ngTouch.ngSwipeLeft} and {@link ngTouch.ngSwipeRight} directives are 23 | * deprecated. Instead, stand-alone libraries for touch handling and gesture interaction 24 | * should be used, for example [HammerJS](https://hammerjs.github.io/) (which is also used by 25 | * Angular). 26 | */ 27 | 28 | // define ngTouch module 29 | /* global ngTouch */ 30 | var ngTouch = angular.module('ngTouch', []); 31 | 32 | ngTouch.info({ angularVersion: '1.8.0' }); 33 | 34 | function nodeName_(element) { 35 | return angular.$$lowercase(element.nodeName || (element[0] && element[0].nodeName)); 36 | } 37 | 38 | /* global ngTouch: false */ 39 | 40 | /** 41 | * @ngdoc service 42 | * @name $swipe 43 | * 44 | * @deprecated 45 | * sinceVersion="1.7.0" 46 | * 47 | * See the {@link ngTouch module} documentation for more information. 48 | * 49 | * @description 50 | * The `$swipe` service is a service that abstracts the messier details of hold-and-drag swipe 51 | * behavior, to make implementing swipe-related directives more convenient. 52 | * 53 | * Requires the {@link ngTouch `ngTouch`} module to be installed. 54 | * 55 | * `$swipe` is used by the `ngSwipeLeft` and `ngSwipeRight` directives in `ngTouch`. 56 | * 57 | * # Usage 58 | * The `$swipe` service is an object with a single method: `bind`. `bind` takes an element 59 | * which is to be watched for swipes, and an object with four handler functions. See the 60 | * documentation for `bind` below. 61 | */ 62 | 63 | ngTouch.factory('$swipe', [function() { 64 | // The total distance in any direction before we make the call on swipe vs. scroll. 65 | var MOVE_BUFFER_RADIUS = 10; 66 | 67 | var POINTER_EVENTS = { 68 | 'mouse': { 69 | start: 'mousedown', 70 | move: 'mousemove', 71 | end: 'mouseup' 72 | }, 73 | 'touch': { 74 | start: 'touchstart', 75 | move: 'touchmove', 76 | end: 'touchend', 77 | cancel: 'touchcancel' 78 | }, 79 | 'pointer': { 80 | start: 'pointerdown', 81 | move: 'pointermove', 82 | end: 'pointerup', 83 | cancel: 'pointercancel' 84 | } 85 | }; 86 | 87 | function getCoordinates(event) { 88 | var originalEvent = event.originalEvent || event; 89 | var touches = originalEvent.touches && originalEvent.touches.length ? originalEvent.touches : [originalEvent]; 90 | var e = (originalEvent.changedTouches && originalEvent.changedTouches[0]) || touches[0]; 91 | 92 | return { 93 | x: e.clientX, 94 | y: e.clientY 95 | }; 96 | } 97 | 98 | function getEvents(pointerTypes, eventType) { 99 | var res = []; 100 | angular.forEach(pointerTypes, function(pointerType) { 101 | var eventName = POINTER_EVENTS[pointerType][eventType]; 102 | if (eventName) { 103 | res.push(eventName); 104 | } 105 | }); 106 | return res.join(' '); 107 | } 108 | 109 | return { 110 | /** 111 | * @ngdoc method 112 | * @name $swipe#bind 113 | * 114 | * @description 115 | * The main method of `$swipe`. It takes an element to be watched for swipe motions, and an 116 | * object containing event handlers. 117 | * The pointer types that should be used can be specified via the optional 118 | * third argument, which is an array of strings `'mouse'`, `'touch'` and `'pointer'`. By default, 119 | * `$swipe` will listen for `mouse`, `touch` and `pointer` events. 120 | * 121 | * The four events are `start`, `move`, `end`, and `cancel`. `start`, `move`, and `end` 122 | * receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }` and the raw 123 | * `event`. `cancel` receives the raw `event` as its single parameter. 124 | * 125 | * `start` is called on either `mousedown`, `touchstart` or `pointerdown`. After this event, `$swipe` is 126 | * watching for `touchmove`, `mousemove` or `pointermove` events. These events are ignored until the total 127 | * distance moved in either dimension exceeds a small threshold. 128 | * 129 | * Once this threshold is exceeded, either the horizontal or vertical delta is greater. 130 | * - If the horizontal distance is greater, this is a swipe and `move` and `end` events follow. 131 | * - If the vertical distance is greater, this is a scroll, and we let the browser take over. 132 | * A `cancel` event is sent. 133 | * 134 | * `move` is called on `mousemove`, `touchmove` and `pointermove` after the above logic has determined that 135 | * a swipe is in progress. 136 | * 137 | * `end` is called when a swipe is successfully completed with a `touchend`, `mouseup` or `pointerup`. 138 | * 139 | * `cancel` is called either on a `touchcancel` or `pointercancel` from the browser, or when we begin scrolling 140 | * as described above. 141 | * 142 | */ 143 | bind: function(element, eventHandlers, pointerTypes) { 144 | // Absolute total movement, used to control swipe vs. scroll. 145 | var totalX, totalY; 146 | // Coordinates of the start position. 147 | var startCoords; 148 | // Last event's position. 149 | var lastPos; 150 | // Whether a swipe is active. 151 | var active = false; 152 | 153 | pointerTypes = pointerTypes || ['mouse', 'touch', 'pointer']; 154 | element.on(getEvents(pointerTypes, 'start'), function(event) { 155 | startCoords = getCoordinates(event); 156 | active = true; 157 | totalX = 0; 158 | totalY = 0; 159 | lastPos = startCoords; 160 | if (eventHandlers['start']) { 161 | eventHandlers['start'](startCoords, event); 162 | } 163 | }); 164 | var events = getEvents(pointerTypes, 'cancel'); 165 | if (events) { 166 | element.on(events, function(event) { 167 | active = false; 168 | if (eventHandlers['cancel']) { 169 | eventHandlers['cancel'](event); 170 | } 171 | }); 172 | } 173 | 174 | element.on(getEvents(pointerTypes, 'move'), function(event) { 175 | if (!active) return; 176 | 177 | // Android will send a touchcancel if it thinks we're starting to scroll. 178 | // So when the total distance (+ or - or both) exceeds 10px in either direction, 179 | // we either: 180 | // - On totalX > totalY, we send preventDefault() and treat this as a swipe. 181 | // - On totalY > totalX, we let the browser handle it as a scroll. 182 | 183 | if (!startCoords) return; 184 | var coords = getCoordinates(event); 185 | 186 | totalX += Math.abs(coords.x - lastPos.x); 187 | totalY += Math.abs(coords.y - lastPos.y); 188 | 189 | lastPos = coords; 190 | 191 | if (totalX < MOVE_BUFFER_RADIUS && totalY < MOVE_BUFFER_RADIUS) { 192 | return; 193 | } 194 | 195 | // One of totalX or totalY has exceeded the buffer, so decide on swipe vs. scroll. 196 | if (totalY > totalX) { 197 | // Allow native scrolling to take over. 198 | active = false; 199 | if (eventHandlers['cancel']) { 200 | eventHandlers['cancel'](event); 201 | } 202 | return; 203 | } else { 204 | // Prevent the browser from scrolling. 205 | event.preventDefault(); 206 | if (eventHandlers['move']) { 207 | eventHandlers['move'](coords, event); 208 | } 209 | } 210 | }); 211 | 212 | element.on(getEvents(pointerTypes, 'end'), function(event) { 213 | if (!active) return; 214 | active = false; 215 | if (eventHandlers['end']) { 216 | eventHandlers['end'](getCoordinates(event), event); 217 | } 218 | }); 219 | } 220 | }; 221 | }]); 222 | 223 | /* global ngTouch: false */ 224 | 225 | /** 226 | * @ngdoc directive 227 | * @name ngSwipeLeft 228 | * 229 | * @deprecated 230 | * sinceVersion="1.7.0" 231 | * 232 | * See the {@link ngTouch module} documentation for more information. 233 | * 234 | * @description 235 | * Specify custom behavior when an element is swiped to the left on a touchscreen device. 236 | * A leftward swipe is a quick, right-to-left slide of the finger. 237 | * Though ngSwipeLeft is designed for touch-based devices, it will work with a mouse click and drag 238 | * too. 239 | * 240 | * To disable the mouse click and drag functionality, add `ng-swipe-disable-mouse` to 241 | * the `ng-swipe-left` or `ng-swipe-right` DOM Element. 242 | * 243 | * Requires the {@link ngTouch `ngTouch`} module to be installed. 244 | * 245 | * @element ANY 246 | * @param {expression} ngSwipeLeft {@link guide/expression Expression} to evaluate 247 | * upon left swipe. (Event object is available as `$event`) 248 | * 249 | * @example 250 | 251 | 252 |
253 | Some list content, like an email in the inbox 254 |
255 |
256 | 257 | 258 |
259 |
260 | 261 | angular.module('ngSwipeLeftExample', ['ngTouch']); 262 | 263 |
264 | */ 265 | 266 | /** 267 | * @ngdoc directive 268 | * @name ngSwipeRight 269 | * 270 | * @deprecated 271 | * sinceVersion="1.7.0" 272 | * 273 | * See the {@link ngTouch module} documentation for more information. 274 | * 275 | * @description 276 | * Specify custom behavior when an element is swiped to the right on a touchscreen device. 277 | * A rightward swipe is a quick, left-to-right slide of the finger. 278 | * Though ngSwipeRight is designed for touch-based devices, it will work with a mouse click and drag 279 | * too. 280 | * 281 | * Requires the {@link ngTouch `ngTouch`} module to be installed. 282 | * 283 | * @element ANY 284 | * @param {expression} ngSwipeRight {@link guide/expression Expression} to evaluate 285 | * upon right swipe. (Event object is available as `$event`) 286 | * 287 | * @example 288 | 289 | 290 |
291 | Some list content, like an email in the inbox 292 |
293 |
294 | 295 | 296 |
297 |
298 | 299 | angular.module('ngSwipeRightExample', ['ngTouch']); 300 | 301 |
302 | */ 303 | 304 | function makeSwipeDirective(directiveName, direction, eventName) { 305 | ngTouch.directive(directiveName, ['$parse', '$swipe', function($parse, $swipe) { 306 | // The maximum vertical delta for a swipe should be less than 75px. 307 | var MAX_VERTICAL_DISTANCE = 75; 308 | // Vertical distance should not be more than a fraction of the horizontal distance. 309 | var MAX_VERTICAL_RATIO = 0.3; 310 | // At least a 30px lateral motion is necessary for a swipe. 311 | var MIN_HORIZONTAL_DISTANCE = 30; 312 | 313 | return function(scope, element, attr) { 314 | var swipeHandler = $parse(attr[directiveName]); 315 | 316 | var startCoords, valid; 317 | 318 | function validSwipe(coords) { 319 | // Check that it's within the coordinates. 320 | // Absolute vertical distance must be within tolerances. 321 | // Horizontal distance, we take the current X - the starting X. 322 | // This is negative for leftward swipes and positive for rightward swipes. 323 | // After multiplying by the direction (-1 for left, +1 for right), legal swipes 324 | // (ie. same direction as the directive wants) will have a positive delta and 325 | // illegal ones a negative delta. 326 | // Therefore this delta must be positive, and larger than the minimum. 327 | if (!startCoords) return false; 328 | var deltaY = Math.abs(coords.y - startCoords.y); 329 | var deltaX = (coords.x - startCoords.x) * direction; 330 | return valid && // Short circuit for already-invalidated swipes. 331 | deltaY < MAX_VERTICAL_DISTANCE && 332 | deltaX > 0 && 333 | deltaX > MIN_HORIZONTAL_DISTANCE && 334 | deltaY / deltaX < MAX_VERTICAL_RATIO; 335 | } 336 | 337 | var pointerTypes = ['touch']; 338 | if (!angular.isDefined(attr['ngSwipeDisableMouse'])) { 339 | pointerTypes.push('mouse'); 340 | } 341 | $swipe.bind(element, { 342 | 'start': function(coords, event) { 343 | startCoords = coords; 344 | valid = true; 345 | }, 346 | 'cancel': function(event) { 347 | valid = false; 348 | }, 349 | 'end': function(coords, event) { 350 | if (validSwipe(coords)) { 351 | scope.$apply(function() { 352 | element.triggerHandler(eventName); 353 | swipeHandler(scope, {$event: event}); 354 | }); 355 | } 356 | } 357 | }, pointerTypes); 358 | }; 359 | }]); 360 | } 361 | 362 | // Left is negative X-coordinate, right is positive. 363 | makeSwipeDirective('ngSwipeLeft', -1, 'swipeleft'); 364 | makeSwipeDirective('ngSwipeRight', 1, 'swiperight'); 365 | 366 | 367 | 368 | })(window, window.angular); 369 | --------------------------------------------------------------------------------