billy_cms 0.0.2

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.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +4 -0
  4. data/CODE_OF_CONDUCT.md +49 -0
  5. data/Gemfile +8 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +41 -0
  8. data/Rakefile +10 -0
  9. data/app/assets/images/billy_cms/32px.png +0 -0
  10. data/app/assets/images/billy_cms/40px.png +0 -0
  11. data/app/assets/images/billy_cms/fileicon.png +0 -0
  12. data/app/assets/images/billy_cms/throbber.gif +0 -0
  13. data/app/assets/javascripts/billy_cms/backend/app.js.erb +217 -0
  14. data/app/assets/javascripts/billy_cms/backend/attributes.js +33 -0
  15. data/app/assets/javascripts/billy_cms/backend/billy_object.js +41 -0
  16. data/app/assets/javascripts/billy_cms/backend/directives/ng-jstree.js +200 -0
  17. data/app/assets/javascripts/billy_cms/backend/jstree.js +7781 -0
  18. data/app/assets/javascripts/billy_cms/backend/ngfileupload.js +2506 -0
  19. data/app/assets/javascripts/billy_cms/backend/page_type.js +11 -0
  20. data/app/assets/javascripts/billy_cms/backend/ui-router.js +4370 -0
  21. data/app/assets/javascripts/billy_cms/backend.js +15 -0
  22. data/app/assets/javascripts/billy_cms/frontend/api_connector.js +5 -0
  23. data/app/assets/javascripts/billy_cms/frontend/editing.es6 +120 -0
  24. data/app/assets/javascripts/billy_cms/frontend/google_maps.js +13 -0
  25. data/app/assets/javascripts/billy_cms/frontend/navbar.js +0 -0
  26. data/app/assets/javascripts/billy_cms/frontend/slick.es6 +8 -0
  27. data/app/assets/javascripts/billy_cms.js +16 -0
  28. data/app/assets/stylesheets/billy_cms/backend/index.scss +17 -0
  29. data/app/assets/stylesheets/billy_cms/editmode.scss +78 -0
  30. data/app/assets/stylesheets/billy_cms/index.scss +5 -0
  31. data/app/assets/stylesheets/billy_cms/jstree.scss +1061 -0
  32. data/app/assets/stylesheets/billy_cms.css +15 -0
  33. data/app/controllers/billy_cms/api/api_controller.rb +25 -0
  34. data/app/controllers/billy_cms/api/attributes_controller.rb +65 -0
  35. data/app/controllers/billy_cms/api/page_types_controller.rb +13 -0
  36. data/app/controllers/billy_cms/api/pages_controller.rb +63 -0
  37. data/app/controllers/billy_cms/backend_controller.rb +10 -0
  38. data/app/controllers/billy_cms/base_controller.rb +23 -0
  39. data/app/helpers/billy_cms/cms_path_helper.rb +8 -0
  40. data/app/helpers/billy_cms/content_helper.rb +17 -0
  41. data/app/helpers/billy_cms/permission_helper.rb +24 -0
  42. data/app/models/billy_cms/additional_attribute.rb +6 -0
  43. data/app/models/billy_cms/additional_attributes_page_types.rb +6 -0
  44. data/app/models/billy_cms/page.rb +61 -0
  45. data/app/models/billy_cms/page_type.rb +7 -0
  46. data/app/models/billy_router.rb +20 -0
  47. data/app/serializers/billy_cms/additional_attribute_serializer.rb +6 -0
  48. data/app/serializers/billy_cms/page_serializer.rb +15 -0
  49. data/app/serializers/billy_cms/page_type_serializer.rb +5 -0
  50. data/app/views/billy_cms/_edit_page_button.html.erb +1 -0
  51. data/app/views/billy_cms/_page_settings_form.html.erb +25 -0
  52. data/app/views/billy_cms/_page_settings_modal.html.erb +19 -0
  53. data/app/views/billy_cms/_settings_page_button.html.erb +4 -0
  54. data/app/views/billy_cms/backend/_attributes.html.erb +12 -0
  55. data/app/views/billy_cms/backend/_header.html.erb +84 -0
  56. data/app/views/billy_cms/backend/_tree.html.erb +58 -0
  57. data/app/views/billy_cms/backend/index.html.erb +19 -0
  58. data/billy_cms.gemspec +32 -0
  59. data/bin/console +14 -0
  60. data/bin/setup +8 -0
  61. data/lib/billy_cms/version.rb +3 -0
  62. data/lib/billy_cms.rb +5 -0
  63. metadata +147 -0
