billy_cms 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
+ }]);