kms 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/javascripts/kms/application.js +1 -0
- data/app/assets/javascripts/kms/application/controllers/assets_controller.coffee.erb +14 -4
- data/app/assets/javascripts/kms/application/controllers/pages_controller.coffee.erb +12 -2
- data/app/assets/javascripts/kms/application/controllers/snippets_controller.coffee.erb +13 -3
- data/app/assets/javascripts/kms/application/controllers/templates_controller.coffee.erb +13 -3
- data/app/assets/javascripts/kms/application/controllers/users_controller.coffee +5 -5
- data/app/assets/javascripts/kms/application/module.coffee +6 -2
- data/app/assets/javascripts/kms/application/routes.coffee.erb +10 -0
- data/app/assets/javascripts/templates/assets/edit.html.slim +2 -1
- data/app/assets/javascripts/templates/assets/form.html.slim +1 -1
- data/app/assets/javascripts/templates/pages/edit.html.slim +1 -0
- data/app/assets/javascripts/templates/shared/hotkey_notification.html.slim +6 -0
- data/app/assets/javascripts/templates/snippets/edit.html.slim +1 -0
- data/app/assets/javascripts/templates/templates/edit.html.slim +1 -0
- data/app/assets/javascripts/templates/users/edit.html.slim +5 -0
- data/app/assets/javascripts/templates/users/form.html.slim +3 -2
- data/app/assets/javascripts/templates/users/index.html.slim +2 -1
- data/app/assets/stylesheets/kms/custom.css.scss +10 -0
- data/app/controllers/kms/assets_controller.rb +6 -3
- data/app/controllers/kms/users_controller.rb +14 -0
- data/app/services/kms/resource_service.rb +3 -1
- data/app/views/layouts/kms/kms.html.erb +1 -1
- data/config/initializers/devise.rb +9 -0
- data/config/locales/en.yml +12 -0
- data/config/locales/ru.yml +12 -0
- data/config/routes.rb +1 -1
- data/lib/kms/engine.rb +1 -1
- data/lib/kms/version.rb +1 -1
- data/spec/controllers/kms/assets_controller_spec.rb +28 -10
- data/spec/controllers/kms/users_controller_spec.rb +23 -0
- data/spec/internal/config/routes.rb +1 -1
- data/spec/internal/log/test.log +0 -105823
- data/vendor/assets/bower.json +5 -4
- data/vendor/assets/bower_components/angular-cookies/angular-cookies.js +22 -18
- data/vendor/assets/bower_components/angular-cookies/angular-cookies.min.js +4 -4
- data/vendor/assets/bower_components/angular-cookies/angular-cookies.min.js.map +2 -2
- data/vendor/assets/bower_components/angular-cookies/bower.json +2 -2
- data/vendor/assets/bower_components/angular-cookies/package.json +1 -1
- data/vendor/assets/bower_components/angular-hotkeys/Gruntfile.js +118 -0
- data/vendor/assets/bower_components/angular-hotkeys/LICENSE +20 -0
- data/vendor/assets/bower_components/angular-hotkeys/README.md +248 -0
- data/vendor/assets/bower_components/angular-hotkeys/bower.json +19 -0
- data/vendor/assets/bower_components/angular-hotkeys/build/hotkeys.css +110 -0
- data/vendor/assets/bower_components/angular-hotkeys/build/hotkeys.js +1661 -0
- data/vendor/assets/bower_components/angular-hotkeys/build/hotkeys.min.css +1 -0
- data/vendor/assets/bower_components/angular-hotkeys/build/hotkeys.min.js +7 -0
- data/vendor/assets/bower_components/angular-hotkeys/package.json +45 -0
- data/vendor/assets/bower_components/angular-hotkeys/src/hotkeys.css +104 -0
- data/vendor/assets/bower_components/angular-hotkeys/src/hotkeys.js +633 -0
- data/vendor/assets/bower_components/angular-loading-bar/CHANGELOG.md +33 -0
- data/vendor/assets/bower_components/angular-loading-bar/CONTRIBUTING.md +17 -0
- data/vendor/assets/bower_components/angular-loading-bar/Gruntfile.js +9 -1
- data/vendor/assets/bower_components/angular-loading-bar/ISSUE_TEMPLATE.md +14 -0
- data/vendor/assets/bower_components/angular-loading-bar/PULL_REQUEST_TEMPLATE.md +13 -0
- data/vendor/assets/bower_components/angular-loading-bar/README.md +30 -3
- data/vendor/assets/bower_components/angular-loading-bar/bower.json +11 -6
- data/vendor/assets/bower_components/angular-loading-bar/build/loading-bar.css +5 -5
- data/vendor/assets/bower_components/angular-loading-bar/build/loading-bar.js +39 -12
- data/vendor/assets/bower_components/angular-loading-bar/build/loading-bar.min.css +1 -8
- data/vendor/assets/bower_components/angular-loading-bar/build/loading-bar.min.js +3 -3
- data/vendor/assets/bower_components/angular-loading-bar/index.js +2 -0
- data/vendor/assets/bower_components/angular-loading-bar/package.json +12 -15
- data/vendor/assets/bower_components/angular-loading-bar/src/loading-bar.css +3 -3
- data/vendor/assets/bower_components/angular-loading-bar/src/loading-bar.js +37 -10
- data/vendor/assets/bower_components/angular-sanitize/angular-sanitize.js +504 -386
- data/vendor/assets/bower_components/angular-sanitize/angular-sanitize.min.js +13 -12
- data/vendor/assets/bower_components/angular-sanitize/angular-sanitize.min.js.map +3 -3
- data/vendor/assets/bower_components/angular-sanitize/bower.json +2 -2
- data/vendor/assets/bower_components/angular-sanitize/package.json +1 -1
- data/vendor/assets/bower_components/angular-ui-router/CHANGELOG.md +1410 -0
- data/vendor/assets/bower_components/angular-ui-router/CONTRIBUTING.md +64 -16
- data/vendor/assets/bower_components/angular-ui-router/DOCS.md +48 -0
- data/vendor/assets/bower_components/angular-ui-router/ISSUE_TEMPLATE.md +53 -0
- data/vendor/assets/bower_components/angular-ui-router/LICENSE +1 -1
- data/vendor/assets/bower_components/angular-ui-router/README.md +24 -211
- data/vendor/assets/bower_components/angular-ui-router/artifacts.json +8 -0
- data/vendor/assets/bower_components/angular-ui-router/bower.json +1 -23
- data/vendor/assets/bower_components/angular-ui-router/karma.conf.js +105 -0
- data/vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.js +9744 -3901
- data/vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.js.map +192 -0
- data/vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.min.js +9 -4
- data/vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.min.js.map +1679 -0
- data/vendor/assets/bower_components/angular-ui-router/release/resolveService.js +83 -0
- data/vendor/assets/bower_components/angular-ui-router/release/resolveService.js.map +19 -0
- data/vendor/assets/bower_components/angular-ui-router/release/resolveService.min.js +8 -0
- data/vendor/assets/bower_components/angular-ui-router/release/resolveService.min.js.map +47 -0
- data/vendor/assets/bower_components/angular-ui-router/release/stateEvents.js +294 -0
- data/vendor/assets/bower_components/angular-ui-router/release/stateEvents.js.map +17 -0
- data/vendor/assets/bower_components/angular-ui-router/release/stateEvents.min.js +8 -0
- data/vendor/assets/bower_components/angular-ui-router/release/stateEvents.min.js.map +102 -0
- data/vendor/assets/bower_components/angular-ui-router/release/ui-router-angularjs.js +2014 -0
- data/vendor/assets/bower_components/angular-ui-router/release/ui-router-angularjs.js.map +70 -0
- data/vendor/assets/bower_components/angular-ui-router/release/ui-router-angularjs.min.js +9 -0
- data/vendor/assets/bower_components/angular-ui-router/release/ui-router-angularjs.min.js.map +541 -0
- data/vendor/assets/bower_components/angular-ui-router/rollup.config.js +116 -0
- data/vendor/assets/bower_components/angular-ui-router/tslint.json +60 -0
- data/vendor/assets/bower_components/angular-ui-router/yarn.lock +4146 -0
- data/vendor/assets/bower_components/angular-ui-tree/yarn.lock +4945 -0
- data/vendor/assets/bower_components/angular/angular.js +4019 -2449
- data/vendor/assets/bower_components/angular/angular.min.js +331 -319
- data/vendor/assets/bower_components/angular/angular.min.js.gzip +0 -0
- data/vendor/assets/bower_components/angular/angular.min.js.map +3 -3
- data/vendor/assets/bower_components/angular/bower.json +1 -1
- data/vendor/assets/bower_components/angular/package.json +1 -1
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/LICENSE +21 -0
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/README.md +14 -14
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/bower.json +25 -12
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/development_index.html +59 -52
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/dist/angularjs-dropdown-multiselect.min.js +1 -1
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/index.html +73 -0
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/package.json +19 -7
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/pages/javascripts/pages/home/ExampleCtrl.js +126 -3
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/pages/javascripts/pages/home/home.html +1262 -852
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/pages/stylesheets/stylesheet.css +10 -5
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/src/angularjs-dropdown-multiselect.js +612 -287
- metadata +66 -169
- data/spec/internal/config/database.yml +0 -7
- data/spec/internal/public/uploads/kms/asset/file/1/avatar.jpg +0 -0
- data/spec/internal/public/uploads/kms/asset/file/2/avatar.jpg +0 -0
- data/spec/internal/public/uploads/kms/asset/file/2/style.css +0 -1
- data/spec/internal/public/uploads/kms/asset/file/3/style.css +0 -1
- data/spec/internal/public/uploads/kms/asset/file/4/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500976987-41025-0002-0883/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977082-41195-0002-6495/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977109-41364-0002-4518/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977152-41405-0002-2345/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977327-41694-0002-5448/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977376-41732-0002-7916/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977392-41759-0002-7593/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977410-42259-0002-7527/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977429-42306-0002-5937/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500977437-42324-0002-5880/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500983228-53594-0002-4559/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500983284-53632-0002-6590/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500983360-53784-0002-7289/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500983469-54321-0002-0386/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500983469-54321-0004-5691/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500983511-54352-0002-5720/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500983511-54352-0004-1399/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500983610-54507-0002-4280/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500983610-54507-0004-9758/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500984466-57012-0002-4146/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500984466-57012-0004-5895/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500984509-57158-0002-9657/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500984509-57158-0004-5003/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500984616-57697-0002-7201/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500984616-57697-0004-6255/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500985257-58947-0002-3629/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500985257-58947-0004-5338/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500985407-58947-0006-5929/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500985473-59264-0002-0397/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500985473-59264-0004-6493/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500985475-59264-0007-8674/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500985538-59468-0002-9206/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500985538-59468-0004-2586/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500985538-59468-0007-6200/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988358-65877-0002-4528/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500988358-65877-0004-5904/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988358-65877-0007-7320/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988407-65916-0002-3138/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500988407-65916-0004-5400/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988407-65916-0007-1655/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988421-65950-0002-9415/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500988421-65950-0004-7130/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988421-65950-0007-9886/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988435-65981-0002-3228/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500988435-65981-0004-3682/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988435-65981-0007-1582/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988475-66122-0002-9516/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500988475-66122-0004-5634/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988530-66122-0007-2272/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988554-66315-0002-6262/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500988554-66315-0004-6099/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500988554-66315-0007-1632/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500991751-73722-0002-9937/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1500991751-73722-0004-8034/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1500991751-73722-0007-7763/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1501233238-34385-0002-3210/avatar.jpg +0 -0
- data/spec/internal/public/uploads/tmp/1501233238-34385-0004-5881/style.css +0 -1
- data/spec/internal/public/uploads/tmp/1501233238-34385-0007-6280/style.css +0 -1
- data/spec/internal/tmp/cache/assets/test/sprockets/v3.0/1XyAFYlYI0pK7WAgjR4PgXV6BgU6huJSviWmHetdCRs.cache +0 -1
- data/vendor/assets/bower_components/angular-ui-router/api/angular-ui-router.d.ts +0 -126
- data/vendor/assets/bower_components/angular-ui-router/src/common.js +0 -292
- data/vendor/assets/bower_components/angular-ui-router/src/resolve.js +0 -252
- data/vendor/assets/bower_components/angular-ui-router/src/state.js +0 -1373
- data/vendor/assets/bower_components/angular-ui-router/src/stateDirectives.js +0 -268
- data/vendor/assets/bower_components/angular-ui-router/src/stateFilters.js +0 -39
- data/vendor/assets/bower_components/angular-ui-router/src/templateFactory.js +0 -110
- data/vendor/assets/bower_components/angular-ui-router/src/urlMatcherFactory.js +0 -1036
- data/vendor/assets/bower_components/angular-ui-router/src/urlRouter.js +0 -413
- data/vendor/assets/bower_components/angular-ui-router/src/view.js +0 -71
- data/vendor/assets/bower_components/angular-ui-router/src/viewDirective.js +0 -302
- data/vendor/assets/bower_components/angular-ui-router/src/viewScroll.js +0 -52
- data/vendor/assets/bower_components/angularjs-dropdown-multiselect/pages/index.html +0 -67
- data/vendor/assets/bower_components/bootstrap/Gemfile.lock +0 -43
@@ -26,7 +26,7 @@ angular.module('chieffancypants.loadingBar', ['cfp.loadingBarInterceptor']);
|
|
26
26
|
angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar'])
|
27
27
|
.config(['$httpProvider', function ($httpProvider) {
|
28
28
|
|
29
|
-
var interceptor = ['$q', '$cacheFactory', '$timeout', '$rootScope', 'cfpLoadingBar', function ($q, $cacheFactory, $timeout, $rootScope, cfpLoadingBar) {
|
29
|
+
var interceptor = ['$q', '$cacheFactory', '$timeout', '$rootScope', '$log', 'cfpLoadingBar', function ($q, $cacheFactory, $timeout, $rootScope, $log, cfpLoadingBar) {
|
30
30
|
|
31
31
|
/**
|
32
32
|
* The total number of requests made
|
@@ -107,9 +107,14 @@ angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar'])
|
|
107
107
|
},
|
108
108
|
|
109
109
|
'response': function(response) {
|
110
|
+
if (!response || !response.config) {
|
111
|
+
$log.error('Broken interceptor detected: Config object not supplied in response:\n https://github.com/chieffancypants/angular-loading-bar/pull/50');
|
112
|
+
return response;
|
113
|
+
}
|
114
|
+
|
110
115
|
if (!response.config.ignoreLoadingBar && !isCached(response.config)) {
|
111
116
|
reqsCompleted++;
|
112
|
-
$rootScope.$broadcast('cfpLoadingBar:loaded', {url: response.config.url});
|
117
|
+
$rootScope.$broadcast('cfpLoadingBar:loaded', {url: response.config.url, result: response});
|
113
118
|
if (reqsCompleted >= reqsTotal) {
|
114
119
|
setComplete();
|
115
120
|
} else {
|
@@ -120,9 +125,14 @@ angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar'])
|
|
120
125
|
},
|
121
126
|
|
122
127
|
'responseError': function(rejection) {
|
128
|
+
if (!rejection || !rejection.config) {
|
129
|
+
$log.error('Broken interceptor detected: Config object not supplied in rejection:\n https://github.com/chieffancypants/angular-loading-bar/pull/50');
|
130
|
+
return $q.reject(rejection);
|
131
|
+
}
|
132
|
+
|
123
133
|
if (!rejection.config.ignoreLoadingBar && !isCached(rejection.config)) {
|
124
134
|
reqsCompleted++;
|
125
|
-
$rootScope.$broadcast('cfpLoadingBar:loaded', {url: rejection.config.url});
|
135
|
+
$rootScope.$broadcast('cfpLoadingBar:loaded', {url: rejection.config.url, result: rejection});
|
126
136
|
if (reqsCompleted >= reqsTotal) {
|
127
137
|
setComplete();
|
128
138
|
} else {
|
@@ -150,6 +160,7 @@ angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar'])
|
|
150
160
|
angular.module('cfp.loadingBar', [])
|
151
161
|
.provider('cfpLoadingBar', function() {
|
152
162
|
|
163
|
+
this.autoIncrement = true;
|
153
164
|
this.includeSpinner = true;
|
154
165
|
this.includeBar = true;
|
155
166
|
this.latencyThreshold = 100;
|
@@ -170,6 +181,7 @@ angular.module('cfp.loadingBar', [])
|
|
170
181
|
started = false,
|
171
182
|
status = 0;
|
172
183
|
|
184
|
+
var autoIncrement = this.autoIncrement;
|
173
185
|
var includeSpinner = this.includeSpinner;
|
174
186
|
var includeBar = this.includeBar;
|
175
187
|
var startSize = this.startSize;
|
@@ -182,7 +194,6 @@ angular.module('cfp.loadingBar', [])
|
|
182
194
|
$animate = $injector.get('$animate');
|
183
195
|
}
|
184
196
|
|
185
|
-
var $parent = $document.find($parentSelector).eq(0);
|
186
197
|
$timeout.cancel(completeTimeout);
|
187
198
|
|
188
199
|
// do not continually broadcast the started event:
|
@@ -190,15 +201,28 @@ angular.module('cfp.loadingBar', [])
|
|
190
201
|
return;
|
191
202
|
}
|
192
203
|
|
204
|
+
var document = $document[0];
|
205
|
+
var parent = document.querySelector ?
|
206
|
+
document.querySelector($parentSelector)
|
207
|
+
: $document.find($parentSelector)[0]
|
208
|
+
;
|
209
|
+
|
210
|
+
if (! parent) {
|
211
|
+
parent = document.getElementsByTagName('body')[0];
|
212
|
+
}
|
213
|
+
|
214
|
+
var $parent = angular.element(parent);
|
215
|
+
var $after = parent.lastChild && angular.element(parent.lastChild);
|
216
|
+
|
193
217
|
$rootScope.$broadcast('cfpLoadingBar:started');
|
194
218
|
started = true;
|
195
219
|
|
196
220
|
if (includeBar) {
|
197
|
-
$animate.enter(loadingBarContainer, $parent);
|
221
|
+
$animate.enter(loadingBarContainer, $parent, $after);
|
198
222
|
}
|
199
223
|
|
200
224
|
if (includeSpinner) {
|
201
|
-
$animate.enter(spinner, $parent);
|
225
|
+
$animate.enter(spinner, $parent, loadingBarContainer);
|
202
226
|
}
|
203
227
|
|
204
228
|
_set(startSize);
|
@@ -220,10 +244,12 @@ angular.module('cfp.loadingBar', [])
|
|
220
244
|
// increment loadingbar to give the illusion that there is always
|
221
245
|
// progress but make sure to cancel the previous timeouts so we don't
|
222
246
|
// have multiple incs running at the same time.
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
247
|
+
if (autoIncrement) {
|
248
|
+
$timeout.cancel(incTimeout);
|
249
|
+
incTimeout = $timeout(function() {
|
250
|
+
_inc();
|
251
|
+
}, 250);
|
252
|
+
}
|
227
253
|
}
|
228
254
|
|
229
255
|
/**
|
@@ -296,6 +322,7 @@ angular.module('cfp.loadingBar', [])
|
|
296
322
|
status : _status,
|
297
323
|
inc : _inc,
|
298
324
|
complete : _complete,
|
325
|
+
autoIncrement : this.autoIncrement,
|
299
326
|
includeSpinner : this.includeSpinner,
|
300
327
|
latencyThreshold : this.latencyThreshold,
|
301
328
|
parentSelector : this.parentSelector,
|
@@ -1,9 +1,9 @@
|
|
1
1
|
/**
|
2
|
-
* @license AngularJS v1.
|
3
|
-
* (c) 2010-
|
2
|
+
* @license AngularJS v1.6.7
|
3
|
+
* (c) 2010-2017 Google, Inc. http://angularjs.org
|
4
4
|
* License: MIT
|
5
5
|
*/
|
6
|
-
(function(window, angular
|
6
|
+
(function(window, angular) {'use strict';
|
7
7
|
|
8
8
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
9
9
|
* Any commits to this file should be reviewed with security in mind. *
|
@@ -17,58 +17,49 @@
|
|
17
17
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
18
18
|
|
19
19
|
var $sanitizeMinErr = angular.$$minErr('$sanitize');
|
20
|
+
var bind;
|
21
|
+
var extend;
|
22
|
+
var forEach;
|
23
|
+
var isDefined;
|
24
|
+
var lowercase;
|
25
|
+
var noop;
|
26
|
+
var nodeContains;
|
27
|
+
var htmlParser;
|
28
|
+
var htmlSanitizeWriter;
|
20
29
|
|
21
30
|
/**
|
22
31
|
* @ngdoc module
|
23
32
|
* @name ngSanitize
|
24
33
|
* @description
|
25
34
|
*
|
26
|
-
* # ngSanitize
|
27
|
-
*
|
28
35
|
* The `ngSanitize` module provides functionality to sanitize HTML.
|
29
36
|
*
|
30
|
-
*
|
31
|
-
* <div doc-module-components="ngSanitize"></div>
|
32
|
-
*
|
33
37
|
* See {@link ngSanitize.$sanitize `$sanitize`} for usage.
|
34
38
|
*/
|
35
39
|
|
36
|
-
/*
|
37
|
-
* HTML Parser By Misko Hevery (misko@hevery.com)
|
38
|
-
* based on: HTML Parser By John Resig (ejohn.org)
|
39
|
-
* Original code by Erik Arvidsson, Mozilla Public License
|
40
|
-
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
41
|
-
*
|
42
|
-
* // Use like so:
|
43
|
-
* htmlParser(htmlString, {
|
44
|
-
* start: function(tag, attrs, unary) {},
|
45
|
-
* end: function(tag) {},
|
46
|
-
* chars: function(text) {},
|
47
|
-
* comment: function(text) {}
|
48
|
-
* });
|
49
|
-
*
|
50
|
-
*/
|
51
|
-
|
52
|
-
|
53
40
|
/**
|
54
41
|
* @ngdoc service
|
55
42
|
* @name $sanitize
|
56
43
|
* @kind function
|
57
44
|
*
|
58
45
|
* @description
|
46
|
+
* Sanitizes an html string by stripping all potentially dangerous tokens.
|
47
|
+
*
|
59
48
|
* The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are
|
60
49
|
* then serialized back to properly escaped html string. This means that no unsafe input can make
|
61
|
-
* it into the returned string
|
62
|
-
*
|
63
|
-
*
|
64
|
-
*
|
65
|
-
*
|
50
|
+
* it into the returned string.
|
51
|
+
*
|
52
|
+
* The whitelist for URL sanitization of attribute values is configured using the functions
|
53
|
+
* `aHrefSanitizationWhitelist` and `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider
|
54
|
+
* `$compileProvider`}.
|
55
|
+
*
|
56
|
+
* The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}.
|
66
57
|
*
|
67
58
|
* @param {string} html HTML input.
|
68
59
|
* @returns {string} Sanitized HTML.
|
69
60
|
*
|
70
61
|
* @example
|
71
|
-
<example module="sanitizeExample" deps="angular-sanitize.js">
|
62
|
+
<example module="sanitizeExample" deps="angular-sanitize.js" name="sanitize-service">
|
72
63
|
<file name="index.html">
|
73
64
|
<script>
|
74
65
|
angular.module('sanitizeExample', ['ngSanitize'])
|
@@ -117,19 +108,19 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
|
|
117
108
|
</file>
|
118
109
|
<file name="protractor.js" type="protractor">
|
119
110
|
it('should sanitize the html snippet by default', function() {
|
120
|
-
expect(element(by.css('#bind-html-with-sanitize div')).
|
111
|
+
expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')).
|
121
112
|
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
|
122
113
|
});
|
123
114
|
|
124
115
|
it('should inline raw snippet if bound to a trusted value', function() {
|
125
|
-
expect(element(by.css('#bind-html-with-trust div')).
|
116
|
+
expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).
|
126
117
|
toBe("<p style=\"color:blue\">an html\n" +
|
127
118
|
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
128
119
|
"snippet</p>");
|
129
120
|
});
|
130
121
|
|
131
122
|
it('should escape snippet without any filter', function() {
|
132
|
-
expect(element(by.css('#bind-default div')).
|
123
|
+
expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).
|
133
124
|
toBe("<p style=\"color:blue\">an html\n" +
|
134
125
|
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
135
126
|
"snippet</p>");
|
@@ -138,396 +129,477 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
|
|
138
129
|
it('should update', function() {
|
139
130
|
element(by.model('snippet')).clear();
|
140
131
|
element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
|
141
|
-
expect(element(by.css('#bind-html-with-sanitize div')).
|
132
|
+
expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')).
|
142
133
|
toBe('new <b>text</b>');
|
143
|
-
expect(element(by.css('#bind-html-with-trust div')).
|
134
|
+
expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).toBe(
|
144
135
|
'new <b onclick="alert(1)">text</b>');
|
145
|
-
expect(element(by.css('#bind-default div')).
|
136
|
+
expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).toBe(
|
146
137
|
"new <b onclick=\"alert(1)\">text</b>");
|
147
138
|
});
|
148
139
|
</file>
|
149
140
|
</example>
|
150
141
|
*/
|
142
|
+
|
143
|
+
|
144
|
+
/**
|
145
|
+
* @ngdoc provider
|
146
|
+
* @name $sanitizeProvider
|
147
|
+
* @this
|
148
|
+
*
|
149
|
+
* @description
|
150
|
+
* Creates and configures {@link $sanitize} instance.
|
151
|
+
*/
|
151
152
|
function $SanitizeProvider() {
|
153
|
+
var svgEnabled = false;
|
154
|
+
|
152
155
|
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
|
156
|
+
if (svgEnabled) {
|
157
|
+
extend(validElements, svgElements);
|
158
|
+
}
|
153
159
|
return function(html) {
|
154
160
|
var buf = [];
|
155
161
|
htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
|
156
|
-
return !/^unsafe
|
162
|
+
return !/^unsafe:/.test($$sanitizeUri(uri, isImage));
|
157
163
|
}));
|
158
164
|
return buf.join('');
|
159
165
|
};
|
160
166
|
}];
|
161
|
-
}
|
162
167
|
|
163
|
-
function sanitizeText(chars) {
|
164
|
-
var buf = [];
|
165
|
-
var writer = htmlSanitizeWriter(buf, angular.noop);
|
166
|
-
writer.chars(chars);
|
167
|
-
return buf.join('');
|
168
|
-
}
|
169
168
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
|
205
|
-
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
|
206
|
-
|
207
|
-
// Inline Elements - HTML5
|
208
|
-
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
|
209
|
-
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
|
210
|
-
"samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
|
211
|
-
|
212
|
-
// SVG Elements
|
213
|
-
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
|
214
|
-
// Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
|
215
|
-
// They can potentially allow for arbitrary javascript to be executed. See #11290
|
216
|
-
var svgElements = makeMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," +
|
217
|
-
"hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline," +
|
218
|
-
"radialGradient,rect,stop,svg,switch,text,title,tspan,use");
|
219
|
-
|
220
|
-
// Special Elements (can contain anything)
|
221
|
-
var specialElements = makeMap("script,style");
|
222
|
-
|
223
|
-
var validElements = angular.extend({},
|
224
|
-
voidElements,
|
225
|
-
blockElements,
|
226
|
-
inlineElements,
|
227
|
-
optionalEndTagElements,
|
228
|
-
svgElements);
|
229
|
-
|
230
|
-
//Attributes that have href and hence need to be sanitized
|
231
|
-
var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href");
|
232
|
-
|
233
|
-
var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
|
234
|
-
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
|
235
|
-
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
|
236
|
-
'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
|
237
|
-
'valign,value,vspace,width');
|
238
|
-
|
239
|
-
// SVG attributes (without "id" and "name" attributes)
|
240
|
-
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
|
241
|
-
var svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
|
242
|
-
'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
|
243
|
-
'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
|
244
|
-
'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
|
245
|
-
'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' +
|
246
|
-
'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' +
|
247
|
-
'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' +
|
248
|
-
'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' +
|
249
|
-
'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' +
|
250
|
-
'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' +
|
251
|
-
'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' +
|
252
|
-
'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' +
|
253
|
-
'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' +
|
254
|
-
'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' +
|
255
|
-
'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true);
|
256
|
-
|
257
|
-
var validAttrs = angular.extend({},
|
258
|
-
uriAttrs,
|
259
|
-
svgAttrs,
|
260
|
-
htmlAttrs);
|
261
|
-
|
262
|
-
function makeMap(str, lowercaseKeys) {
|
263
|
-
var obj = {}, items = str.split(','), i;
|
264
|
-
for (i = 0; i < items.length; i++) {
|
265
|
-
obj[lowercaseKeys ? angular.lowercase(items[i]) : items[i]] = true;
|
266
|
-
}
|
267
|
-
return obj;
|
268
|
-
}
|
269
|
-
|
270
|
-
|
271
|
-
/**
|
272
|
-
* @example
|
273
|
-
* htmlParser(htmlString, {
|
274
|
-
* start: function(tag, attrs, unary) {},
|
275
|
-
* end: function(tag) {},
|
276
|
-
* chars: function(text) {},
|
277
|
-
* comment: function(text) {}
|
278
|
-
* });
|
279
|
-
*
|
280
|
-
* @param {string} html string
|
281
|
-
* @param {object} handler
|
282
|
-
*/
|
283
|
-
function htmlParser(html, handler) {
|
284
|
-
if (typeof html !== 'string') {
|
285
|
-
if (html === null || typeof html === 'undefined') {
|
286
|
-
html = '';
|
169
|
+
/**
|
170
|
+
* @ngdoc method
|
171
|
+
* @name $sanitizeProvider#enableSvg
|
172
|
+
* @kind function
|
173
|
+
*
|
174
|
+
* @description
|
175
|
+
* Enables a subset of svg to be supported by the sanitizer.
|
176
|
+
*
|
177
|
+
* <div class="alert alert-warning">
|
178
|
+
* <p>By enabling this setting without taking other precautions, you might expose your
|
179
|
+
* application to click-hijacking attacks. In these attacks, sanitized svg elements could be positioned
|
180
|
+
* outside of the containing element and be rendered over other elements on the page (e.g. a login
|
181
|
+
* link). Such behavior can then result in phishing incidents.</p>
|
182
|
+
*
|
183
|
+
* <p>To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg
|
184
|
+
* tags within the sanitized content:</p>
|
185
|
+
*
|
186
|
+
* <br>
|
187
|
+
*
|
188
|
+
* <pre><code>
|
189
|
+
* .rootOfTheIncludedContent svg {
|
190
|
+
* overflow: hidden !important;
|
191
|
+
* }
|
192
|
+
* </code></pre>
|
193
|
+
* </div>
|
194
|
+
*
|
195
|
+
* @param {boolean=} flag Enable or disable SVG support in the sanitizer.
|
196
|
+
* @returns {boolean|ng.$sanitizeProvider} Returns the currently configured value if called
|
197
|
+
* without an argument or self for chaining otherwise.
|
198
|
+
*/
|
199
|
+
this.enableSvg = function(enableSvg) {
|
200
|
+
if (isDefined(enableSvg)) {
|
201
|
+
svgEnabled = enableSvg;
|
202
|
+
return this;
|
287
203
|
} else {
|
288
|
-
|
204
|
+
return svgEnabled;
|
289
205
|
}
|
290
|
-
}
|
291
|
-
var index, chars, match, stack = [], last = html, text;
|
292
|
-
stack.last = function() { return stack[stack.length - 1]; };
|
206
|
+
};
|
293
207
|
|
294
|
-
|
295
|
-
|
296
|
-
|
208
|
+
//////////////////////////////////////////////////////////////////////////////////////////////////
|
209
|
+
// Private stuff
|
210
|
+
//////////////////////////////////////////////////////////////////////////////////////////////////
|
297
211
|
|
298
|
-
|
299
|
-
|
212
|
+
bind = angular.bind;
|
213
|
+
extend = angular.extend;
|
214
|
+
forEach = angular.forEach;
|
215
|
+
isDefined = angular.isDefined;
|
216
|
+
lowercase = angular.lowercase;
|
217
|
+
noop = angular.noop;
|
300
218
|
|
301
|
-
|
302
|
-
|
303
|
-
// comments containing -- are not allowed unless they terminate the comment
|
304
|
-
index = html.indexOf("--", 4);
|
219
|
+
htmlParser = htmlParserImpl;
|
220
|
+
htmlSanitizeWriter = htmlSanitizeWriterImpl;
|
305
221
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
}
|
311
|
-
// DOCTYPE
|
312
|
-
} else if (DOCTYPE_REGEXP.test(html)) {
|
313
|
-
match = html.match(DOCTYPE_REGEXP);
|
222
|
+
nodeContains = window.Node.prototype.contains || /** @this */ function(arg) {
|
223
|
+
// eslint-disable-next-line no-bitwise
|
224
|
+
return !!(this.compareDocumentPosition(arg) & 16);
|
225
|
+
};
|
314
226
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
227
|
+
// Regular Expressions for parsing tags and attributes
|
228
|
+
var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
|
229
|
+
// Match everything outside of normal chars and " (quote character)
|
230
|
+
NON_ALPHANUMERIC_REGEXP = /([^#-~ |!])/g;
|
231
|
+
|
232
|
+
|
233
|
+
// Good source of info about elements and attributes
|
234
|
+
// http://dev.w3.org/html5/spec/Overview.html#semantics
|
235
|
+
// http://simon.html5.org/html-elements
|
236
|
+
|
237
|
+
// Safe Void Elements - HTML5
|
238
|
+
// http://dev.w3.org/html5/spec/Overview.html#void-elements
|
239
|
+
var voidElements = toMap('area,br,col,hr,img,wbr');
|
240
|
+
|
241
|
+
// Elements that you can, intentionally, leave open (and which close themselves)
|
242
|
+
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
|
243
|
+
var optionalEndTagBlockElements = toMap('colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr'),
|
244
|
+
optionalEndTagInlineElements = toMap('rp,rt'),
|
245
|
+
optionalEndTagElements = extend({},
|
246
|
+
optionalEndTagInlineElements,
|
247
|
+
optionalEndTagBlockElements);
|
248
|
+
|
249
|
+
// Safe Block Elements - HTML5
|
250
|
+
var blockElements = extend({}, optionalEndTagBlockElements, toMap('address,article,' +
|
251
|
+
'aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,' +
|
252
|
+
'h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul'));
|
253
|
+
|
254
|
+
// Inline Elements - HTML5
|
255
|
+
var inlineElements = extend({}, optionalEndTagInlineElements, toMap('a,abbr,acronym,b,' +
|
256
|
+
'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,' +
|
257
|
+
'samp,small,span,strike,strong,sub,sup,time,tt,u,var'));
|
258
|
+
|
259
|
+
// SVG Elements
|
260
|
+
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
|
261
|
+
// Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
|
262
|
+
// They can potentially allow for arbitrary javascript to be executed. See #11290
|
263
|
+
var svgElements = toMap('circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,' +
|
264
|
+
'hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,' +
|
265
|
+
'radialGradient,rect,stop,svg,switch,text,title,tspan');
|
266
|
+
|
267
|
+
// Blocked Elements (will be stripped)
|
268
|
+
var blockedElements = toMap('script,style');
|
269
|
+
|
270
|
+
var validElements = extend({},
|
271
|
+
voidElements,
|
272
|
+
blockElements,
|
273
|
+
inlineElements,
|
274
|
+
optionalEndTagElements);
|
275
|
+
|
276
|
+
//Attributes that have href and hence need to be sanitized
|
277
|
+
var uriAttrs = toMap('background,cite,href,longdesc,src,xlink:href');
|
278
|
+
|
279
|
+
var htmlAttrs = toMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
|
280
|
+
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
|
281
|
+
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
|
282
|
+
'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
|
283
|
+
'valign,value,vspace,width');
|
284
|
+
|
285
|
+
// SVG attributes (without "id" and "name" attributes)
|
286
|
+
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
|
287
|
+
var svgAttrs = toMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
|
288
|
+
'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
|
289
|
+
'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
|
290
|
+
'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
|
291
|
+
'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' +
|
292
|
+
'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' +
|
293
|
+
'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' +
|
294
|
+
'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' +
|
295
|
+
'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' +
|
296
|
+
'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' +
|
297
|
+
'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' +
|
298
|
+
'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' +
|
299
|
+
'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' +
|
300
|
+
'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' +
|
301
|
+
'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true);
|
302
|
+
|
303
|
+
var validAttrs = extend({},
|
304
|
+
uriAttrs,
|
305
|
+
svgAttrs,
|
306
|
+
htmlAttrs);
|
307
|
+
|
308
|
+
function toMap(str, lowercaseKeys) {
|
309
|
+
var obj = {}, items = str.split(','), i;
|
310
|
+
for (i = 0; i < items.length; i++) {
|
311
|
+
obj[lowercaseKeys ? lowercase(items[i]) : items[i]] = true;
|
312
|
+
}
|
313
|
+
return obj;
|
314
|
+
}
|
328
315
|
|
329
|
-
|
330
|
-
|
331
|
-
|
316
|
+
/**
|
317
|
+
* Create an inert document that contains the dirty HTML that needs sanitizing
|
318
|
+
* Depending upon browser support we use one of three strategies for doing this.
|
319
|
+
* Support: Safari 10.x -> XHR strategy
|
320
|
+
* Support: Firefox -> DomParser strategy
|
321
|
+
*/
|
322
|
+
var getInertBodyElement /* function(html: string): HTMLBodyElement */ = (function(window, document) {
|
323
|
+
var inertDocument;
|
324
|
+
if (document && document.implementation) {
|
325
|
+
inertDocument = document.implementation.createHTMLDocument('inert');
|
326
|
+
} else {
|
327
|
+
throw $sanitizeMinErr('noinert', 'Can\'t create an inert html document');
|
328
|
+
}
|
329
|
+
var inertBodyElement = (inertDocument.documentElement || inertDocument.getDocumentElement()).querySelector('body');
|
332
330
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
}
|
331
|
+
// Check for the Safari 10.1 bug - which allows JS to run inside the SVG G element
|
332
|
+
inertBodyElement.innerHTML = '<svg><g onload="this.parentNode.remove()"></g></svg>';
|
333
|
+
if (!inertBodyElement.querySelector('svg')) {
|
334
|
+
return getInertBodyElement_XHR;
|
335
|
+
} else {
|
336
|
+
// Check for the Firefox bug - which prevents the inner img JS from being sanitized
|
337
|
+
inertBodyElement.innerHTML = '<svg><p><style><img src="</style><img src=x onerror=alert(1)//">';
|
338
|
+
if (inertBodyElement.querySelector('svg img')) {
|
339
|
+
return getInertBodyElement_DOMParser;
|
340
|
+
} else {
|
341
|
+
return getInertBodyElement_InertDocument;
|
345
342
|
}
|
343
|
+
}
|
346
344
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
345
|
+
function getInertBodyElement_XHR(html) {
|
346
|
+
// We add this dummy element to ensure that the rest of the content is parsed as expected
|
347
|
+
// e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the `<head>` tag.
|
348
|
+
html = '<remove></remove>' + html;
|
349
|
+
try {
|
350
|
+
html = encodeURI(html);
|
351
|
+
} catch (e) {
|
352
|
+
return undefined;
|
354
353
|
}
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
if (handler.chars) handler.chars(decodeEntities(text));
|
363
|
-
|
364
|
-
return "";
|
365
|
-
});
|
366
|
-
|
367
|
-
parseEndTag("", stack.last());
|
354
|
+
var xhr = new window.XMLHttpRequest();
|
355
|
+
xhr.responseType = 'document';
|
356
|
+
xhr.open('GET', 'data:text/html;charset=utf-8,' + html, false);
|
357
|
+
xhr.send(null);
|
358
|
+
var body = xhr.response.body;
|
359
|
+
body.firstChild.remove();
|
360
|
+
return body;
|
368
361
|
}
|
369
362
|
|
370
|
-
|
371
|
-
|
372
|
-
|
363
|
+
function getInertBodyElement_DOMParser(html) {
|
364
|
+
// We add this dummy element to ensure that the rest of the content is parsed as expected
|
365
|
+
// e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the `<head>` tag.
|
366
|
+
html = '<remove></remove>' + html;
|
367
|
+
try {
|
368
|
+
var body = new window.DOMParser().parseFromString(html, 'text/html').body;
|
369
|
+
body.firstChild.remove();
|
370
|
+
return body;
|
371
|
+
} catch (e) {
|
372
|
+
return undefined;
|
373
|
+
}
|
373
374
|
}
|
374
|
-
last = html;
|
375
|
-
}
|
376
375
|
|
377
|
-
|
378
|
-
|
376
|
+
function getInertBodyElement_InertDocument(html) {
|
377
|
+
inertBodyElement.innerHTML = html;
|
379
378
|
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
parseEndTag("", stack.last());
|
379
|
+
// Support: IE 9-11 only
|
380
|
+
// strip custom-namespaced attributes on IE<=11
|
381
|
+
if (document.documentMode) {
|
382
|
+
stripCustomNsAttrs(inertBodyElement);
|
385
383
|
}
|
386
|
-
}
|
387
384
|
|
388
|
-
|
389
|
-
parseEndTag("", tagName);
|
385
|
+
return inertBodyElement;
|
390
386
|
}
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
387
|
+
})(window, window.document);
|
388
|
+
|
389
|
+
/**
|
390
|
+
* @example
|
391
|
+
* htmlParser(htmlString, {
|
392
|
+
* start: function(tag, attrs) {},
|
393
|
+
* end: function(tag) {},
|
394
|
+
* chars: function(text) {},
|
395
|
+
* comment: function(text) {}
|
396
|
+
* });
|
397
|
+
*
|
398
|
+
* @param {string} html string
|
399
|
+
* @param {object} handler
|
400
|
+
*/
|
401
|
+
function htmlParserImpl(html, handler) {
|
402
|
+
if (html === null || html === undefined) {
|
403
|
+
html = '';
|
404
|
+
} else if (typeof html !== 'string') {
|
405
|
+
html = '' + html;
|
396
406
|
}
|
397
407
|
|
398
|
-
var
|
408
|
+
var inertBodyElement = getInertBodyElement(html);
|
409
|
+
if (!inertBodyElement) return '';
|
399
410
|
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
+
//mXSS protection
|
412
|
+
var mXSSAttempts = 5;
|
413
|
+
do {
|
414
|
+
if (mXSSAttempts === 0) {
|
415
|
+
throw $sanitizeMinErr('uinput', 'Failed to sanitize html because the input is unstable');
|
416
|
+
}
|
417
|
+
mXSSAttempts--;
|
418
|
+
|
419
|
+
// trigger mXSS if it is going to happen by reading and writing the innerHTML
|
420
|
+
html = inertBodyElement.innerHTML;
|
421
|
+
inertBodyElement = getInertBodyElement(html);
|
422
|
+
} while (html !== inertBodyElement.innerHTML);
|
423
|
+
|
424
|
+
var node = inertBodyElement.firstChild;
|
425
|
+
while (node) {
|
426
|
+
switch (node.nodeType) {
|
427
|
+
case 1: // ELEMENT_NODE
|
428
|
+
handler.start(node.nodeName.toLowerCase(), attrToMap(node.attributes));
|
429
|
+
break;
|
430
|
+
case 3: // TEXT NODE
|
431
|
+
handler.chars(node.textContent);
|
432
|
+
break;
|
433
|
+
}
|
411
434
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
if (
|
435
|
+
var nextNode;
|
436
|
+
if (!(nextNode = node.firstChild)) {
|
437
|
+
if (node.nodeType === 1) {
|
438
|
+
handler.end(node.nodeName.toLowerCase());
|
439
|
+
}
|
440
|
+
nextNode = getNonDescendant('nextSibling', node);
|
441
|
+
if (!nextNode) {
|
442
|
+
while (nextNode == null) {
|
443
|
+
node = getNonDescendant('parentNode', node);
|
444
|
+
if (node === inertBodyElement) break;
|
445
|
+
nextNode = getNonDescendant('nextSibling', node);
|
446
|
+
if (node.nodeType === 1) {
|
447
|
+
handler.end(node.nodeName.toLowerCase());
|
448
|
+
}
|
449
|
+
}
|
450
|
+
}
|
419
451
|
}
|
452
|
+
node = nextNode;
|
420
453
|
}
|
421
454
|
|
422
|
-
|
423
|
-
|
424
|
-
for (i = stack.length - 1; i >= pos; i--)
|
425
|
-
if (handler.end) handler.end(stack[i]);
|
426
|
-
|
427
|
-
// Remove the open elements from the stack
|
428
|
-
stack.length = pos;
|
455
|
+
while ((node = inertBodyElement.firstChild)) {
|
456
|
+
inertBodyElement.removeChild(node);
|
429
457
|
}
|
430
458
|
}
|
431
|
-
}
|
432
459
|
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
460
|
+
function attrToMap(attrs) {
|
461
|
+
var map = {};
|
462
|
+
for (var i = 0, ii = attrs.length; i < ii; i++) {
|
463
|
+
var attr = attrs[i];
|
464
|
+
map[attr.name] = attr.value;
|
465
|
+
}
|
466
|
+
return map;
|
467
|
+
}
|
441
468
|
|
442
|
-
hiddenPre.innerHTML = value.replace(/</g,"<");
|
443
|
-
// innerText depends on styling as it doesn't display hidden elements.
|
444
|
-
// Therefore, it's better to use textContent not to cause unnecessary reflows.
|
445
|
-
return hiddenPre.textContent;
|
446
|
-
}
|
447
469
|
|
448
|
-
/**
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
function encodeEntities(value) {
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
}
|
470
|
+
/**
|
471
|
+
* Escapes all potentially dangerous characters, so that the
|
472
|
+
* resulting string can be safely inserted into attribute or
|
473
|
+
* element text.
|
474
|
+
* @param value
|
475
|
+
* @returns {string} escaped text
|
476
|
+
*/
|
477
|
+
function encodeEntities(value) {
|
478
|
+
return value.
|
479
|
+
replace(/&/g, '&').
|
480
|
+
replace(SURROGATE_PAIR_REGEXP, function(value) {
|
481
|
+
var hi = value.charCodeAt(0);
|
482
|
+
var low = value.charCodeAt(1);
|
483
|
+
return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
|
484
|
+
}).
|
485
|
+
replace(NON_ALPHANUMERIC_REGEXP, function(value) {
|
486
|
+
return '&#' + value.charCodeAt(0) + ';';
|
487
|
+
}).
|
488
|
+
replace(/</g, '<').
|
489
|
+
replace(/>/g, '>');
|
490
|
+
}
|
469
491
|
|
470
|
-
/**
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
function
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
tag =
|
509
|
-
if (!
|
492
|
+
/**
|
493
|
+
* create an HTML/XML writer which writes to buffer
|
494
|
+
* @param {Array} buf use buf.join('') to get out sanitized html string
|
495
|
+
* @returns {object} in the form of {
|
496
|
+
* start: function(tag, attrs) {},
|
497
|
+
* end: function(tag) {},
|
498
|
+
* chars: function(text) {},
|
499
|
+
* comment: function(text) {}
|
500
|
+
* }
|
501
|
+
*/
|
502
|
+
function htmlSanitizeWriterImpl(buf, uriValidator) {
|
503
|
+
var ignoreCurrentElement = false;
|
504
|
+
var out = bind(buf, buf.push);
|
505
|
+
return {
|
506
|
+
start: function(tag, attrs) {
|
507
|
+
tag = lowercase(tag);
|
508
|
+
if (!ignoreCurrentElement && blockedElements[tag]) {
|
509
|
+
ignoreCurrentElement = tag;
|
510
|
+
}
|
511
|
+
if (!ignoreCurrentElement && validElements[tag] === true) {
|
512
|
+
out('<');
|
513
|
+
out(tag);
|
514
|
+
forEach(attrs, function(value, key) {
|
515
|
+
var lkey = lowercase(key);
|
516
|
+
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
|
517
|
+
if (validAttrs[lkey] === true &&
|
518
|
+
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
|
519
|
+
out(' ');
|
520
|
+
out(key);
|
521
|
+
out('="');
|
522
|
+
out(encodeEntities(value));
|
523
|
+
out('"');
|
524
|
+
}
|
525
|
+
});
|
526
|
+
out('>');
|
527
|
+
}
|
528
|
+
},
|
529
|
+
end: function(tag) {
|
530
|
+
tag = lowercase(tag);
|
531
|
+
if (!ignoreCurrentElement && validElements[tag] === true && voidElements[tag] !== true) {
|
510
532
|
out('</');
|
511
533
|
out(tag);
|
512
534
|
out('>');
|
513
535
|
}
|
514
|
-
|
515
|
-
|
536
|
+
// eslint-disable-next-line eqeqeq
|
537
|
+
if (tag == ignoreCurrentElement) {
|
538
|
+
ignoreCurrentElement = false;
|
516
539
|
}
|
517
540
|
},
|
518
|
-
|
519
|
-
if (!
|
541
|
+
chars: function(chars) {
|
542
|
+
if (!ignoreCurrentElement) {
|
520
543
|
out(encodeEntities(chars));
|
521
544
|
}
|
522
545
|
}
|
523
|
-
|
546
|
+
};
|
547
|
+
}
|
548
|
+
|
549
|
+
|
550
|
+
/**
|
551
|
+
* When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' attribute to declare
|
552
|
+
* ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). This is undesirable since we don't want
|
553
|
+
* to allow any of these custom attributes. This method strips them all.
|
554
|
+
*
|
555
|
+
* @param node Root element to process
|
556
|
+
*/
|
557
|
+
function stripCustomNsAttrs(node) {
|
558
|
+
while (node) {
|
559
|
+
if (node.nodeType === window.Node.ELEMENT_NODE) {
|
560
|
+
var attrs = node.attributes;
|
561
|
+
for (var i = 0, l = attrs.length; i < l; i++) {
|
562
|
+
var attrNode = attrs[i];
|
563
|
+
var attrName = attrNode.name.toLowerCase();
|
564
|
+
if (attrName === 'xmlns:ns1' || attrName.lastIndexOf('ns1:', 0) === 0) {
|
565
|
+
node.removeAttributeNode(attrNode);
|
566
|
+
i--;
|
567
|
+
l--;
|
568
|
+
}
|
569
|
+
}
|
570
|
+
}
|
571
|
+
|
572
|
+
var nextNode = node.firstChild;
|
573
|
+
if (nextNode) {
|
574
|
+
stripCustomNsAttrs(nextNode);
|
575
|
+
}
|
576
|
+
|
577
|
+
node = getNonDescendant('nextSibling', node);
|
578
|
+
}
|
579
|
+
}
|
580
|
+
|
581
|
+
function getNonDescendant(propName, node) {
|
582
|
+
// An element is clobbered if its `propName` property points to one of its descendants
|
583
|
+
var nextNode = node[propName];
|
584
|
+
if (nextNode && nodeContains.call(node, nextNode)) {
|
585
|
+
throw $sanitizeMinErr('elclob', 'Failed to sanitize html because the element is clobbered: {0}', node.outerHTML || node.outerText);
|
586
|
+
}
|
587
|
+
return nextNode;
|
588
|
+
}
|
524
589
|
}
|
525
590
|
|
591
|
+
function sanitizeText(chars) {
|
592
|
+
var buf = [];
|
593
|
+
var writer = htmlSanitizeWriter(buf, noop);
|
594
|
+
writer.chars(chars);
|
595
|
+
return buf.join('');
|
596
|
+
}
|
526
597
|
|
527
|
-
// define ngSanitize module and register $sanitize service
|
528
|
-
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
529
598
|
|
530
|
-
|
599
|
+
// define ngSanitize module and register $sanitize service
|
600
|
+
angular.module('ngSanitize', [])
|
601
|
+
.provider('$sanitize', $SanitizeProvider)
|
602
|
+
.info({ angularVersion: '1.6.7' });
|
531
603
|
|
532
604
|
/**
|
533
605
|
* @ngdoc filter
|
@@ -535,40 +607,39 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
|
535
607
|
* @kind function
|
536
608
|
*
|
537
609
|
* @description
|
538
|
-
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
|
610
|
+
* Finds links in text input and turns them into html links. Supports `http/https/ftp/sftp/mailto` and
|
539
611
|
* plain email address links.
|
540
612
|
*
|
541
613
|
* Requires the {@link ngSanitize `ngSanitize`} module to be installed.
|
542
614
|
*
|
543
615
|
* @param {string} text Input text.
|
544
|
-
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
|
545
|
-
* @
|
616
|
+
* @param {string} [target] Window (`_blank|_self|_parent|_top`) or named frame to open links in.
|
617
|
+
* @param {object|function(url)} [attributes] Add custom attributes to the link element.
|
618
|
+
*
|
619
|
+
* Can be one of:
|
620
|
+
*
|
621
|
+
* - `object`: A map of attributes
|
622
|
+
* - `function`: Takes the url as a parameter and returns a map of attributes
|
623
|
+
*
|
624
|
+
* If the map of attributes contains a value for `target`, it overrides the value of
|
625
|
+
* the target parameter.
|
626
|
+
*
|
627
|
+
*
|
628
|
+
* @returns {string} Html-linkified and {@link $sanitize sanitized} text.
|
546
629
|
*
|
547
630
|
* @usage
|
548
631
|
<span ng-bind-html="linky_expression | linky"></span>
|
549
632
|
*
|
550
633
|
* @example
|
551
|
-
<example module="linkyExample" deps="angular-sanitize.js">
|
634
|
+
<example module="linkyExample" deps="angular-sanitize.js" name="linky-filter">
|
552
635
|
<file name="index.html">
|
553
|
-
<script>
|
554
|
-
angular.module('linkyExample', ['ngSanitize'])
|
555
|
-
.controller('ExampleController', ['$scope', function($scope) {
|
556
|
-
$scope.snippet =
|
557
|
-
'Pretty text with some links:\n'+
|
558
|
-
'http://angularjs.org/,\n'+
|
559
|
-
'mailto:us@somewhere.org,\n'+
|
560
|
-
'another@somewhere.org,\n'+
|
561
|
-
'and one more: ftp://127.0.0.1/.';
|
562
|
-
$scope.snippetWithTarget = 'http://angularjs.org/';
|
563
|
-
}]);
|
564
|
-
</script>
|
565
636
|
<div ng-controller="ExampleController">
|
566
637
|
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
567
638
|
<table>
|
568
639
|
<tr>
|
569
|
-
<
|
570
|
-
<
|
571
|
-
<
|
640
|
+
<th>Filter</th>
|
641
|
+
<th>Source</th>
|
642
|
+
<th>Rendered</th>
|
572
643
|
</tr>
|
573
644
|
<tr id="linky-filter">
|
574
645
|
<td>linky filter</td>
|
@@ -582,10 +653,19 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
|
582
653
|
<tr id="linky-target">
|
583
654
|
<td>linky target</td>
|
584
655
|
<td>
|
585
|
-
<pre><div ng-bind-html="
|
656
|
+
<pre><div ng-bind-html="snippetWithSingleURL | linky:'_blank'"><br></div></pre>
|
586
657
|
</td>
|
587
658
|
<td>
|
588
|
-
<div ng-bind-html="
|
659
|
+
<div ng-bind-html="snippetWithSingleURL | linky:'_blank'"></div>
|
660
|
+
</td>
|
661
|
+
</tr>
|
662
|
+
<tr id="linky-custom-attributes">
|
663
|
+
<td>linky custom attributes</td>
|
664
|
+
<td>
|
665
|
+
<pre><div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"><br></div></pre>
|
666
|
+
</td>
|
667
|
+
<td>
|
668
|
+
<div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"></div>
|
589
669
|
</td>
|
590
670
|
</tr>
|
591
671
|
<tr id="escaped-html">
|
@@ -595,6 +675,18 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
|
595
675
|
</tr>
|
596
676
|
</table>
|
597
677
|
</file>
|
678
|
+
<file name="script.js">
|
679
|
+
angular.module('linkyExample', ['ngSanitize'])
|
680
|
+
.controller('ExampleController', ['$scope', function($scope) {
|
681
|
+
$scope.snippet =
|
682
|
+
'Pretty text with some links:\n' +
|
683
|
+
'http://angularjs.org/,\n' +
|
684
|
+
'mailto:us@somewhere.org,\n' +
|
685
|
+
'another@somewhere.org,\n' +
|
686
|
+
'and one more: ftp://127.0.0.1/.';
|
687
|
+
$scope.snippetWithSingleURL = 'http://angularjs.org/';
|
688
|
+
}]);
|
689
|
+
</file>
|
598
690
|
<file name="protractor.js" type="protractor">
|
599
691
|
it('should linkify the snippet with urls', function() {
|
600
692
|
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
|
@@ -622,20 +714,40 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
|
622
714
|
|
623
715
|
it('should work with the target property', function() {
|
624
716
|
expect(element(by.id('linky-target')).
|
625
|
-
element(by.binding("
|
717
|
+
element(by.binding("snippetWithSingleURL | linky:'_blank'")).getText()).
|
626
718
|
toBe('http://angularjs.org/');
|
627
719
|
expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
|
628
720
|
});
|
721
|
+
|
722
|
+
it('should optionally add custom attributes', function() {
|
723
|
+
expect(element(by.id('linky-custom-attributes')).
|
724
|
+
element(by.binding("snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}")).getText()).
|
725
|
+
toBe('http://angularjs.org/');
|
726
|
+
expect(element(by.css('#linky-custom-attributes a')).getAttribute('rel')).toEqual('nofollow');
|
727
|
+
});
|
629
728
|
</file>
|
630
729
|
</example>
|
631
730
|
*/
|
632
731
|
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
|
633
732
|
var LINKY_URL_REGEXP =
|
634
|
-
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
|
733
|
+
/((s?ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
|
635
734
|
MAILTO_REGEXP = /^mailto:/i;
|
636
735
|
|
637
|
-
|
638
|
-
|
736
|
+
var linkyMinErr = angular.$$minErr('linky');
|
737
|
+
var isDefined = angular.isDefined;
|
738
|
+
var isFunction = angular.isFunction;
|
739
|
+
var isObject = angular.isObject;
|
740
|
+
var isString = angular.isString;
|
741
|
+
|
742
|
+
return function(text, target, attributes) {
|
743
|
+
if (text == null || text === '') return text;
|
744
|
+
if (!isString(text)) throw linkyMinErr('notstring', 'Expected string but received: {0}', text);
|
745
|
+
|
746
|
+
var attributesFn =
|
747
|
+
isFunction(attributes) ? attributes :
|
748
|
+
isObject(attributes) ? function getAttributesObject() {return attributes;} :
|
749
|
+
function getEmptyAttributesObject() {return {};};
|
750
|
+
|
639
751
|
var match;
|
640
752
|
var raw = text;
|
641
753
|
var html = [];
|
@@ -664,8 +776,14 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
|
|
664
776
|
}
|
665
777
|
|
666
778
|
function addLink(url, text) {
|
779
|
+
var key, linkAttributes = attributesFn(url);
|
667
780
|
html.push('<a ');
|
668
|
-
|
781
|
+
|
782
|
+
for (key in linkAttributes) {
|
783
|
+
html.push(key + '="' + linkAttributes[key] + '" ');
|
784
|
+
}
|
785
|
+
|
786
|
+
if (isDefined(target) && !('target' in linkAttributes)) {
|
669
787
|
html.push('target="',
|
670
788
|
target,
|
671
789
|
'" ');
|