@@ -0,0 +1,2506 @@
1
+ /**!
2
+ * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,
3
+ * progress, resize, thumbnail, preview, validation and CORS
4
+ * FileAPI Flash shim for old browsers not supporting FormData
5
+ * @author Danial <danial.farid@gmail.com>
6
+ * @version 10.1.9
7
+ */
8
+
9
+ (function () {
10
+ /** @namespace FileAPI.noContentTimeout */
11
+
12
+ function patchXHR(fnName, newFn) {
13
+ window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]);
14
+ }
15
+
16
+ function redefineProp(xhr, prop, fn) {
17
+ try {
18
+ Object.defineProperty(xhr, prop, {get: fn});
19
+ } catch (e) {/*ignore*/
20
+ }
21
+ }
22
+
23
+ if (!window.FileAPI) {
24
+ window.FileAPI = {};
25
+ }
26
+
27
+ if (!window.XMLHttpRequest) {
28
+ throw 'AJAX is not supported. XMLHttpRequest is not defined.';
29
+ }
30
+
31
+ FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad;
32
+ if (FileAPI.shouldLoad) {
33
+ var initializeUploadListener = function (xhr) {
34
+ if (!xhr.__listeners) {
35
+ if (!xhr.upload) xhr.upload = {};
36
+ xhr.__listeners = [];
37
+ var origAddEventListener = xhr.upload.addEventListener;
38
+ xhr.upload.addEventListener = function (t, fn) {
39
+ xhr.__listeners[t] = fn;
40
+ if (origAddEventListener) origAddEventListener.apply(this, arguments);
41
+ };
42
+ }
43
+ };
44
+
45
+ patchXHR('open', function (orig) {
46
+ return function (m, url, b) {
47
+ initializeUploadListener(this);
48
+ this.__url = url;
49
+ try {
50
+ orig.apply(this, [m, url, b]);
51
+ } catch (e) {
52
+ if (e.message.indexOf('Access is denied') > -1) {
53
+ this.__origError = e;
54
+ orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]);
55
+ }
56
+ }
57
+ };
58
+ });
59
+
60
+ patchXHR('getResponseHeader', function (orig) {
61
+ return function (h) {
62
+ return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h]));
63
+ };
64
+ });
65
+
66
+ patchXHR('getAllResponseHeaders', function (orig) {
67
+ return function () {
68
+ return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this));
69
+ };
70
+ });
71
+
72
+ patchXHR('abort', function (orig) {
73
+ return function () {
74
+ return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));
75
+ };
76
+ });
77
+
78
+ patchXHR('setRequestHeader', function (orig) {
79
+ return function (header, value) {
80
+ if (header === '__setXHR_') {
81
+ initializeUploadListener(this);
82
+ var val = value(this);
83
+ // fix for angular < 1.2.0
84
+ if (val instanceof Function) {
85
+ val(this);
86
+ }
87
+ } else {
88
+ this.__requestHeaders = this.__requestHeaders || {};
89
+ this.__requestHeaders[header] = value;
90
+ orig.apply(this, arguments);
91
+ }
92
+ };
93
+ });
94
+
95
+ patchXHR('send', function (orig) {
96
+ return function () {
97
+ var xhr = this;
98
+ if (arguments[0] && arguments[0].__isFileAPIShim) {
99
+ var formData = arguments[0];
100
+ var config = {
101
+ url: xhr.__url,
102
+ jsonp: false, //removes the callback form param
103
+ cache: true, //removes the ?fileapiXXX in the url
104
+ complete: function (err, fileApiXHR) {
105
+ if (err && angular.isString(err) && err.indexOf('#2174') !== -1) {
106
+ // this error seems to be fine the file is being uploaded properly.
107
+ err = null;
108
+ }
109
+ xhr.__completed = true;
110
+ if (!err && xhr.__listeners.load)
111
+ xhr.__listeners.load({
112
+ type: 'load',
113
+ loaded: xhr.__loaded,
114
+ total: xhr.__total,
115
+ target: xhr,
116
+ lengthComputable: true
117
+ });
118
+ if (!err && xhr.__listeners.loadend)
119
+ xhr.__listeners.loadend({
120
+ type: 'loadend',
121
+ loaded: xhr.__loaded,
122
+ total: xhr.__total,
123
+ target: xhr,
124
+ lengthComputable: true
125
+ });
126
+ if (err === 'abort' && xhr.__listeners.abort)
127
+ xhr.__listeners.abort({
128
+ type: 'abort',
129
+ loaded: xhr.__loaded,
130
+ total: xhr.__total,
131
+ target: xhr,
132
+ lengthComputable: true
133
+ });
134
+ if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () {
135
+ return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status;
136
+ });
137
+ if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () {
138
+ return fileApiXHR.statusText;
139
+ });
140
+ redefineProp(xhr, 'readyState', function () {
141
+ return 4;
142
+ });
143
+ if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () {
144
+ return fileApiXHR.response;
145
+ });
146
+ var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined);
147
+ redefineProp(xhr, 'responseText', function () {
148
+ return resp;
149
+ });
150
+ redefineProp(xhr, 'response', function () {
151
+ return resp;
152
+ });
153
+ if (err) redefineProp(xhr, 'err', function () {
154
+ return err;
155
+ });
156
+ xhr.__fileApiXHR = fileApiXHR;
157
+ if (xhr.onreadystatechange) xhr.onreadystatechange();
158
+ if (xhr.onload) xhr.onload();
159
+ },
160
+ progress: function (e) {
161
+ e.target = xhr;
162
+ if (xhr.__listeners.progress) xhr.__listeners.progress(e);
163
+ xhr.__total = e.total;
164
+ xhr.__loaded = e.loaded;
165
+ if (e.total === e.loaded) {
166
+ // fix flash issue that doesn't call complete if there is no response text from the server
167
+ var _this = this;
168
+ setTimeout(function () {
169
+ if (!xhr.__completed) {
170
+ xhr.getAllResponseHeaders = function () {
171
+ };
172
+ _this.complete(null, {status: 204, statusText: 'No Content'});
173
+ }
174
+ }, FileAPI.noContentTimeout || 10000);
175
+ }
176
+ },
177
+ headers: xhr.__requestHeaders
178
+ };
179
+ config.data = {};
180
+ config.files = {};
181
+ for (var i = 0; i < formData.data.length; i++) {
182
+ var item = formData.data[i];
183
+ if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) {
184
+ config.files[item.key] = item.val;
185
+ } else {
186
+ config.data[item.key] = item.val;
187
+ }
188
+ }
189
+
190
+ setTimeout(function () {
191
+ if (!FileAPI.hasFlash) {
192
+ throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
193
+ }
194
+ xhr.__fileApiXHR = FileAPI.upload(config);
195
+ }, 1);
196
+ } else {
197
+ if (this.__origError) {
198
+ throw this.__origError;
199
+ }
200
+ orig.apply(xhr, arguments);
201
+ }
202
+ };
203
+ });
204
+ window.XMLHttpRequest.__isFileAPIShim = true;
205
+ window.FormData = FormData = function () {
206
+ return {
207
+ append: function (key, val, name) {
208
+ if (val.__isFileAPIBlobShim) {
209
+ val = val.data[0];
210
+ }
211
+ this.data.push({
212
+ key: key,
213
+ val: val,
214
+ name: name
215
+ });
216
+ },
217
+ data: [],
218
+ __isFileAPIShim: true
219
+ };
220
+ };
221
+
222
+ window.Blob = Blob = function (b) {
223
+ return {
224
+ data: b,
225
+ __isFileAPIBlobShim: true
226
+ };
227
+ };
228
+ }
229
+
230
+ })();
231
+
232
+ (function () {
233
+ /** @namespace FileAPI.forceLoad */
234
+ /** @namespace window.FileAPI.jsUrl */
235
+ /** @namespace window.FileAPI.jsPath */
236
+
237
+ function isInputTypeFile(elem) {
238
+ return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file';
239
+ }
240
+
241
+ function hasFlash() {
242
+ try {
243
+ var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
244
+ if (fo) return true;
245
+ } catch (e) {
246
+ if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true;
247
+ }
248
+ return false;
249
+ }
250
+
251
+ function getOffset(obj) {
252
+ var left = 0, top = 0;
253
+
254
+ if (window.jQuery) {
255
+ return jQuery(obj).offset();
256
+ }
257
+
258
+ if (obj.offsetParent) {
259
+ do {
260
+ left += (obj.offsetLeft - obj.scrollLeft);
261
+ top += (obj.offsetTop - obj.scrollTop);
262
+ obj = obj.offsetParent;
263
+ } while (obj);
264
+ }
265
+ return {
266
+ left: left,
267
+ top: top
268
+ };
269
+ }
270
+
271
+ if (FileAPI.shouldLoad) {
272
+ FileAPI.hasFlash = hasFlash();
273
+
274
+ //load FileAPI
275
+ if (FileAPI.forceLoad) {
276
+ FileAPI.html5 = false;
277
+ }
278
+
279
+ if (!FileAPI.upload) {
280
+ var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src;
281
+ if (window.FileAPI.jsUrl) {
282
+ jsUrl = window.FileAPI.jsUrl;
283
+ } else if (window.FileAPI.jsPath) {
284
+ basePath = window.FileAPI.jsPath;
285
+ } else {
286
+ for (i = 0; i < allScripts.length; i++) {
287
+ src = allScripts[i].src;
288
+ index = src.search(/\/ng\-file\-upload[\-a-zA-z0-9\.]*\.js/);
289
+ if (index > -1) {
290
+ basePath = src.substring(0, index + 1);
291
+ break;
292
+ }
293
+ }
294
+ }
295
+
296
+ if (FileAPI.staticPath == null) FileAPI.staticPath = basePath;
297
+ script.setAttribute('src', jsUrl || basePath + 'FileAPI.js');
298
+ document.getElementsByTagName('head')[0].appendChild(script);
299
+ }
300
+
301
+ FileAPI.ngfFixIE = function (elem, fileElem, changeFn) {
302
+ if (!hasFlash()) {
303
+ throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
304
+ }
305
+ var fixInputStyle = function () {
306
+ if (elem.attr('disabled')) {
307
+ if (fileElem) fileElem.removeClass('js-fileapi-wrapper');
308
+ } else {
309
+ if (!fileElem.attr('__ngf_flash_')) {
310
+ fileElem.unbind('change');
311
+ fileElem.unbind('click');
312
+ fileElem.bind('change', function (evt) {
313
+ fileApiChangeFn.apply(this, [evt]);
314
+ changeFn.apply(this, [evt]);
315
+ });
316
+ fileElem.attr('__ngf_flash_', 'true');
317
+ }
318
+ fileElem.addClass('js-fileapi-wrapper');
319
+ if (!isInputTypeFile(elem)) {
320
+ fileElem.css('position', 'absolute')
321
+ .css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px')
322
+ .css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')
323
+ .css('filter', 'alpha(opacity=0)').css('display', elem.css('display'))
324
+ .css('overflow', 'hidden').css('z-index', '900000')
325
+ .css('visibility', 'visible');
326
+ }
327
+ }
328
+ };
329
+
330
+ elem.bind('mouseenter', fixInputStyle);
331
+
332
+ var fileApiChangeFn = function (evt) {
333
+ var files = FileAPI.getFiles(evt);
334
+ //just a double check for #233
335
+ for (var i = 0; i < files.length; i++) {
336
+ if (files[i].size === undefined) files[i].size = 0;
337
+ if (files[i].name === undefined) files[i].name = 'file';
338
+ if (files[i].type === undefined) files[i].type = 'undefined';
339
+ }
340
+ if (!evt.target) {
341
+ evt.target = {};
342
+ }
343
+ evt.target.files = files;
344
+ // if evt.target.files is not writable use helper field
345
+ if (evt.target.files !== files) {
346
+ evt.__files_ = files;
347
+ }
348
+ (evt.__files_ || evt.target.files).item = function (i) {
349
+ return (evt.__files_ || evt.target.files)[i] || null;
350
+ };
351
+ };
352
+ };
353
+
354
+ FileAPI.disableFileInput = function (elem, disable) {
355
+ if (disable) {
356
+ elem.removeClass('js-fileapi-wrapper');
357
+ } else {
358
+ elem.addClass('js-fileapi-wrapper');
359
+ }
360
+ };
361
+ }
362
+ })();
363
+
364
+ if (!window.FileReader) {
365
+ window.FileReader = function () {
366
+ var _this = this, loadStarted = false;
367
+ this.listeners = {};
368
+ this.addEventListener = function (type, fn) {
369
+ _this.listeners[type] = _this.listeners[type] || [];
370
+ _this.listeners[type].push(fn);
371
+ };
372
+ this.removeEventListener = function (type, fn) {
373
+ if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1);
374
+ };
375
+ this.dispatchEvent = function (evt) {
376
+ var list = _this.listeners[evt.type];
377
+ if (list) {
378
+ for (var i = 0; i < list.length; i++) {
379
+ list[i].call(_this, evt);
380
+ }
381
+ }
382
+ };
383
+ this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null;
384
+
385
+ var constructEvent = function (type, evt) {
386
+ var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error};
387
+ if (evt.result != null) e.target.result = evt.result;
388
+ return e;
389
+ };
390
+ var listener = function (evt) {
391
+ if (!loadStarted) {
392
+ loadStarted = true;
393
+ if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt));
394
+ }
395
+ var e;
396
+ if (evt.type === 'load') {
397
+ if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt));
398
+ e = constructEvent('load', evt);
399
+ if (_this.onload) _this.onload(e);
400
+ _this.dispatchEvent(e);
401
+ } else if (evt.type === 'progress') {
402
+ e = constructEvent('progress', evt);
403
+ if (_this.onprogress) _this.onprogress(e);
404
+ _this.dispatchEvent(e);
405
+ } else {
406
+ e = constructEvent('error', evt);
407
+ if (_this.onerror) _this.onerror(e);
408
+ _this.dispatchEvent(e);
409
+ }
410
+ };
411
+ this.readAsDataURL = function (file) {
412
+ FileAPI.readAsDataURL(file, listener);
413
+ };
414
+ this.readAsText = function (file) {
415
+ FileAPI.readAsText(file, listener);
416
+ };
417
+ };
418
+ }
419
+
420
+ /**!
421
+ * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,
422
+ * progress, resize, thumbnail, preview, validation and CORS
423
+ * @author Danial <danial.farid@gmail.com>
424
+ * @version 10.1.9
425
+ */
426
+
427
+ if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) {
428
+ window.XMLHttpRequest.prototype.setRequestHeader = (function (orig) {
429
+ return function (header, value) {
430
+ if (header === '__setXHR_') {
431
+ var val = value(this);
432
+ // fix for angular < 1.2.0
433
+ if (val instanceof Function) {
434
+ val(this);
435
+ }
436
+ } else {
437
+ orig.apply(this, arguments);
438
+ }
439
+ };
440
+ })(window.XMLHttpRequest.prototype.setRequestHeader);
441
+ }
442
+
443
+ var ngFileUpload = angular.module('ngFileUpload', []);
444
+
445
+ ngFileUpload.version = '10.1.9';
446
+
447
+ ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {
448
+ var upload = this;
449
+
450
+ this.isResumeSupported = function () {
451
+ return window.Blob && (window.Blob instanceof Function) && new window.Blob().slice;
452
+ };
453
+
454
+ var resumeSupported = this.isResumeSupported();
455
+
456
+ function sendHttp(config) {
457
+ config.method = config.method || 'POST';
458
+ config.headers = config.headers || {};
459
+
460
+ var deferred = config._deferred = config._deferred || $q.defer();
461
+ var promise = deferred.promise;
462
+
463
+ function notifyProgress(e) {
464
+ if (deferred.notify) {
465
+ deferred.notify(e);
466
+ }
467
+ if (promise.progressFunc) {
468
+ $timeout(function () {
469
+ promise.progressFunc(e);
470
+ });
471
+ }
472
+ }
473
+
474
+ function getNotifyEvent(n) {
475
+ if (config._start != null && resumeSupported) {
476
+ return {
477
+ loaded: n.loaded + config._start, total: config._file.size, type: n.type, config: config,
478
+ lengthComputable: true, target: n.target
479
+ };
480
+ } else {
481
+ return n;
482
+ }
483
+ }
484
+
485
+ if (!config.disableProgress) {
486
+ config.headers.__setXHR_ = function () {
487
+ return function (xhr) {
488
+ if (!xhr || !(xhr instanceof XMLHttpRequest)) return;
489
+ config.__XHR = xhr;
490
+ if (config.xhrFn) config.xhrFn(xhr);
491
+ xhr.upload.addEventListener('progress', function (e) {
492
+ e.config = config;
493
+ notifyProgress(getNotifyEvent(e));
494
+ }, false);
495
+ //fix for firefox not firing upload progress end, also IE8-9
496
+ xhr.upload.addEventListener('load', function (e) {
497
+ if (e.lengthComputable) {
498
+ e.config = config;
499
+ notifyProgress(getNotifyEvent(e));
500
+ }
501
+ }, false);
502
+ };
503
+ };
504
+ }
505
+
506
+ function uploadWithAngular() {
507
+ $http(config).then(function (r) {
508
+ if (resumeSupported && config._chunkSize && !config._finished) {
509
+ notifyProgress({loaded: config._end, total: config._file.size, config: config, type: 'progress'});
510
+ upload.upload(config, true);
511
+ } else {
512
+ if (config._finished) delete config._finished;
513
+ deferred.resolve(r);
514
+ }
515
+ }, function (e) {
516
+ deferred.reject(e);
517
+ }, function (n) {
518
+ deferred.notify(n);
519
+ });
520
+ }
521
+
522
+ if (!resumeSupported) {
523
+ uploadWithAngular();
524
+ } else if (config._chunkSize && config._end && !config._finished) {
525
+ config._start = config._end;
526
+ config._end += config._chunkSize;
527
+ uploadWithAngular();
528
+ } else if (config.resumeSizeUrl) {
529
+ $http.get(config.resumeSizeUrl).then(function (resp) {
530
+ if (config.resumeSizeResponseReader) {
531
+ config._start = config.resumeSizeResponseReader(resp.data);
532
+ } else {
533
+ config._start = parseInt((resp.data.size == null ? resp.data : resp.data.size).toString());
534
+ }
535
+ if (config._chunkSize) {
536
+ config._end = config._start + config._chunkSize;
537
+ }
538
+ uploadWithAngular();
539
+ }, function (e) {
540
+ throw e;
541
+ });
542
+ } else if (config.resumeSize) {
543
+ config.resumeSize().then(function (size) {
544
+ config._start = size;
545
+ uploadWithAngular();
546
+ }, function (e) {
547
+ throw e;
548
+ });
549
+ } else {
550
+ uploadWithAngular();
551
+ }
552
+
553
+
554
+ promise.success = function (fn) {
555
+ promise.then(function (response) {
556
+ fn(response.data, response.status, response.headers, config);
557
+ });
558
+ return promise;
559
+ };
560
+
561
+ promise.error = function (fn) {
562
+ promise.then(null, function (response) {
563
+ fn(response.data, response.status, response.headers, config);
564
+ });
565
+ return promise;
566
+ };
567
+
568
+ promise.progress = function (fn) {
569
+ promise.progressFunc = fn;
570
+ promise.then(null, null, function (n) {
571
+ fn(n);
572
+ });
573
+ return promise;
574
+ };
575
+ promise.abort = promise.pause = function () {
576
+ if (config.__XHR) {
577
+ $timeout(function () {
578
+ config.__XHR.abort();
579
+ });
580
+ }
581
+ return promise;
582
+ };
583
+ promise.xhr = function (fn) {
584
+ config.xhrFn = (function (origXhrFn) {
585
+ return function () {
586
+ if (origXhrFn) origXhrFn.apply(promise, arguments);
587
+ fn.apply(promise, arguments);
588
+ };
589
+ })(config.xhrFn);
590
+ return promise;
591
+ };
592
+
593
+ return promise;
594
+ }
595
+
596
+ this.rename = function (file, name) {
597
+ file.ngfName = name;
598
+ return file;
599
+ };
600
+
601
+ this.jsonBlob = function (val) {
602
+ if (val != null && !angular.isString(val)) {
603
+ val = JSON.stringify(val);
604
+ }
605
+ var blob = new window.Blob([val], {type: 'application/json'});
606
+ blob._ngfBlob = true;
607
+ return blob;
608
+ };
609
+
610
+ this.json = function (val) {
611
+ return angular.toJson(val);
612
+ };
613
+
614
+ function copy(obj) {
615
+ var clone = {};
616
+ for (var key in obj) {
617
+ if (obj.hasOwnProperty(key)) {
618
+ clone[key] = obj[key];
619
+ }
620
+ }
621
+ return clone;
622
+ }
623
+
624
+ this.upload = function (config, internal) {
625
+ function isFile(file) {
626
+ return file != null && (file instanceof window.Blob || (file.flashId && file.name && file.size));
627
+ }
628
+
629
+ function toResumeFile(file, formData) {
630
+ if (file._ngfBlob) return file;
631
+ config._file = config._file || file;
632
+ if (config._start != null && resumeSupported) {
633
+ if (config._end && config._end >= file.size) {
634
+ config._finished = true;
635
+ config._end = file.size;
636
+ }
637
+ var slice = file.slice(config._start, config._end || file.size);
638
+ slice.name = file.name;
639
+ slice.ngfName = file.ngfName;
640
+ if (config._chunkSize) {
641
+ formData.append('_chunkSize', config._end - config._start);
642
+ formData.append('_chunkNumber', Math.floor(config._start / config._chunkSize));
643
+ formData.append('_totalSize', config._file.size);
644
+ }
645
+ return slice;
646
+ }
647
+ return file;
648
+ }
649
+
650
+ function addFieldToFormData(formData, val, key) {
651
+ if (val !== undefined) {
652
+ if (angular.isDate(val)) {
653
+ val = val.toISOString();
654
+ }
655
+ if (angular.isString(val)) {
656
+ formData.append(key, val);
657
+ } else if (isFile(val)) {
658
+ var file = toResumeFile(val, formData);
659
+ var split = key.split(',');
660
+ if (split[1]) {
661
+ file.ngfName = split[1].replace(/^\s+|\s+$/g, '');
662
+ key = split[0];
663
+ }
664
+ config._fileKey = config._fileKey || key;
665
+ formData.append(key, file, file.ngfName || file.name);
666
+ } else {
667
+ if (angular.isObject(val)) {
668
+ if (val.$$ngfCircularDetection) throw 'ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: ' + key;
669
+
670
+ val.$$ngfCircularDetection = true;
671
+ try {
672
+ for (var k in val) {
673
+ if (val.hasOwnProperty(k) && k !== '$$ngfCircularDetection') {
674
+ var objectKey = config.objectKey == null ? '[i]' : config.objectKey;
675
+ if (val.length && parseInt(k) > -1) {
676
+ objectKey = config.arrayKey == null ? objectKey : config.arrayKey;
677
+ }
678
+ addFieldToFormData(formData, val[k], key + objectKey.replace(/[ik]/g, k));
679
+ }
680
+ }
681
+ } finally {
682
+ delete val.$$ngfCircularDetection;
683
+ }
684
+ } else {
685
+ formData.append(key, val);
686
+ }
687
+ }
688
+ }
689
+ }
690
+
691
+ function digestConfig() {
692
+ config._chunkSize = upload.translateScalars(config.resumeChunkSize);
693
+ config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;
694
+
695
+ config.headers = config.headers || {};
696
+ config.headers['Content-Type'] = undefined;
697
+ config.transformRequest = config.transformRequest ?
698
+ (angular.isArray(config.transformRequest) ?
699
+ config.transformRequest : [config.transformRequest]) : [];
700
+ config.transformRequest.push(function (data) {
701
+ var formData = new window.FormData(), key;
702
+ data = data || config.fields || {};
703
+ if (config.file) {
704
+ data.file = config.file;
705
+ }
706
+ for (key in data) {
707
+ if (data.hasOwnProperty(key)) {
708
+ var val = data[key];
709
+ if (config.formDataAppender) {
710
+ config.formDataAppender(formData, key, val);
711
+ } else {
712
+ addFieldToFormData(formData, val, key);
713
+ }
714
+ }
715
+ }
716
+
717
+ return formData;
718
+ });
719
+ }
720
+
721
+ if (!internal) config = copy(config);
722
+ if (!config._isDigested) {
723
+ config._isDigested = true;
724
+ digestConfig();
725
+ }
726
+
727
+ return sendHttp(config);
728
+ };
729
+
730
+ this.http = function (config) {
731
+ config = copy(config);
732
+ config.transformRequest = config.transformRequest || function (data) {
733
+ if ((window.ArrayBuffer && data instanceof window.ArrayBuffer) || data instanceof window.Blob) {
734
+ return data;
735
+ }
736
+ return $http.defaults.transformRequest[0].apply(this, arguments);
737
+ };
738
+ config._chunkSize = upload.translateScalars(config.resumeChunkSize);
739
+ config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;
740
+
741
+ return sendHttp(config);
742
+ };
743
+
744
+ this.translateScalars = function (str) {
745
+ if (angular.isString(str)) {
746
+ if (str.search(/kb/i) === str.length - 2) {
747
+ return parseFloat(str.substring(0, str.length - 2) * 1000);
748
+ } else if (str.search(/mb/i) === str.length - 2) {
749
+ return parseFloat(str.substring(0, str.length - 2) * 1000000);
750
+ } else if (str.search(/gb/i) === str.length - 2) {
751
+ return parseFloat(str.substring(0, str.length - 2) * 1000000000);
752
+ } else if (str.search(/b/i) === str.length - 1) {
753
+ return parseFloat(str.substring(0, str.length - 1));
754
+ } else if (str.search(/s/i) === str.length - 1) {
755
+ return parseFloat(str.substring(0, str.length - 1));
756
+ } else if (str.search(/m/i) === str.length - 1) {
757
+ return parseFloat(str.substring(0, str.length - 1) * 60);
758
+ } else if (str.search(/h/i) === str.length - 1) {
759
+ return parseFloat(str.substring(0, str.length - 1) * 3600);
760
+ }
761
+ }
762
+ return str;
763
+ };
764
+
765
+ this.setDefaults = function (defaults) {
766
+ this.defaults = defaults || {};
767
+ };
768
+
769
+ this.defaults = {};
770
+ this.version = ngFileUpload.version;
771
+ }
772
+
773
+ ]);
774
+
775
+ ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadExif', function ($parse, $timeout, $compile, $q, UploadExif) {
776
+ var upload = UploadExif;
777
+ upload.getAttrWithDefaults = function (attr, name) {
778
+ if (attr[name] != null) return attr[name];
779
+ var def = upload.defaults[name];
780
+ return (def == null ? def : (angular.isString(def) ? def : JSON.stringify(def)));
781
+ };
782
+
783
+ upload.attrGetter = function (name, attr, scope, params) {
784
+ var attrVal = this.getAttrWithDefaults(attr, name);
785
+ if (scope) {
786
+ try {
787
+ if (params) {
788
+ return $parse(attrVal)(scope, params);
789
+ } else {
790
+ return $parse(attrVal)(scope);
791
+ }
792
+ } catch (e) {
793
+ // hangle string value without single qoute
794
+ if (name.search(/min|max|pattern/i)) {
795
+ return attrVal;
796
+ } else {
797
+ throw e;
798
+ }
799
+ }
800
+ } else {
801
+ return attrVal;
802
+ }
803
+ };
804
+
805
+ upload.shouldUpdateOn = function (type, attr, scope) {
806
+ var modelOptions = upload.attrGetter('ngModelOptions', attr, scope);
807
+ if (modelOptions && modelOptions.updateOn) {
808
+ return modelOptions.updateOn.split(' ').indexOf(type) > -1;
809
+ }
810
+ return true;
811
+ };
812
+
813
+ upload.emptyPromise = function () {
814
+ var d = $q.defer();
815
+ var args = arguments;
816
+ $timeout(function () {
817
+ d.resolve.apply(d, args);
818
+ });
819
+ return d.promise;
820
+ };
821
+
822
+ upload.happyPromise = function (promise, data) {
823
+ var d = $q.defer();
824
+ promise.then(function (result) {
825
+ d.resolve(result);
826
+ }, function (error) {
827
+ $timeout(function () {
828
+ throw error;
829
+ });
830
+ d.resolve(data);
831
+ });
832
+ return d.promise;
833
+ };
834
+
835
+ function applyExifRotations(files, attr, scope) {
836
+ var promises = [upload.emptyPromise()];
837
+ angular.forEach(files, function (f, i) {
838
+ if (f.type.indexOf('image/jpeg') === 0 && upload.attrGetter('ngfFixOrientation', attr, scope, {$file: f})) {
839
+ promises.push(upload.happyPromise(upload.applyExifRotation(f), f).then(function (fixedFile) {
840
+ files.splice(i, 1, fixedFile);
841
+ }));
842
+ }
843
+ });
844
+ return $q.all(promises);
845
+ }
846
+
847
+ function resize(files, attr, scope) {
848
+ var param = upload.attrGetter('ngfResize', attr, scope);
849
+ if (!param || !upload.isResizeSupported() || !files.length) return upload.emptyPromise();
850
+ var promises = [upload.emptyPromise()];
851
+ angular.forEach(files, function (f, i) {
852
+ if (f.type.indexOf('image') === 0) {
853
+ if (param.pattern && !upload.validatePattern(f, param.pattern)) return;
854
+ var promise = upload.resize(f, param.width, param.height, param.quality,
855
+ param.type, param.ratio, param.centerCrop);
856
+ promises.push(promise);
857
+ promise.then(function (resizedFile) {
858
+ files.splice(i, 1, resizedFile);
859
+ }, function (e) {
860
+ f.$error = 'resize';
861
+ f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name);
862
+ });
863
+ }
864
+ });
865
+ return $q.all(promises);
866
+ }
867
+
868
+ function handleKeep(files, prevFiles, attr, scope) {
869
+ var dupFiles = [];
870
+ var keep = upload.attrGetter('ngfKeep', attr, scope);
871
+ if (keep) {
872
+ var hasNew = false;
873
+
874
+ if (keep === 'distinct' || upload.attrGetter('ngfKeepDistinct', attr, scope) === true) {
875
+ var len = prevFiles.length;
876
+ if (files) {
877
+ for (var i = 0; i < files.length; i++) {
878
+ for (var j = 0; j < len; j++) {
879
+ if (files[i].name === prevFiles[j].name) {
880
+ dupFiles.push(files[i]);
881
+ break;
882
+ }
883
+ }
884
+ if (j === len) {
885
+ prevFiles.push(files[i]);
886
+ hasNew = true;
887
+ }
888
+ }
889
+ }
890
+ files = prevFiles;
891
+ } else {
892
+ files = prevFiles.concat(files || []);
893
+ }
894
+ }
895
+ return {files: files, dupFiles: dupFiles, keep: keep};
896
+ }
897
+
898
+ upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) {
899
+ function update(files, invalidFiles, newFiles, dupFiles, isSingleModel) {
900
+ var file = files && files.length ? files[0] : null;
901
+
902
+ if (ngModel) {
903
+ upload.applyModelValidation(ngModel, files);
904
+ ngModel.$ngfModelChange = true;
905
+ ngModel.$setViewValue(isSingleModel ? file : files);
906
+ }
907
+
908
+ if (fileChange) {
909
+ $parse(fileChange)(scope, {
910
+ $files: files,
911
+ $file: file,
912
+ $newFiles: newFiles,
913
+ $duplicateFiles: dupFiles,
914
+ $invalidFiles: invalidFiles,
915
+ $event: evt
916
+ });
917
+ }
918
+
919
+ var invalidModel = upload.attrGetter('ngfModelInvalid', attr);
920
+ if (invalidModel) {
921
+ $timeout(function () {
922
+ $parse(invalidModel).assign(scope, invalidFiles);
923
+ });
924
+ }
925
+ $timeout(function () {
926
+ // scope apply changes
927
+ });
928
+ }
929
+
930
+ var newFiles = files;
931
+ var prevFiles = ngModel && ngModel.$modelValue && (angular.isArray(ngModel.$modelValue) ?
932
+ ngModel.$modelValue : [ngModel.$modelValue]);
933
+ prevFiles = (prevFiles || attr.$$ngfPrevFiles || []).slice(0);
934
+ var keepResult = handleKeep(files, prevFiles, attr, scope);
935
+ files = keepResult.files;
936
+ var dupFiles = keepResult.dupFiles;
937
+ var isSingleModel = !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr) && !keepResult.keep;
938
+
939
+ attr.$$ngfPrevFiles = files;
940
+
941
+ if (keepResult.keep && (!newFiles || !newFiles.length)) return;
942
+
943
+ upload.attrGetter('ngfBeforeModelChange', attr, scope, {$files: files,
944
+ $file: files && files.length ? files[0] : null,
945
+ $duplicateFiles: dupFiles,
946
+ $event: evt});
947
+
948
+ upload.validate(newFiles, ngModel, attr, scope).then(function () {
949
+ if (noDelay) {
950
+ update(files, [], newFiles, dupFiles, isSingleModel);
951
+ } else {
952
+ var options = upload.attrGetter('ngModelOptions', attr, scope);
953
+ if (!options || !options.allowInvalid) {
954
+ var valids = [], invalids = [];
955
+ angular.forEach(files, function (file) {
956
+ if (file.$error) {
957
+ invalids.push(file);
958
+ } else {
959
+ valids.push(file);
960
+ }
961
+ });
962
+ files = valids;
963
+ }
964
+ var fixOrientation = upload.emptyPromise(files);
965
+ if (upload.attrGetter('ngfFixOrientation', attr, scope) && upload.isExifSupported()) {
966
+ fixOrientation = applyExifRotations(files, attr, scope);
967
+ }
968
+ fixOrientation.then(function () {
969
+ resize(files, attr, scope).then(function () {
970
+ $timeout(function () {
971
+ update(files, invalids, newFiles, dupFiles, isSingleModel);
972
+ }, options && options.debounce ? options.debounce.change || options.debounce : 0);
973
+ }, function (e) {
974
+ throw 'Could not resize files ' + e;
975
+ });
976
+ });
977
+ }
978
+ });
979
+
980
+ // cleaning object url memories
981
+ var l = prevFiles.length;
982
+ while (l--) {
983
+ var prevFile = prevFiles[l];
984
+ if (window.URL && prevFile.blobUrl) {
985
+ URL.revokeObjectURL(prevFile.blobUrl);
986
+ delete prevFile.blobUrl;
987
+ }
988
+ }
989
+ };
990
+
991
+ return upload;
992
+ }]);
993
+
994
+ ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) {
995
+ var generatedElems = [];
996
+
997
+ function isDelayedClickSupported(ua) {
998
+ // fix for android native browser < 4.4 and safari windows
999
+ var m = ua.match(/Android[^\d]*(\d+)\.(\d+)/);
1000
+ if (m && m.length > 2) {
1001
+ var v = Upload.defaults.androidFixMinorVersion || 4;
1002
+ return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v);
1003
+ }
1004
+
1005
+ // safari on windows
1006
+ return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua);
1007
+ }
1008
+
1009
+ function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) {
1010
+ /** @namespace attr.ngfSelect */
1011
+ /** @namespace attr.ngfChange */
1012
+ /** @namespace attr.ngModel */
1013
+ /** @namespace attr.ngModelOptions */
1014
+ /** @namespace attr.ngfMultiple */
1015
+ /** @namespace attr.ngfCapture */
1016
+ /** @namespace attr.ngfValidate */
1017
+ /** @namespace attr.ngfKeep */
1018
+ var attrGetter = function (name, scope) {
1019
+ return upload.attrGetter(name, attr, scope);
1020
+ };
1021
+
1022
+ function isInputTypeFile() {
1023
+ return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file';
1024
+ }
1025
+
1026
+ function fileChangeAttr() {
1027
+ return attrGetter('ngfChange') || attrGetter('ngfSelect');
1028
+ }
1029
+
1030
+ function changeFn(evt) {
1031
+ if (upload.shouldUpdateOn('change', attr, scope)) {
1032
+ var fileList = evt.__files_ || (evt.target && evt.target.files), files = [];
1033
+ for (var i = 0; i < fileList.length; i++) {
1034
+ files.push(fileList[i]);
1035
+ }
1036
+ upload.updateModel(ngModel, attr, scope, fileChangeAttr(),
1037
+ files.length ? files : null, evt);
1038
+ }
1039
+ }
1040
+
1041
+ upload.registerModelChangeValidator(ngModel, attr, scope);
1042
+
1043
+ var unwatches = [];
1044
+ unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () {
1045
+ fileElem.attr('multiple', attrGetter('ngfMultiple', scope));
1046
+ }));
1047
+ unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () {
1048
+ fileElem.attr('capture', attrGetter('ngfCapture', scope));
1049
+ }));
1050
+ unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () {
1051
+ fileElem.attr('accept', attrGetter('ngfAccept', scope));
1052
+ }));
1053
+ attr.$observe('accept', function () {
1054
+ fileElem.attr('accept', attrGetter('accept'));
1055
+ });
1056
+ unwatches.push(function () {
1057
+ if (attr.$$observers) delete attr.$$observers.accept;
1058
+ });
1059
+ function bindAttrToFileInput(fileElem) {
1060
+ if (elem !== fileElem) {
1061
+ for (var i = 0; i < elem[0].attributes.length; i++) {
1062
+ var attribute = elem[0].attributes[i];
1063
+ if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') {
1064
+ if (attribute.value == null || attribute.value === '') {
1065
+ if (attribute.name === 'required') attribute.value = 'required';
1066
+ if (attribute.name === 'multiple') attribute.value = 'multiple';
1067
+ }
1068
+ fileElem.attr(attribute.name, attribute.name === 'id' ? 'ngf-' + attribute.value : attribute.value);
1069
+ }
1070
+ }
1071
+ }
1072
+ }
1073
+
1074
+ function createFileInput() {
1075
+ if (isInputTypeFile()) {
1076
+ return elem;
1077
+ }
1078
+
1079
+ var fileElem = angular.element('<input type="file">');
1080
+
1081
+ bindAttrToFileInput(fileElem);
1082
+
1083
+ var label = angular.element('<label>upload</label>');
1084
+ label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden')
1085
+ .css('width', '0px').css('height', '0px').css('border', 'none')
1086
+ .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1');
1087
+ generatedElems.push({el: elem, ref: label});
1088
+
1089
+ document.body.appendChild(label.append(fileElem)[0]);
1090
+
1091
+ return fileElem;
1092
+ }
1093
+
1094
+ var initialTouchStartY = 0;
1095
+
1096
+ function clickHandler(evt) {
1097
+ if (elem.attr('disabled') || attrGetter('ngfSelectDisabled', scope)) return false;
1098
+
1099
+ var r = handleTouch(evt);
1100
+ if (r != null) return r;
1101
+
1102
+ resetModel(evt);
1103
+
1104
+ // fix for md when the element is removed from the DOM and added back #460
1105
+ try {
1106
+ if (!isInputTypeFile() && !document.body.contains(fileElem[0])) {
1107
+ generatedElems.push({el: elem, ref: fileElem.parent()});
1108
+ document.body.appendChild(fileElem[0].parent());
1109
+ fileElem.bind('change', changeFn);
1110
+ }
1111
+ } catch(e){/*ignore*/}
1112
+
1113
+ if (isDelayedClickSupported(navigator.userAgent)) {
1114
+ setTimeout(function () {
1115
+ fileElem[0].click();
1116
+ }, 0);
1117
+ } else {
1118
+ fileElem[0].click();
1119
+ }
1120
+
1121
+ return false;
1122
+ }
1123
+
1124
+ function handleTouch(evt) {
1125
+ var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches);
1126
+ if (evt.type === 'touchstart') {
1127
+ initialTouchStartY = touches ? touches[0].clientY : 0;
1128
+ return true; // don't block event default
1129
+ } else {
1130
+ evt.stopPropagation();
1131
+ evt.preventDefault();
1132
+
1133
+ // prevent scroll from triggering event
1134
+ if (evt.type === 'touchend') {
1135
+ var currentLocation = touches ? touches[0].clientY : 0;
1136
+ if (Math.abs(currentLocation - initialTouchStartY) > 20) return false;
1137
+ }
1138
+ }
1139
+ }
1140
+
1141
+ var fileElem = elem;
1142
+
1143
+ function resetModel(evt) {
1144
+ if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) {
1145
+ fileElem.val(null);
1146
+ upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true);
1147
+ }
1148
+ }
1149
+
1150
+ if (!isInputTypeFile()) {
1151
+ fileElem = createFileInput();
1152
+ }
1153
+ fileElem.bind('change', changeFn);
1154
+
1155
+ if (!isInputTypeFile()) {
1156
+ elem.bind('click touchstart touchend', clickHandler);
1157
+ } else {
1158
+ elem.bind('click', resetModel);
1159
+ }
1160
+
1161
+ function ie10SameFileSelectFix(evt) {
1162
+ if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) {
1163
+ if (!fileElem[0].parentNode) {
1164
+ fileElem = null;
1165
+ return;
1166
+ }
1167
+ evt.preventDefault();
1168
+ evt.stopPropagation();
1169
+ fileElem.unbind('click');
1170
+ var clone = fileElem.clone();
1171
+ fileElem.replaceWith(clone);
1172
+ fileElem = clone;
1173
+ fileElem.attr('__ngf_ie10_Fix_', 'true');
1174
+ fileElem.bind('change', changeFn);
1175
+ fileElem.bind('click', ie10SameFileSelectFix);
1176
+ fileElem[0].click();
1177
+ return false;
1178
+ } else {
1179
+ fileElem.removeAttr('__ngf_ie10_Fix_');
1180
+ }
1181
+ }
1182
+
1183
+ if (navigator.appVersion.indexOf('MSIE 10') !== -1) {
1184
+ fileElem.bind('click', ie10SameFileSelectFix);
1185
+ }
1186
+
1187
+ if (ngModel) ngModel.$formatters.push(function (val) {
1188
+ if (val == null || val.length === 0) {
1189
+ if (fileElem.val()) {
1190
+ fileElem.val(null);
1191
+ }
1192
+ }
1193
+ return val;
1194
+ });
1195
+
1196
+ scope.$on('$destroy', function () {
1197
+ if (!isInputTypeFile()) fileElem.parent().remove();
1198
+ angular.forEach(unwatches, function (unwatch) {
1199
+ unwatch();
1200
+ });
1201
+ });
1202
+
1203
+ $timeout(function () {
1204
+ for (var i = 0; i < generatedElems.length; i++) {
1205
+ var g = generatedElems[i];
1206
+ if (!document.body.contains(g.el[0])) {
1207
+ generatedElems.splice(i, 1);
1208
+ g.ref.remove();
1209
+ }
1210
+ }
1211
+ });
1212
+
1213
+ if (window.FileAPI && window.FileAPI.ngfFixIE) {
1214
+ window.FileAPI.ngfFixIE(elem, fileElem, changeFn);
1215
+ }
1216
+ }
1217
+
1218
+ return {
1219
+ restrict: 'AEC',
1220
+ require: '?ngModel',
1221
+ link: function (scope, elem, attr, ngModel) {
1222
+ linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload);
1223
+ }
1224
+ };
1225
+ }]);
1226
+
1227
+ (function () {
1228
+
1229
+ ngFileUpload.service('UploadDataUrl', ['UploadBase', '$timeout', '$q', function (UploadBase, $timeout, $q) {
1230
+ var upload = UploadBase;
1231
+ upload.base64DataUrl = function (file) {
1232
+ if (angular.isArray(file)) {
1233
+ var d = $q.defer(), count = 0;
1234
+ angular.forEach(file, function (f) {
1235
+ upload.dataUrl(f, true)['finally'](function () {
1236
+ count++;
1237
+ if (count === file.length) {
1238
+ var urls = [];
1239
+ angular.forEach(file, function (ff) {
1240
+ urls.push(ff.$ngfDataUrl);
1241
+ });
1242
+ d.resolve(urls, file);
1243
+ }
1244
+ });
1245
+ });
1246
+ return d.promise;
1247
+ } else {
1248
+ return upload.dataUrl(file, true);
1249
+ }
1250
+ };
1251
+ upload.dataUrl = function (file, disallowObjectUrl) {
1252
+ if (!file) return upload.emptyPromise(file, file);
1253
+ if ((disallowObjectUrl && file.$ngfDataUrl != null) || (!disallowObjectUrl && file.$ngfBlobUrl != null)) {
1254
+ return upload.emptyPromise(disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl, file);
1255
+ }
1256
+ var p = disallowObjectUrl ? file.$$ngfDataUrlPromise : file.$$ngfBlobUrlPromise;
1257
+ if (p) return p;
1258
+
1259
+ var deferred = $q.defer();
1260
+ $timeout(function () {
1261
+ if (window.FileReader && file &&
1262
+ (!window.FileAPI || navigator.userAgent.indexOf('MSIE 8') === -1 || file.size < 20000) &&
1263
+ (!window.FileAPI || navigator.userAgent.indexOf('MSIE 9') === -1 || file.size < 4000000)) {
1264
+ //prefer URL.createObjectURL for handling refrences to files of all sizes
1265
+ //since it doesn´t build a large string in memory
1266
+ var URL = window.URL || window.webkitURL;
1267
+ if (URL && URL.createObjectURL && !disallowObjectUrl) {
1268
+ var url;
1269
+ try {
1270
+ url = URL.createObjectURL(file);
1271
+ } catch (e) {
1272
+ $timeout(function () {
1273
+ file.$ngfBlobUrl = '';
1274
+ deferred.reject();
1275
+ });
1276
+ return;
1277
+ }
1278
+ $timeout(function () {
1279
+ file.$ngfBlobUrl = url;
1280
+ if (url) deferred.resolve(url, file);
1281
+ });
1282
+ } else {
1283
+ var fileReader = new FileReader();
1284
+ fileReader.onload = function (e) {
1285
+ $timeout(function () {
1286
+ file.$ngfDataUrl = e.target.result;
1287
+ deferred.resolve(e.target.result, file);
1288
+ });
1289
+ };
1290
+ fileReader.onerror = function () {
1291
+ $timeout(function () {
1292
+ file.$ngfDataUrl = '';
1293
+ deferred.reject();
1294
+ });
1295
+ };
1296
+ fileReader.readAsDataURL(file);
1297
+ }
1298
+ } else {
1299
+ $timeout(function () {
1300
+ file[disallowObjectUrl ? 'dataUrl' : 'blobUrl'] = '';
1301
+ deferred.reject();
1302
+ });
1303
+ }
1304
+ });
1305
+
1306
+ if (disallowObjectUrl) {
1307
+ p = file.$$ngfDataUrlPromise = deferred.promise;
1308
+ } else {
1309
+ p = file.$$ngfBlobUrlPromise = deferred.promise;
1310
+ }
1311
+ p['finally'](function () {
1312
+ delete file[disallowObjectUrl ? '$$ngfDataUrlPromise' : '$$ngfBlobUrlPromise'];
1313
+ });
1314
+ return p;
1315
+ };
1316
+ return upload;
1317
+ }]);
1318
+
1319
+ function getTagType(el) {
1320
+ if (el.tagName.toLowerCase() === 'img') return 'image';
1321
+ if (el.tagName.toLowerCase() === 'audio') return 'audio';
1322
+ if (el.tagName.toLowerCase() === 'video') return 'video';
1323
+ return /./;
1324
+ }
1325
+
1326
+ function linkFileDirective(Upload, $timeout, scope, elem, attr, directiveName, resizeParams, isBackground) {
1327
+ function constructDataUrl(file) {
1328
+ var disallowObjectUrl = Upload.attrGetter('ngfNoObjectUrl', attr, scope);
1329
+ Upload.dataUrl(file, disallowObjectUrl)['finally'](function () {
1330
+ $timeout(function () {
1331
+ var src = (disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl;
1332
+ if (isBackground) {
1333
+ elem.css('background-image', 'url(\'' + (src || '') + '\')');
1334
+ } else {
1335
+ elem.attr('src', src);
1336
+ }
1337
+ if (src) {
1338
+ elem.removeClass('ng-hide');
1339
+ } else {
1340
+ elem.addClass('ng-hide');
1341
+ }
1342
+ });
1343
+ });
1344
+ }
1345
+
1346
+ $timeout(function () {
1347
+ var unwatch = scope.$watch(attr[directiveName], function (file) {
1348
+ var size = resizeParams;
1349
+ if (directiveName === 'ngfThumbnail') {
1350
+ if (!size) {
1351
+ size = {width: elem[0].clientWidth, height: elem[0].clientHeight};
1352
+ }
1353
+ if (size.width === 0 && window.getComputedStyle) {
1354
+ var style = getComputedStyle(elem[0]);
1355
+ size = {
1356
+ width: parseInt(style.width.slice(0, -2)),
1357
+ height: parseInt(style.height.slice(0, -2))
1358
+ };
1359
+ }
1360
+ }
1361
+
1362
+ if (angular.isString(file)) {
1363
+ elem.removeClass('ng-hide');
1364
+ if (isBackground) {
1365
+ return elem.css('background-image', 'url(\'' + file + '\')');
1366
+ } else {
1367
+ return elem.attr('src', file);
1368
+ }
1369
+ }
1370
+ if (file && file.type && file.type.search(getTagType(elem[0])) === 0 &&
1371
+ (!isBackground || file.type.indexOf('image') === 0)) {
1372
+ if (size && Upload.isResizeSupported()) {
1373
+ Upload.resize(file, size.width, size.height, size.quality).then(
1374
+ function (f) {
1375
+ constructDataUrl(f);
1376
+ }, function (e) {
1377
+ throw e;
1378
+ }
1379
+ );
1380
+ } else {
1381
+ constructDataUrl(file);
1382
+ }
1383
+ } else {
1384
+ elem.addClass('ng-hide');
1385
+ }
1386
+ });
1387
+
1388
+ scope.$on('$destroy', function () {
1389
+ unwatch();
1390
+ });
1391
+ });
1392
+ }
1393
+
1394
+
1395
+ /** @namespace attr.ngfSrc */
1396
+ /** @namespace attr.ngfNoObjectUrl */
1397
+ ngFileUpload.directive('ngfSrc', ['Upload', '$timeout', function (Upload, $timeout) {
1398
+ return {
1399
+ restrict: 'AE',
1400
+ link: function (scope, elem, attr) {
1401
+ linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfSrc',
1402
+ Upload.attrGetter('ngfResize', attr, scope), false);
1403
+ }
1404
+ };
1405
+ }]);
1406
+
1407
+ /** @namespace attr.ngfBackground */
1408
+ /** @namespace attr.ngfNoObjectUrl */
1409
+ ngFileUpload.directive('ngfBackground', ['Upload', '$timeout', function (Upload, $timeout) {
1410
+ return {
1411
+ restrict: 'AE',
1412
+ link: function (scope, elem, attr) {
1413
+ linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfBackground',
1414
+ Upload.attrGetter('ngfResize', attr, scope), true);
1415
+ }
1416
+ };
1417
+ }]);
1418
+
1419
+ /** @namespace attr.ngfThumbnail */
1420
+ /** @namespace attr.ngfAsBackground */
1421
+ /** @namespace attr.ngfSize */
1422
+ /** @namespace attr.ngfNoObjectUrl */
1423
+ ngFileUpload.directive('ngfThumbnail', ['Upload', '$timeout', function (Upload, $timeout) {
1424
+ return {
1425
+ restrict: 'AE',
1426
+ link: function (scope, elem, attr) {
1427
+ var size = Upload.attrGetter('ngfSize', attr, scope);
1428
+ linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfThumbnail', size,
1429
+ Upload.attrGetter('ngfAsBackground', attr, scope));
1430
+ }
1431
+ };
1432
+ }]);
1433
+
1434
+ ngFileUpload.config(['$compileProvider', function ($compileProvider) {
1435
+ if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|local|file|data|blob):/);
1436
+ if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|local|file|data|blob):/);
1437
+ }]);
1438
+
1439
+ ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) {
1440
+ return function (file, disallowObjectUrl, trustedUrl) {
1441
+ if (angular.isString(file)) {
1442
+ return $sce.trustAsResourceUrl(file);
1443
+ }
1444
+ var src = file && ((disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl);
1445
+ if (file && !src) {
1446
+ if (!file.$ngfDataUrlFilterInProgress && angular.isObject(file)) {
1447
+ file.$ngfDataUrlFilterInProgress = true;
1448
+ UploadDataUrl.dataUrl(file, disallowObjectUrl);
1449
+ }
1450
+ return '';
1451
+ }
1452
+ if (file) delete file.$ngfDataUrlFilterInProgress;
1453
+ return (file && src ? (trustedUrl ? $sce.trustAsResourceUrl(src) : src) : file) || '';
1454
+ };
1455
+ }]);
1456
+
1457
+ })();
1458
+
1459
+ ngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', function (UploadDataUrl, $q, $timeout) {
1460
+ var upload = UploadDataUrl;
1461
+
1462
+ function globStringToRegex(str) {
1463
+ var regexp = '', excludes = [];
1464
+ if (str.length > 2 && str[0] === '/' && str[str.length - 1] === '/') {
1465
+ regexp = str.substring(1, str.length - 1);
1466
+ } else {
1467
+ var split = str.split(',');
1468
+ if (split.length > 1) {
1469
+ for (var i = 0; i < split.length; i++) {
1470
+ var r = globStringToRegex(split[i]);
1471
+ if (r.regexp) {
1472
+ regexp += '(' + r.regexp + ')';
1473
+ if (i < split.length - 1) {
1474
+ regexp += '|';
1475
+ }
1476
+ } else {
1477
+ excludes = excludes.concat(r.excludes);
1478
+ }
1479
+ }
1480
+ } else {
1481
+ if (str.indexOf('!') === 0) {
1482
+ excludes.push('^((?!' + globStringToRegex(str.substring(1)).regexp + ').)*$');
1483
+ } else {
1484
+ if (str.indexOf('.') === 0) {
1485
+ str = '*' + str;
1486
+ }
1487
+ regexp = '^' + str.replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&') + '$';
1488
+ regexp = regexp.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
1489
+ }
1490
+ }
1491
+ }
1492
+ return {regexp: regexp, excludes: excludes};
1493
+ }
1494
+
1495
+ upload.validatePattern = function (file, val) {
1496
+ if (!val) {
1497
+ return true;
1498
+ }
1499
+ var pattern = globStringToRegex(val), valid = true;
1500
+ if (pattern.regexp && pattern.regexp.length) {
1501
+ var regexp = new RegExp(pattern.regexp, 'i');
1502
+ valid = (file.type != null && regexp.test(file.type)) ||
1503
+ (file.name != null && regexp.test(file.name));
1504
+ }
1505
+ var len = pattern.excludes.length;
1506
+ while (len--) {
1507
+ var exclude = new RegExp(pattern.excludes[len], 'i');
1508
+ valid = valid && (file.type == null || exclude.test(file.type)) &&
1509
+ (file.name == null || exclude.test(file.name));
1510
+ }
1511
+ return valid;
1512
+ };
1513
+
1514
+ upload.ratioToFloat = function(val) {
1515
+ var r = val.toString(), xIndex = r.search(/[x:]/i);
1516
+ if (xIndex > -1) {
1517
+ r = parseFloat(r.substring(0, xIndex)) / parseFloat(r.substring(xIndex + 1));
1518
+ } else {
1519
+ r = parseFloat(r);
1520
+ }
1521
+ return r;
1522
+ };
1523
+
1524
+ upload.registerModelChangeValidator = function (ngModel, attr, scope) {
1525
+ if (ngModel) {
1526
+ ngModel.$formatters.push(function (files) {
1527
+ if (!ngModel.$ngfModelChange) {
1528
+ upload.validate(files, ngModel, attr, scope, function () {
1529
+ upload.applyModelValidation(ngModel, files);
1530
+ });
1531
+ } else {
1532
+ ngModel.$ngfModelChange = false;
1533
+ }
1534
+ });
1535
+ }
1536
+ };
1537
+
1538
+ function markModelAsDirty(ngModel, files) {
1539
+ if (files != null && !ngModel.$dirty) {
1540
+ if (ngModel.$setDirty) {
1541
+ ngModel.$setDirty();
1542
+ } else {
1543
+ ngModel.$dirty = true;
1544
+ }
1545
+ }
1546
+ }
1547
+
1548
+ upload.applyModelValidation = function (ngModel, files) {
1549
+ markModelAsDirty(ngModel, files);
1550
+ angular.forEach(ngModel.$ngfValidations, function (validation) {
1551
+ ngModel.$setValidity(validation.name, validation.valid);
1552
+ });
1553
+ };
1554
+
1555
+ upload.validate = function (files, ngModel, attr, scope) {
1556
+ ngModel = ngModel || {};
1557
+ ngModel.$ngfValidations = ngModel.$ngfValidations || [];
1558
+
1559
+ angular.forEach(ngModel.$ngfValidations, function (v) {
1560
+ v.valid = true;
1561
+ });
1562
+
1563
+ var attrGetter = function (name, params) {
1564
+ return upload.attrGetter(name, attr, scope, params);
1565
+ };
1566
+
1567
+ if (files == null || files.length === 0) {
1568
+ return upload.emptyPromise(ngModel);
1569
+ }
1570
+
1571
+ files = files.length === undefined ? [files] : files.slice(0);
1572
+
1573
+ function validateSync(name, validatorVal, fn) {
1574
+ if (files) {
1575
+ var dName = 'ngf' + name[0].toUpperCase() + name.substr(1);
1576
+ var i = files.length, valid = null;
1577
+ while (i--) {
1578
+ var file = files[i];
1579
+ if (file) {
1580
+ var val = attrGetter(dName, {'$file': file});
1581
+ if (val == null) {
1582
+ val = validatorVal(attrGetter('ngfValidate') || {});
1583
+ valid = valid == null ? true : valid;
1584
+ }
1585
+ if (val != null) {
1586
+ if (!fn(file, val)) {
1587
+ file.$error = name;
1588
+ file.$errorParam = val;
1589
+ files.splice(i, 1);
1590
+ valid = false;
1591
+ }
1592
+ }
1593
+ }
1594
+ }
1595
+ if (valid !== null) {
1596
+ ngModel.$ngfValidations.push({name: name, valid: valid});
1597
+ }
1598
+ }
1599
+ }
1600
+
1601
+ validateSync('pattern', function (cons) {
1602
+ return cons.pattern;
1603
+ }, upload.validatePattern);
1604
+ validateSync('minSize', function (cons) {
1605
+ return cons.size && cons.size.min;
1606
+ }, function (file, val) {
1607
+ return file.size >= upload.translateScalars(val);
1608
+ });
1609
+ validateSync('maxSize', function (cons) {
1610
+ return cons.size && cons.size.max;
1611
+ }, function (file, val) {
1612
+ return file.size <= upload.translateScalars(val);
1613
+ });
1614
+ var totalSize = 0;
1615
+ validateSync('maxTotalSize', function (cons) {
1616
+ return cons.maxTotalSize && cons.maxTotalSize;
1617
+ }, function (file, val) {
1618
+ totalSize += file.size;
1619
+ if (totalSize > upload.translateScalars(val)) {
1620
+ files.splice(0, files.length);
1621
+ return false;
1622
+ }
1623
+ return true;
1624
+ });
1625
+
1626
+ validateSync('validateFn', function () {
1627
+ return null;
1628
+ }, function (file, r) {
1629
+ return r === true || r === null || r === '';
1630
+ });
1631
+
1632
+ if (!files.length) {
1633
+ return upload.emptyPromise(ngModel, ngModel.$ngfValidations);
1634
+ }
1635
+
1636
+ function validateAsync(name, validatorVal, type, asyncFn, fn) {
1637
+ var promises = [upload.emptyPromise()];
1638
+ if (files) {
1639
+ var dName = 'ngf' + name[0].toUpperCase() + name.substr(1);
1640
+ files = files.length === undefined ? [files] : files;
1641
+ angular.forEach(files, function (file) {
1642
+ var defer = $q.defer();
1643
+ promises.push(defer.promise);
1644
+ if (type && (file.type == null || file.type.search(type) !== 0)) {
1645
+ defer.resolve();
1646
+ return;
1647
+ }
1648
+ var val = attrGetter(dName, {'$file': file}) || validatorVal(attrGetter('ngfValidate', {'$file': file}) || {});
1649
+ if (val) {
1650
+ asyncFn(file, val).then(function (d) {
1651
+ if (!fn(d, val)) {
1652
+ file.$error = name;
1653
+ file.$errorParam = val;
1654
+ defer.reject();
1655
+ } else {
1656
+ defer.resolve();
1657
+ }
1658
+ }, function () {
1659
+ if (attrGetter('ngfValidateForce', {'$file': file})) {
1660
+ file.$error = name;
1661
+ file.$errorParam = val;
1662
+ defer.reject();
1663
+ } else {
1664
+ defer.resolve();
1665
+ }
1666
+ });
1667
+ } else {
1668
+ defer.resolve();
1669
+ }
1670
+ });
1671
+ return $q.all(promises).then(function () {
1672
+ ngModel.$ngfValidations.push({name: name, valid: true});
1673
+ }, function () {
1674
+ ngModel.$ngfValidations.push({name: name, valid: false});
1675
+ });
1676
+ }
1677
+ }
1678
+
1679
+ var deffer = $q.defer();
1680
+ var promises = [];
1681
+
1682
+ promises.push(upload.happyPromise(validateAsync('maxHeight', function (cons) {
1683
+ return cons.height && cons.height.max;
1684
+ }, /image/, this.imageDimensions, function (d, val) {
1685
+ return d.height <= val;
1686
+ })));
1687
+ promises.push(upload.happyPromise(validateAsync('minHeight', function (cons) {
1688
+ return cons.height && cons.height.min;
1689
+ }, /image/, this.imageDimensions, function (d, val) {
1690
+ return d.height >= val;
1691
+ })));
1692
+ promises.push(upload.happyPromise(validateAsync('maxWidth', function (cons) {
1693
+ return cons.width && cons.width.max;
1694
+ }, /image/, this.imageDimensions, function (d, val) {
1695
+ return d.width <= val;
1696
+ })));
1697
+ promises.push(upload.happyPromise(validateAsync('minWidth', function (cons) {
1698
+ return cons.width && cons.width.min;
1699
+ }, /image/, this.imageDimensions, function (d, val) {
1700
+ return d.width >= val;
1701
+ })));
1702
+ promises.push(upload.happyPromise(validateAsync('ratio', function (cons) {
1703
+ return cons.ratio;
1704
+ }, /image/, this.imageDimensions, function (d, val) {
1705
+ var split = val.toString().split(','), valid = false;
1706
+ for (var i = 0; i < split.length; i++) {
1707
+ if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.0001) {
1708
+ valid = true;
1709
+ }
1710
+ }
1711
+ return valid;
1712
+ })));
1713
+ promises.push(upload.happyPromise(validateAsync('maxRatio', function (cons) {
1714
+ return cons.ratio;
1715
+ }, /image/, this.imageDimensions, function (d, val) {
1716
+ return (d.width / d.height) - upload.ratioToFloat(val) < 0.0001;
1717
+ })));
1718
+ promises.push(upload.happyPromise(validateAsync('minRatio', function (cons) {
1719
+ return cons.ratio;
1720
+ }, /image/, this.imageDimensions, function (d, val) {
1721
+ return (d.width / d.height) - upload.ratioToFloat(val) > -0.0001;
1722
+ })));
1723
+ promises.push(upload.happyPromise(validateAsync('maxDuration', function (cons) {
1724
+ return cons.duration && cons.duration.max;
1725
+ }, /audio|video/, this.mediaDuration, function (d, val) {
1726
+ return d <= upload.translateScalars(val);
1727
+ })));
1728
+ promises.push(upload.happyPromise(validateAsync('minDuration', function (cons) {
1729
+ return cons.duration && cons.duration.min;
1730
+ }, /audio|video/, this.mediaDuration, function (d, val) {
1731
+ return d >= upload.translateScalars(val);
1732
+ })));
1733
+
1734
+ promises.push(upload.happyPromise(validateAsync('validateAsyncFn', function () {
1735
+ return null;
1736
+ }, null, function (file, val) {
1737
+ return val;
1738
+ }, function (r) {
1739
+ return r === true || r === null || r === '';
1740
+ })));
1741
+
1742
+ return $q.all(promises).then(function () {
1743
+ deffer.resolve(ngModel, ngModel.$ngfValidations);
1744
+ });
1745
+ };
1746
+
1747
+ upload.imageDimensions = function (file) {
1748
+ if (file.$ngfWidth && file.$ngfHeight) {
1749
+ var d = $q.defer();
1750
+ $timeout(function () {
1751
+ d.resolve({width: file.$ngfWidth, height: file.$ngfHeight});
1752
+ });
1753
+ return d.promise;
1754
+ }
1755
+ if (file.$ngfDimensionPromise) return file.$ngfDimensionPromise;
1756
+
1757
+ var deferred = $q.defer();
1758
+ $timeout(function () {
1759
+ if (file.type.indexOf('image') !== 0) {
1760
+ deferred.reject('not image');
1761
+ return;
1762
+ }
1763
+ upload.dataUrl(file).then(function (dataUrl) {
1764
+ var img = angular.element('<img>').attr('src', dataUrl).css('visibility', 'hidden').css('position', 'fixed');
1765
+
1766
+ function success() {
1767
+ var width = img[0].clientWidth;
1768
+ var height = img[0].clientHeight;
1769
+ img.remove();
1770
+ file.$ngfWidth = width;
1771
+ file.$ngfHeight = height;
1772
+ deferred.resolve({width: width, height: height});
1773
+ }
1774
+
1775
+ function error() {
1776
+ img.remove();
1777
+ deferred.reject('load error');
1778
+ }
1779
+
1780
+ img.on('load', success);
1781
+ img.on('error', error);
1782
+ var count = 0;
1783
+
1784
+ function checkLoadError() {
1785
+ $timeout(function () {
1786
+ if (img[0].parentNode) {
1787
+ if (img[0].clientWidth) {
1788
+ success();
1789
+ } else if (count > 10) {
1790
+ error();
1791
+ } else {
1792
+ checkLoadError();
1793
+ }
1794
+ }
1795
+ }, 1000);
1796
+ }
1797
+
1798
+ checkLoadError();
1799
+
1800
+ angular.element(document.getElementsByTagName('body')[0]).append(img);
1801
+ }, function () {
1802
+ deferred.reject('load error');
1803
+ });
1804
+ });
1805
+
1806
+ file.$ngfDimensionPromise = deferred.promise;
1807
+ file.$ngfDimensionPromise['finally'](function () {
1808
+ delete file.$ngfDimensionPromise;
1809
+ });
1810
+ return file.$ngfDimensionPromise;
1811
+ };
1812
+
1813
+ upload.mediaDuration = function (file) {
1814
+ if (file.$ngfDuration) {
1815
+ var d = $q.defer();
1816
+ $timeout(function () {
1817
+ d.resolve(file.$ngfDuration);
1818
+ });
1819
+ return d.promise;
1820
+ }
1821
+ if (file.$ngfDurationPromise) return file.$ngfDurationPromise;
1822
+
1823
+ var deferred = $q.defer();
1824
+ $timeout(function () {
1825
+ if (file.type.indexOf('audio') !== 0 && file.type.indexOf('video') !== 0) {
1826
+ deferred.reject('not media');
1827
+ return;
1828
+ }
1829
+ upload.dataUrl(file).then(function (dataUrl) {
1830
+ var el = angular.element(file.type.indexOf('audio') === 0 ? '<audio>' : '<video>')
1831
+ .attr('src', dataUrl).css('visibility', 'none').css('position', 'fixed');
1832
+
1833
+ function success() {
1834
+ var duration = el[0].duration;
1835
+ file.$ngfDuration = duration;
1836
+ el.remove();
1837
+ deferred.resolve(duration);
1838
+ }
1839
+
1840
+ function error() {
1841
+ el.remove();
1842
+ deferred.reject('load error');
1843
+ }
1844
+
1845
+ el.on('loadedmetadata', success);
1846
+ el.on('error', error);
1847
+ var count = 0;
1848
+
1849
+ function checkLoadError() {
1850
+ $timeout(function () {
1851
+ if (el[0].parentNode) {
1852
+ if (el[0].duration) {
1853
+ success();
1854
+ } else if (count > 10) {
1855
+ error();
1856
+ } else {
1857
+ checkLoadError();
1858
+ }
1859
+ }
1860
+ }, 1000);
1861
+ }
1862
+
1863
+ checkLoadError();
1864
+
1865
+ angular.element(document.body).append(el);
1866
+ }, function () {
1867
+ deferred.reject('load error');
1868
+ });
1869
+ });
1870
+
1871
+ file.$ngfDurationPromise = deferred.promise;
1872
+ file.$ngfDurationPromise['finally'](function () {
1873
+ delete file.$ngfDurationPromise;
1874
+ });
1875
+ return file.$ngfDurationPromise;
1876
+ };
1877
+ return upload;
1878
+ }
1879
+ ]);
1880
+
1881
+ ngFileUpload.service('UploadResize', ['UploadValidate', '$q', function (UploadValidate, $q) {
1882
+ var upload = UploadValidate;
1883
+
1884
+ /**
1885
+ * Conserve aspect ratio of the original region. Useful when shrinking/enlarging
1886
+ * images to fit into a certain area.
1887
+ * Source: http://stackoverflow.com/a/14731922
1888
+ *
1889
+ * @param {Number} srcWidth Source area width
1890
+ * @param {Number} srcHeight Source area height
1891
+ * @param {Number} maxWidth Nestable area maximum available width
1892
+ * @param {Number} maxHeight Nestable area maximum available height
1893
+ * @return {Object} { width, height }
1894
+ */
1895
+ var calculateAspectRatioFit = function (srcWidth, srcHeight, maxWidth, maxHeight, centerCrop) {
1896
+ var ratio = centerCrop ? Math.max(maxWidth / srcWidth, maxHeight / srcHeight) :
1897
+ Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
1898
+ return {width: srcWidth * ratio, height: srcHeight * ratio,
1899
+ marginX: srcWidth * ratio - maxWidth, marginY: srcHeight * ratio - maxHeight};
1900
+ };
1901
+
1902
+ // Extracted from https://github.com/romelgomez/angular-firebase-image-upload/blob/master/app/scripts/fileUpload.js#L89
1903
+ var resize = function (imagen, width, height, quality, type, ratio, centerCrop) {
1904
+ var deferred = $q.defer();
1905
+ var canvasElement = document.createElement('canvas');
1906
+ var imageElement = document.createElement('img');
1907
+
1908
+ imageElement.onload = function () {
1909
+ try {
1910
+ if (ratio) {
1911
+ var ratioFloat = upload.ratioToFloat(ratio);
1912
+ var imgRatio = imageElement.width / imageElement.height;
1913
+ if (imgRatio < ratioFloat) {
1914
+ width = imageElement.width;
1915
+ height = width / ratioFloat;
1916
+ } else {
1917
+ height = imageElement.height;
1918
+ width = height * ratioFloat;
1919
+ }
1920
+ }
1921
+ if (!width) {
1922
+ width = imageElement.width;
1923
+ }
1924
+ if (!height) {
1925
+ height = imageElement.height;
1926
+ }
1927
+ var dimensions = calculateAspectRatioFit(imageElement.width, imageElement.height, width, height, centerCrop);
1928
+ canvasElement.width = Math.min(dimensions.width, width);
1929
+ canvasElement.height = Math.min(dimensions.height, height);
1930
+ var context = canvasElement.getContext('2d');
1931
+ context.drawImage(imageElement,
1932
+ Math.min(0, -dimensions.marginX / 2), Math.min(0, -dimensions.marginY / 2),
1933
+ dimensions.width, dimensions.height);
1934
+ deferred.resolve(canvasElement.toDataURL(type || 'image/WebP', quality || 1.0));
1935
+ } catch (e) {
1936
+ deferred.reject(e);
1937
+ }
1938
+ };
1939
+ imageElement.onerror = function () {
1940
+ deferred.reject();
1941
+ };
1942
+ imageElement.src = imagen;
1943
+ return deferred.promise;
1944
+ };
1945
+
1946
+ upload.dataUrltoBlob = function (dataurl, name) {
1947
+ var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
1948
+ bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
1949
+ while (n--) {
1950
+ u8arr[n] = bstr.charCodeAt(n);
1951
+ }
1952
+ var blob = new window.Blob([u8arr], {type: mime});
1953
+ blob.name = name;
1954
+ return blob;
1955
+ };
1956
+
1957
+ upload.isResizeSupported = function () {
1958
+ var elem = document.createElement('canvas');
1959
+ return window.atob && elem.getContext && elem.getContext('2d');
1960
+ };
1961
+
1962
+ if (upload.isResizeSupported()) {
1963
+ // add name getter to the blob constructor prototype
1964
+ Object.defineProperty(window.Blob.prototype, 'name', {
1965
+ get: function () {
1966
+ return this.$ngfName;
1967
+ },
1968
+ set: function (v) {
1969
+ this.$ngfName = v;
1970
+ },
1971
+ configurable: true
1972
+ });
1973
+ }
1974
+
1975
+ upload.resize = function (file, width, height, quality, type, ratio, centerCrop) {
1976
+ if (file.type.indexOf('image') !== 0) return upload.emptyPromise(file);
1977
+
1978
+ var deferred = $q.defer();
1979
+ upload.dataUrl(file, true).then(function (url) {
1980
+ resize(url, width, height, quality, type || file.type, ratio, centerCrop).then(function (dataUrl) {
1981
+ deferred.resolve(upload.dataUrltoBlob(dataUrl, file.name));
1982
+ }, function () {
1983
+ deferred.reject();
1984
+ });
1985
+ }, function () {
1986
+ deferred.reject();
1987
+ });
1988
+ return deferred.promise;
1989
+ };
1990
+
1991
+ return upload;
1992
+ }]);
1993
+
1994
+ (function () {
1995
+ ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$location', 'Upload', '$http', '$q',
1996
+ function ($parse, $timeout, $location, Upload, $http, $q) {
1997
+ return {
1998
+ restrict: 'AEC',
1999
+ require: '?ngModel',
2000
+ link: function (scope, elem, attr, ngModel) {
2001
+ linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, Upload, $http, $q);
2002
+ }
2003
+ };
2004
+ }]);
2005
+
2006
+ ngFileUpload.directive('ngfNoFileDrop', function () {
2007
+ return function (scope, elem) {
2008
+ if (dropAvailable()) elem.css('display', 'none');
2009
+ };
2010
+ });
2011
+
2012
+ ngFileUpload.directive('ngfDropAvailable', ['$parse', '$timeout', 'Upload', function ($parse, $timeout, Upload) {
2013
+ return function (scope, elem, attr) {
2014
+ if (dropAvailable()) {
2015
+ var model = $parse(Upload.attrGetter('ngfDropAvailable', attr));
2016
+ $timeout(function () {
2017
+ model(scope);
2018
+ if (model.assign) {
2019
+ model.assign(scope, true);
2020
+ }
2021
+ });
2022
+ }
2023
+ };
2024
+ }]);
2025
+
2026
+ function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, upload, $http, $q) {
2027
+ var available = dropAvailable();
2028
+
2029
+ var attrGetter = function (name, scope, params) {
2030
+ return upload.attrGetter(name, attr, scope, params);
2031
+ };
2032
+
2033
+ if (attrGetter('dropAvailable')) {
2034
+ $timeout(function () {
2035
+ if (scope[attrGetter('dropAvailable')]) {
2036
+ scope[attrGetter('dropAvailable')].value = available;
2037
+ } else {
2038
+ scope[attrGetter('dropAvailable')] = available;
2039
+ }
2040
+ });
2041
+ }
2042
+ if (!available) {
2043
+ if (attrGetter('ngfHideOnDropNotAvailable', scope) === true) {
2044
+ elem.css('display', 'none');
2045
+ }
2046
+ return;
2047
+ }
2048
+
2049
+ function isDisabled() {
2050
+ return elem.attr('disabled') || attrGetter('ngfDropDisabled', scope);
2051
+ }
2052
+
2053
+ if (attrGetter('ngfSelect') == null) {
2054
+ upload.registerModelChangeValidator(ngModel, attr, scope);
2055
+ }
2056
+
2057
+ var leaveTimeout = null;
2058
+ var stopPropagation = $parse(attrGetter('ngfStopPropagation'));
2059
+ var dragOverDelay = 1;
2060
+ var actualDragOverClass;
2061
+
2062
+ elem[0].addEventListener('dragover', function (evt) {
2063
+ if (isDisabled()) return;
2064
+ evt.preventDefault();
2065
+ if (stopPropagation(scope)) evt.stopPropagation();
2066
+ // handling dragover events from the Chrome download bar
2067
+ if (navigator.userAgent.indexOf('Chrome') > -1) {
2068
+ var b = evt.dataTransfer.effectAllowed;
2069
+ evt.dataTransfer.dropEffect = ('move' === b || 'linkMove' === b) ? 'move' : 'copy';
2070
+ }
2071
+ $timeout.cancel(leaveTimeout);
2072
+ if (!actualDragOverClass) {
2073
+ actualDragOverClass = 'C';
2074
+ calculateDragOverClass(scope, attr, evt, function (clazz) {
2075
+ actualDragOverClass = clazz;
2076
+ elem.addClass(actualDragOverClass);
2077
+ attrGetter('ngfDrag', scope, {$isDragging: true, $class: actualDragOverClass, $event: evt});
2078
+ });
2079
+ }
2080
+ }, false);
2081
+ elem[0].addEventListener('dragenter', function (evt) {
2082
+ if (isDisabled()) return;
2083
+ evt.preventDefault();
2084
+ if (stopPropagation(scope)) evt.stopPropagation();
2085
+ }, false);
2086
+ elem[0].addEventListener('dragleave', function (evt) {
2087
+ if (isDisabled()) return;
2088
+ evt.preventDefault();
2089
+ if (stopPropagation(scope)) evt.stopPropagation();
2090
+ leaveTimeout = $timeout(function () {
2091
+ if (actualDragOverClass) elem.removeClass(actualDragOverClass);
2092
+ actualDragOverClass = null;
2093
+ attrGetter('ngfDrag', scope, {$isDragging: false, $event: evt});
2094
+ }, dragOverDelay || 100);
2095
+ }, false);
2096
+ elem[0].addEventListener('drop', function (evt) {
2097
+ if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
2098
+ evt.preventDefault();
2099
+ if (stopPropagation(scope)) evt.stopPropagation();
2100
+ if (actualDragOverClass) elem.removeClass(actualDragOverClass);
2101
+ actualDragOverClass = null;
2102
+ var html;
2103
+ try {
2104
+ html = (evt.dataTransfer && evt.dataTransfer.getData && evt.dataTransfer.getData('text/html'));
2105
+ } catch (e) {/* Fix IE11 that throw error calling getData */
2106
+ }
2107
+ if (upload.shouldUpdateOn('dropUrl', attr, scope) && html) {
2108
+ extractUrlAndUpdateModel(html, evt);
2109
+ } else {
2110
+ extractFiles(evt, function (files) {
2111
+ upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);
2112
+ }, attrGetter('ngfAllowDir', scope) !== false,
2113
+ attrGetter('multiple') || attrGetter('ngfMultiple', scope));
2114
+ }
2115
+ }, false);
2116
+ elem[0].addEventListener('paste', function (evt) {
2117
+ if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
2118
+ evt.preventDefault();
2119
+ }
2120
+ if (isDisabled() || !upload.shouldUpdateOn('paste', attr, scope)) return;
2121
+ var files = [];
2122
+ var clipboard = evt.clipboardData || evt.originalEvent.clipboardData;
2123
+ if (clipboard && clipboard.items) {
2124
+ for (var k = 0; k < clipboard.items.length; k++) {
2125
+ if (clipboard.items[k].type.indexOf('image') !== -1) {
2126
+ files.push(clipboard.items[k].getAsFile());
2127
+ }
2128
+ }
2129
+ }
2130
+ if (files.length) {
2131
+ upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);
2132
+ } else {
2133
+ var html;
2134
+ try {
2135
+ html = (clipboard && clipboard.getData && clipboard.getData('text/html'));
2136
+ } catch (e) {/* Fix IE11 that throw error calling getData */
2137
+ }
2138
+ if (upload.shouldUpdateOn('pasteUrl', attr, scope) && html) {
2139
+ extractUrlAndUpdateModel(html, evt);
2140
+ }
2141
+ }
2142
+ }, false);
2143
+
2144
+ if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&
2145
+ attrGetter('ngfEnableFirefoxPaste', scope)) {
2146
+ elem.attr('contenteditable', true);
2147
+ elem.on('keypress', function (e) {
2148
+ if (!e.metaKey && !e.ctrlKey) {
2149
+ e.preventDefault();
2150
+ }
2151
+ });
2152
+ }
2153
+
2154
+ function extractUrlAndUpdateModel(html, evt) {
2155
+ var urls = [];
2156
+ html.replace(/<(img src|img [^>]* src) *=\"([^\"]*)\"/gi, function (m, n, src) {
2157
+ urls.push(src);
2158
+ });
2159
+ var promises = [], files = [];
2160
+ if (urls.length) {
2161
+ angular.forEach(urls, function (url) {
2162
+ promises.push($http({url: url, method: 'get', responseType: 'arraybuffer'}).then(function (resp) {
2163
+ var arrayBufferView = new Uint8Array(resp.data);
2164
+ var type = resp.headers('content-type') || 'image/WebP';
2165
+ var blob = new window.Blob([arrayBufferView], {type: type});
2166
+ files.push(blob);
2167
+ //var split = type.split('[/;]');
2168
+ //blob.name = url.substring(0, 150).replace(/\W+/g, '') + '.' + (split.length > 1 ? split[1] : 'jpg');
2169
+ }));
2170
+ });
2171
+ $q.all(promises).then(function () {
2172
+ upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);
2173
+ });
2174
+ }
2175
+ }
2176
+
2177
+ function calculateDragOverClass(scope, attr, evt, callback) {
2178
+ var obj = attrGetter('ngfDragOverClass', scope, {$event: evt}), dClass = 'dragover';
2179
+ if (angular.isString(obj)) {
2180
+ dClass = obj;
2181
+ } else if (obj) {
2182
+ if (obj.delay) dragOverDelay = obj.delay;
2183
+ if (obj.accept || obj.reject) {
2184
+ var items = evt.dataTransfer.items;
2185
+ if (items == null || !items.length) {
2186
+ dClass = obj.accept;
2187
+ } else {
2188
+ var pattern = obj.pattern || attrGetter('ngfPattern', scope, {$event: evt});
2189
+ var len = items.length;
2190
+ while (len--) {
2191
+ if (!upload.validatePattern(items[len], pattern)) {
2192
+ dClass = obj.reject;
2193
+ break;
2194
+ } else {
2195
+ dClass = obj.accept;
2196
+ }
2197
+ }
2198
+ }
2199
+ }
2200
+ }
2201
+ callback(dClass);
2202
+ }
2203
+
2204
+ function extractFiles(evt, callback, allowDir, multiple) {
2205
+ var files = [], processing = 0;
2206
+
2207
+ function traverseFileTree(files, entry, path) {
2208
+ if (entry != null) {
2209
+ if (entry.isDirectory) {
2210
+ var filePath = (path || '') + entry.name;
2211
+ files.push({name: entry.name, type: 'directory', path: filePath});
2212
+ var dirReader = entry.createReader();
2213
+ var entries = [];
2214
+ processing++;
2215
+ var readEntries = function () {
2216
+ dirReader.readEntries(function (results) {
2217
+ try {
2218
+ if (!results.length) {
2219
+ for (var i = 0; i < entries.length; i++) {
2220
+ traverseFileTree(files, entries[i], (path ? path : '') + entry.name + '/');
2221
+ }
2222
+ processing--;
2223
+ } else {
2224
+ entries = entries.concat(Array.prototype.slice.call(results || [], 0));
2225
+ readEntries();
2226
+ }
2227
+ } catch (e) {
2228
+ processing--;
2229
+ console.error(e);
2230
+ }
2231
+ }, function () {
2232
+ processing--;
2233
+ });
2234
+ };
2235
+ readEntries();
2236
+ } else {
2237
+ processing++;
2238
+ entry.file(function (file) {
2239
+ try {
2240
+ processing--;
2241
+ file.path = (path ? path : '') + file.name;
2242
+ files.push(file);
2243
+ } catch (e) {
2244
+ processing--;
2245
+ console.error(e);
2246
+ }
2247
+ }, function () {
2248
+ processing--;
2249
+ });
2250
+ }
2251
+ }
2252
+ }
2253
+
2254
+ var items = evt.dataTransfer.items;
2255
+
2256
+ if (items && items.length > 0 && $location.protocol() !== 'file') {
2257
+ for (var i = 0; i < items.length; i++) {
2258
+ if (items[i].webkitGetAsEntry && items[i].webkitGetAsEntry() && items[i].webkitGetAsEntry().isDirectory) {
2259
+ var entry = items[i].webkitGetAsEntry();
2260
+ if (entry.isDirectory && !allowDir) {
2261
+ continue;
2262
+ }
2263
+ if (entry != null) {
2264
+ traverseFileTree(files, entry);
2265
+ }
2266
+ } else {
2267
+ var f = items[i].getAsFile();
2268
+ if (f != null) files.push(f);
2269
+ }
2270
+ if (!multiple && files.length > 0) break;
2271
+ }
2272
+ } else {
2273
+ var fileList = evt.dataTransfer.files;
2274
+ if (fileList != null) {
2275
+ for (var j = 0; j < fileList.length; j++) {
2276
+ var file = fileList.item(j);
2277
+ if (file.type || file.size > 0) {
2278
+ files.push(file);
2279
+ }
2280
+ if (!multiple && files.length > 0) {
2281
+ break;
2282
+ }
2283
+ }
2284
+ }
2285
+ }
2286
+ var delays = 0;
2287
+ (function waitForProcess(delay) {
2288
+ $timeout(function () {
2289
+ if (!processing) {
2290
+ if (!multiple && files.length > 1) {
2291
+ i = 0;
2292
+ while (files[i].type === 'directory') i++;
2293
+ files = [files[i]];
2294
+ }
2295
+ callback(files);
2296
+ } else {
2297
+ if (delays++ * 10 < 20 * 1000) {
2298
+ waitForProcess(10);
2299
+ }
2300
+ }
2301
+ }, delay || 0);
2302
+ })();
2303
+ }
2304
+ }
2305
+
2306
+ function dropAvailable() {
2307
+ var div = document.createElement('div');
2308
+ return ('draggable' in div) && ('ondrop' in div) && !/Edge\/12./i.test(navigator.userAgent);
2309
+ }
2310
+
2311
+ })();
2312
+
2313
+ // customized version of https://github.com/exif-js/exif-js
2314
+ ngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize, $q) {
2315
+ var upload = UploadResize;
2316
+
2317
+ function findEXIFinJPEG(file) {
2318
+ var dataView = new DataView(file);
2319
+
2320
+ if ((dataView.getUint8(0) !== 0xFF) || (dataView.getUint8(1) !== 0xD8)) {
2321
+ return 'Not a valid JPEG';
2322
+ }
2323
+
2324
+ var offset = 2,
2325
+ length = file.byteLength,
2326
+ marker;
2327
+
2328
+ while (offset < length) {
2329
+ if (dataView.getUint8(offset) !== 0xFF) {
2330
+ return 'Not a valid marker at offset ' + offset + ', found: ' + dataView.getUint8(offset);
2331
+ }
2332
+
2333
+ marker = dataView.getUint8(offset + 1);
2334
+ if (marker === 225) {
2335
+ return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2);
2336
+ } else {
2337
+ offset += 2 + dataView.getUint16(offset + 2);
2338
+ }
2339
+ }
2340
+ }
2341
+
2342
+ function readOrientation(file, tiffStart, dirStart, bigEnd) {
2343
+ var entries = file.getUint16(dirStart, !bigEnd),
2344
+ entryOffset, i;
2345
+
2346
+ for (i = 0; i < entries; i++) {
2347
+ entryOffset = dirStart + i * 12 + 2;
2348
+ var val = file.getUint16(entryOffset, !bigEnd);
2349
+ if (0x0112 === val) {
2350
+ return readTagValue(file, entryOffset, tiffStart, bigEnd);
2351
+ }
2352
+ }
2353
+ return null;
2354
+ }
2355
+
2356
+ function readTagValue(file, entryOffset, tiffStart, bigEnd) {
2357
+ var numValues = file.getUint32(entryOffset + 4, !bigEnd),
2358
+ valueOffset = file.getUint32(entryOffset + 8, !bigEnd) + tiffStart, offset, vals, n;
2359
+
2360
+ if (numValues === 1) {
2361
+ return file.getUint16(entryOffset + 8, !bigEnd);
2362
+ } else {
2363
+ offset = numValues > 2 ? valueOffset : (entryOffset + 8);
2364
+ vals = [];
2365
+ for (n = 0; n < numValues; n++) {
2366
+ vals[n] = file.getUint16(offset + 2 * n, !bigEnd);
2367
+ }
2368
+ return vals;
2369
+ }
2370
+ }
2371
+
2372
+ function getStringFromDB(buffer, start, length) {
2373
+ var outstr = '';
2374
+ for (var n = start; n < start + length; n++) {
2375
+ outstr += String.fromCharCode(buffer.getUint8(n));
2376
+ }
2377
+ return outstr;
2378
+ }
2379
+
2380
+ function readEXIFData(file, start) {
2381
+ if (getStringFromDB(file, start, 4) !== 'Exif') {
2382
+ return 'Not valid EXIF data! ' + getStringFromDB(file, start, 4);
2383
+ }
2384
+
2385
+ var bigEnd,
2386
+ tiffOffset = start + 6;
2387
+
2388
+ // test for TIFF validity and endianness
2389
+ if (file.getUint16(tiffOffset) === 0x4949) {
2390
+ bigEnd = false;
2391
+ } else if (file.getUint16(tiffOffset) === 0x4D4D) {
2392
+ bigEnd = true;
2393
+ } else {
2394
+ return 'Not valid TIFF data! (no 0x4949 or 0x4D4D)';
2395
+ }
2396
+
2397
+ if (file.getUint16(tiffOffset + 2, !bigEnd) !== 0x002A) {
2398
+ return 'Not valid TIFF data! (no 0x002A)';
2399
+ }
2400
+
2401
+ var firstIFDOffset = file.getUint32(tiffOffset + 4, !bigEnd);
2402
+
2403
+ if (firstIFDOffset < 0x00000008) {
2404
+ return 'Not valid TIFF data! (First offset less than 8)', file.getUint32(tiffOffset + 4, !bigEnd);
2405
+ }
2406
+
2407
+ return readOrientation(file, tiffOffset, tiffOffset + firstIFDOffset, bigEnd);
2408
+
2409
+ }
2410
+
2411
+ upload.isExifSupported = function() {
2412
+ return window.FileReader && new FileReader().readAsArrayBuffer && upload.isResizeSupported();
2413
+ };
2414
+
2415
+ upload.orientation = function (file) {
2416
+ if (file.$ngfOrientation != null) {
2417
+ return upload.emptyPromise(file.$ngfOrientation);
2418
+ }
2419
+ var defer = $q.defer();
2420
+ var fileReader = new FileReader();
2421
+ fileReader.onload = function (e) {
2422
+ var orientation;
2423
+ try {
2424
+ orientation = findEXIFinJPEG(e.target.result);
2425
+ } catch (e) {
2426
+ defer.reject(e);
2427
+ return;
2428
+ }
2429
+ if (angular.isString(orientation)) {
2430
+ defer.resolve(1);
2431
+ } else {
2432
+ file.$ngfOrientation = orientation;
2433
+ defer.resolve(orientation);
2434
+ }
2435
+ };
2436
+ fileReader.onerror = function (e) {
2437
+ defer.reject(e);
2438
+ };
2439
+
2440
+ fileReader.readAsArrayBuffer(file);
2441
+ return defer.promise;
2442
+ };
2443
+
2444
+
2445
+ function applyTransform(ctx, orientation, width, height) {
2446
+ switch (orientation) {
2447
+ case 2:
2448
+ return ctx.transform(-1, 0, 0, 1, width, 0);
2449
+ case 3:
2450
+ return ctx.transform(-1, 0, 0, -1, width, height);
2451
+ case 4:
2452
+ return ctx.transform(1, 0, 0, -1, 0, height);
2453
+ case 5:
2454
+ return ctx.transform(0, 1, 1, 0, 0, 0);
2455
+ case 6:
2456
+ return ctx.transform(0, 1, -1, 0, height, 0);
2457
+ case 7:
2458
+ return ctx.transform(0, -1, -1, 0, height, width);
2459
+ case 8:
2460
+ return ctx.transform(0, -1, 1, 0, 0, width);
2461
+ }
2462
+ }
2463
+
2464
+ upload.applyExifRotation = function (file) {
2465
+ if (file.type.indexOf('image/jpeg') !== 0) {
2466
+ return upload.emptyPromise(file);
2467
+ }
2468
+
2469
+ var deferred = $q.defer();
2470
+ upload.orientation(file).then(function (orientation) {
2471
+ if (!orientation || orientation < 2 || orientation > 8) {
2472
+ deferred.resolve(file);
2473
+ }
2474
+ upload.dataUrl(file, true).then(function (url) {
2475
+ var canvas = document.createElement('canvas');
2476
+ var img = document.createElement('img');
2477
+
2478
+ img.onload = function () {
2479
+ try {
2480
+ canvas.width = orientation > 4 ? img.height : img.width;
2481
+ canvas.height = orientation > 4 ? img.width : img.height;
2482
+ var ctx = canvas.getContext('2d');
2483
+ applyTransform(ctx, orientation, img.width, img.height);
2484
+ ctx.drawImage(img, 0, 0);
2485
+ var dataUrl = canvas.toDataURL(file.type || 'image/WebP', 1.0);
2486
+ var blob = upload.dataUrltoBlob(dataUrl, file.name);
2487
+ deferred.resolve(blob);
2488
+ } catch (e) {
2489
+ deferred.reject(e);
2490
+ }
2491
+ };
2492
+ img.onerror = function () {
2493
+ deferred.reject();
2494
+ };
2495
+ img.src = url;
2496
+ }, function (e) {
2497
+ deferred.reject(e);
2498
+ });
2499
+ }, function (e) {
2500
+ deferred.reject(e);
2501
+ });
2502
+ return deferred.promise;
2503
+ };
2504
+
2505
+ return upload;
2506
+ }]);