From 04ac67d27ef0a449e3cb400724a6f310a2dd9752 Mon Sep 17 00:00:00 2001 From: Sika Production Date: Mon, 19 Sep 2016 19:08:10 +0300 Subject: [PATCH 01/11] added 10s retry to infoscreen on connection failure --- infoscreen/static/js/infoscreen_controllers.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infoscreen/static/js/infoscreen_controllers.js b/infoscreen/static/js/infoscreen_controllers.js index 704488b..8525c87 100644 --- a/infoscreen/static/js/infoscreen_controllers.js +++ b/infoscreen/static/js/infoscreen_controllers.js @@ -11,6 +11,8 @@ app.controller('infoscreen_main', function($scope,$http,$timeout){ $http.get('/infoscreen/rotation/'+$scope.rotation).then(function(response){ templates = response.data.instances; $scope.next(); + },function(response){ + $timeout(get_rotation, 10000); }); } From 908315999b2203d3b60960d6effbcbac2ad112af Mon Sep 17 00:00:00 2001 From: okalintu Date: Mon, 19 Sep 2016 19:34:54 +0300 Subject: [PATCH 02/11] really fixed timeouts this time --- infoscreen/static/js/infoscreen_controllers.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/infoscreen/static/js/infoscreen_controllers.js b/infoscreen/static/js/infoscreen_controllers.js index 8525c87..3733d80 100644 --- a/infoscreen/static/js/infoscreen_controllers.js +++ b/infoscreen/static/js/infoscreen_controllers.js @@ -1,5 +1,4 @@ var app = angular.module('infoApp', ['ngAnimate', 'ngRoute']); - app.controller('infoscreen_main', function($scope,$http,$timeout){ var templates = []; $scope.init = function(rot){ @@ -8,7 +7,7 @@ app.controller('infoscreen_main', function($scope,$http,$timeout){ } var get_rotation = function(){ $scope.index = -1; - $http.get('/infoscreen/rotation/'+$scope.rotation).then(function(response){ + $http.get('/infoscreen/rotation/'+$scope.rotation, {timeout:5000}).then(function(response){ templates = response.data.instances; $scope.next(); },function(response){ From 380f3c658f8915ea11fba28a2879d54edd4eaf2d Mon Sep 17 00:00:00 2001 From: Sika Production Date: Fri, 23 Sep 2016 15:41:19 +0300 Subject: [PATCH 03/11] modified abbjobs to expire in 60 days instead of 30 --- infoscreen/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infoscreen/views.py b/infoscreen/views.py index eb51ff5..888b809 100644 --- a/infoscreen/views.py +++ b/infoscreen/views.py @@ -19,7 +19,7 @@ def default(request,*args,**kwargs): # send abb jobs which have been created less than month ago @require_http_methods(["GET"]) def abb_job_list(request, *args, **kwargs): - limit = timezone.now() - timedelta(days=30) + limit = timezone.now() - timedelta(days=60) jobs = ABBJob.objects.filter(created__gt=limit) joblist = list(map(lambda j:j.get_dict(), jobs)) return HttpResponse(json.dumps(joblist)) From 203f5e1b8d5002faeeb74deb18945700be458ef6 Mon Sep 17 00:00:00 2001 From: okalintu Date: Wed, 21 Sep 2016 17:44:24 +0300 Subject: [PATCH 04/11] remodeled infoinstances and infoitems --- infoscreen/admin.py | 7 +- .../migrations/0005_auto_20160921_1252.py | 36 +++++++++ ...tem_imageinfoitem_infoinstance_infoitem.py | 58 ++++++++++++++ infoscreen/models.py | 80 +++++++++++++------ .../{genericimage.html => generic_image.html} | 0 infoscreen/views.py | 1 - 6 files changed, 155 insertions(+), 27 deletions(-) create mode 100644 infoscreen/migrations/0005_auto_20160921_1252.py create mode 100644 infoscreen/migrations/0006_abbinfoitem_externalimageinfoitem_imageinfoitem_infoinstance_infoitem.py rename infoscreen/static/html/{genericimage.html => generic_image.html} (100%) diff --git a/infoscreen/admin.py b/infoscreen/admin.py index da0268b..c3b7409 100644 --- a/infoscreen/admin.py +++ b/infoscreen/admin.py @@ -1,9 +1,10 @@ from django.contrib import admin -from infoscreen.models import Rotation, InfoItem, InfoInstance, InfoOption +from infoscreen.models import Rotation, InfoItem, InfoInstance, ImageInfoItem, ExternalImageInfoItem, ABBInfoItem # Register your models here. - admin.site.register(Rotation) admin.site.register(InfoItem) +admin.site.register(ImageInfoItem) +admin.site.register(ExternalImageInfoItem) +admin.site.register(ABBInfoItem) admin.site.register(InfoInstance) -admin.site.register(InfoOption) diff --git a/infoscreen/migrations/0005_auto_20160921_1252.py b/infoscreen/migrations/0005_auto_20160921_1252.py new file mode 100644 index 0000000..00efb65 --- /dev/null +++ b/infoscreen/migrations/0005_auto_20160921_1252.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2016-09-21 12:52 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('infoscreen', '0004_auto_20160914_1747'), + ] + + operations = [ + migrations.RemoveField( + model_name='infoinstance', + name='item', + ), + migrations.RemoveField( + model_name='infoinstance', + name='rotation', + ), + migrations.RemoveField( + model_name='infooption', + name='item', + ), + migrations.DeleteModel( + name='InfoInstance', + ), + migrations.DeleteModel( + name='InfoItem', + ), + migrations.DeleteModel( + name='InfoOption', + ), + ] diff --git a/infoscreen/migrations/0006_abbinfoitem_externalimageinfoitem_imageinfoitem_infoinstance_infoitem.py b/infoscreen/migrations/0006_abbinfoitem_externalimageinfoitem_imageinfoitem_infoinstance_infoitem.py new file mode 100644 index 0000000..13c7368 --- /dev/null +++ b/infoscreen/migrations/0006_abbinfoitem_externalimageinfoitem_imageinfoitem_infoinstance_infoitem.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2016-09-21 12:54 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('infoscreen', '0005_auto_20160921_1252'), + ] + + operations = [ + migrations.CreateModel( + name='InfoInstance', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('duration', models.FloatField(default=15.0)), + ('item_id', models.PositiveIntegerField()), + ('item_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), + ('rotation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='infoscreen.Rotation')), + ], + ), + migrations.CreateModel( + name='InfoItem', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('expire_date', models.DateTimeField(blank=True, null=True)), + ], + ), + migrations.CreateModel( + name='ABBInfoItem', + fields=[ + ('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')), + ], + bases=('infoscreen.infoitem',), + ), + migrations.CreateModel( + name='ExternalImageInfoItem', + fields=[ + ('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')), + ('url', models.TextField()), + ], + bases=('infoscreen.infoitem',), + ), + migrations.CreateModel( + name='ImageInfoItem', + fields=[ + ('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')), + ('img', models.ImageField(upload_to='infoimages/')), + ], + bases=('infoscreen.infoitem',), + ), + ] diff --git a/infoscreen/models.py b/infoscreen/models.py index e123a2c..edc3869 100644 --- a/infoscreen/models.py +++ b/infoscreen/models.py @@ -1,28 +1,71 @@ from django.db import models from django.utils import timezone +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType class InfoItem(models.Model): + class __meta__: + abstract = True name = models.CharField(max_length=255) - template_url = models.CharField(max_length=512) expire_date = models.DateTimeField(blank=True,null=True) # None means never expiring item + def get_template_url(self): + raise NotImplementedError("inheriting classes must implement get_template_url") + def get_edit_template_url(self): + raise NotImplementedError("inheriting classes must implement get_template_url") + def get_dict(self): - # fetch options - opts = {} - for o in self.options.all(): - opts[o.key] = o.value - # parse dict return { 'name': self.name, - 'template_url': self.template_url, - 'options': opts + 'template_url': self.get_template_url(), + 'edit_template_url': self.get_edit_template_url(), + 'options': {} } def __str__(self): return self.name + +class ABBInfoItem(InfoItem): + def get_template_url(self): + return "/static/html/abb.html" + def get_edit_template_url(self): + return "/static/html/generic_edit.html" + +class ImageInfoItem(InfoItem): + img = models.ImageField(upload_to="infoimages/") + + def get_template_url(self): + #get param to avoid angular from optimizing same template with different options + return "/static/html/generic_image.html?img={}".format(self.name) + + def get_edit_template_url(self): + return "/static/html/generic_image_edit.html" + + def get_dict(self): + d = super().get_dict() + d["options"] = {'img': self.img.url} + return d + +class ExternalImageInfoItem(InfoItem): + url = models.TextField() + + def get_template_url(self): + return "/static/html/generic_image.html?img={}".format(self.name) + + def get_edit_template_url(self): + return "/static/html/generic_external_image_edit.html" + + def get_dict(self): + d = super().get_dict() + d["options"] = {'img': self.url} + return d + class InfoInstance(models.Model): rotation = models.ForeignKey('Rotation', related_name='instances') - item = models.ForeignKey('InfoItem') duration = models.FloatField(default=15.0) # seconds + # generic relation to somekind of InfoItem + item_id = models.PositiveIntegerField() + item_type = models.ForeignKey(ContentType,on_delete=models.CASCADE) + item = GenericForeignKey('item_type','item_id') def get_dict(self): return { @@ -32,24 +75,15 @@ class InfoInstance(models.Model): def __str__(self): return "{}: {} ({}s)".format(self.rotation.name, self.item.name, self.duration) -class InfoOption(models.Model): - class __meta__: - unique_together = ("item", "key") - - item = models.ForeignKey('InfoItem', related_name='options') - key = models.CharField(max_length=255) - value = models.CharField(max_length=255) - - def __str__(self): - return "{}: {} -> {}".format(self.item.name, self.key, self.value) - class Rotation(models.Model): name = models.CharField(max_length=255) def get_dict(self): - # exclude expired items from rotation (note: using filter would exclude items with no expide_date) - instances = self.instances.exclude(item__expire_date__lt=timezone.now()) - instance_list = list(map(lambda i:i.get_dict(), instances)) + # exclude expired items from rotation (note: using tricky syntax to avoid excluding items with no expire_date) + now = timezone.now() + instances = self.instances.all() + filtered = filter(lambda i: (i.item.expire_date or now) >= now, list(instances)) + instance_list = list(map(lambda i:i.get_dict(), filtered)) return { 'name': self.name, diff --git a/infoscreen/static/html/genericimage.html b/infoscreen/static/html/generic_image.html similarity index 100% rename from infoscreen/static/html/genericimage.html rename to infoscreen/static/html/generic_image.html diff --git a/infoscreen/views.py b/infoscreen/views.py index 888b809..9637eb4 100644 --- a/infoscreen/views.py +++ b/infoscreen/views.py @@ -5,7 +5,6 @@ from infoscreen.models import ABBJob, Rotation from django.utils import timezone from datetime import datetime, timedelta import json - def index(request,idx, *args, **kwargs): return render(request, 'infoscreen_index.html',{'rotation':idx}) From 08f85d421121b09d7bd98951dc7986c21db03585 Mon Sep 17 00:00:00 2001 From: okalintu Date: Mon, 26 Sep 2016 20:20:31 +0300 Subject: [PATCH 05/11] sori pojat... --- infoscreen/models.py | 125 +- infoscreen/static/html/abb_create.html | 7 + .../html/generic_external_image_create.html | 11 + .../static/html/generic_image_create.html | 10 + infoscreen/static/html/infoscreen_admin.html | 39 + infoscreen/static/js/infoadmin_controllers.js | 129 + .../static/js/infoscreen_controllers.js | 8 +- .../js/ng-file-upload-bower-12.2.11/.versions | 4 + .../FileAPI.flash.swf | Bin 0 -> 71214 bytes .../ng-file-upload-bower-12.2.11/FileAPI.js | 4313 +++++++++++++++++ .../FileAPI.min.js | 6 + .../js/ng-file-upload-bower-12.2.11/LICENSE | 20 + .../js/ng-file-upload-bower-12.2.11/README.md | 5 + .../ng-file-upload-bower-12.2.11/bower.json | 14 + .../ng-file-upload-all.js | 2888 +++++++++++ .../ng-file-upload-all.min.js | 4 + .../ng-file-upload-shim.js | 421 ++ .../ng-file-upload-shim.min.js | 2 + .../ng-file-upload.js | 2466 ++++++++++ .../ng-file-upload.min.js | 3 + .../ng-file-upload-bower-12.2.11/package.js | 12 + infoscreen/templates/infoscreen_admin.html | 17 + infoscreen/views.py | 110 +- sikweb/urls.py | 26 +- 24 files changed, 10619 insertions(+), 21 deletions(-) create mode 100644 infoscreen/static/html/abb_create.html create mode 100644 infoscreen/static/html/generic_external_image_create.html create mode 100644 infoscreen/static/html/generic_image_create.html create mode 100644 infoscreen/static/html/infoscreen_admin.html create mode 100644 infoscreen/static/js/infoadmin_controllers.js create mode 100644 infoscreen/static/js/ng-file-upload-bower-12.2.11/.versions create mode 100755 infoscreen/static/js/ng-file-upload-bower-12.2.11/FileAPI.flash.swf create mode 100644 infoscreen/static/js/ng-file-upload-bower-12.2.11/FileAPI.js create mode 100755 infoscreen/static/js/ng-file-upload-bower-12.2.11/FileAPI.min.js create mode 100644 infoscreen/static/js/ng-file-upload-bower-12.2.11/LICENSE create mode 100755 infoscreen/static/js/ng-file-upload-bower-12.2.11/README.md create mode 100755 infoscreen/static/js/ng-file-upload-bower-12.2.11/bower.json create mode 100644 infoscreen/static/js/ng-file-upload-bower-12.2.11/ng-file-upload-all.js create mode 100644 infoscreen/static/js/ng-file-upload-bower-12.2.11/ng-file-upload-all.min.js create mode 100644 infoscreen/static/js/ng-file-upload-bower-12.2.11/ng-file-upload-shim.js create mode 100644 infoscreen/static/js/ng-file-upload-bower-12.2.11/ng-file-upload-shim.min.js create mode 100644 infoscreen/static/js/ng-file-upload-bower-12.2.11/ng-file-upload.js create mode 100644 infoscreen/static/js/ng-file-upload-bower-12.2.11/ng-file-upload.min.js create mode 100644 infoscreen/static/js/ng-file-upload-bower-12.2.11/package.js create mode 100644 infoscreen/templates/infoscreen_admin.html diff --git a/infoscreen/models.py b/infoscreen/models.py index edc3869..f459318 100644 --- a/infoscreen/models.py +++ b/infoscreen/models.py @@ -1,5 +1,7 @@ from django.db import models +from django import forms from django.utils import timezone +from datetime import datetime from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType @@ -11,54 +13,120 @@ class InfoItem(models.Model): def get_template_url(self): raise NotImplementedError("inheriting classes must implement get_template_url") - def get_edit_template_url(self): - raise NotImplementedError("inheriting classes must implement get_template_url") + @staticmethod + def get_create_template_url(): + raise NotImplementedError("inheriting classes must implement get_create_template_url") + + @classmethod + def create_from_dict(cls,d): + item = cls() + item.update_from_dict(d) + return item + + def update_from_dict(self,d): + try: + expire_date = d.pop('expire_date', None) + self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S") + except: + pass + dmap = { + 'name': 'name', + } + for k,v in d.items(): + try: + self.__setattr__(dmap[k],v) + except KeyError: + pass + self.save() + def get_dict(self): return { + 'id': self.id, 'name': self.name, + 'item_type': ContentType.objects.get_for_model(self).id, 'template_url': self.get_template_url(), - 'edit_template_url': self.get_edit_template_url(), + 'create_template_url': self.get_create_template_url(), 'options': {} } + + def delete(self): + # since generic foreignkeys suck. delete infoitems pointing here manually + InfoInstance.objects.filter(item_id=self.id, item_type=ContentType.objects.get_for_model(self)).delete() + super().delete() + + @classmethod + def get_subclasses(cls): + for subclass in cls.__subclasses__(): + yield from subclass.get_subclasses() + yield subclass + def __str__(self): return self.name + class ABBInfoItem(InfoItem): def get_template_url(self): return "/static/html/abb.html" - def get_edit_template_url(self): - return "/static/html/generic_edit.html" + @staticmethod + def get_create_template_url(): + return "/static/html/abb_create.html" class ImageInfoItem(InfoItem): img = models.ImageField(upload_to="infoimages/") def get_template_url(self): #get param to avoid angular from optimizing same template with different options - return "/static/html/generic_image.html?img={}".format(self.name) + return "/static/html/generic_image.html?img={}".format(self.name) - def get_edit_template_url(self): - return "/static/html/generic_image_edit.html" + @staticmethod + def get_create_template_url(): + return "/static/html/generic_image_create.html" def get_dict(self): d = super().get_dict() d["options"] = {'img': self.img.url} return d - + class ExternalImageInfoItem(InfoItem): url = models.TextField() def get_template_url(self): return "/static/html/generic_image.html?img={}".format(self.name) - def get_edit_template_url(self): - return "/static/html/generic_external_image_edit.html" + @staticmethod + def get_create_template_url(): + return "/static/html/generic_external_image_create.html" def get_dict(self): d = super().get_dict() d["options"] = {'img': self.url} return d + @classmethod + def create_from_dict(cls,d): + item = cls() + item.update_from_dict(d) + return item + + def update_from_dict(self,d): + try: + expire_date = d.pop('expire_date', None) + self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S") + except: + pass + + dmap = { + 'name': 'name', + 'url': 'url', + } + for k,v in d.items(): + try: + self.__setattr__(dmap[k],v) + except KeyError: + pass + self.save() + class InfoInstance(models.Model): rotation = models.ForeignKey('Rotation', related_name='instances') duration = models.FloatField(default=15.0) # seconds @@ -67,8 +135,27 @@ class InfoInstance(models.Model): item_type = models.ForeignKey(ContentType,on_delete=models.CASCADE) item = GenericForeignKey('item_type','item_id') + @classmethod + def create_from_dict(cls,d): + try: + rotation = Rotation.objects.get(pk=int(d["rotation_id"])) + ct = ContentType.objects.get_for_id(int(d["item_type"])) + item = ct.get_object_for_this_type(pk=int(d["item_id"])) + duration = float(d["duration"]) + except: + raise RuntimeError("invalid parameters supplied supplied") + try: + return cls.objects.create( + rotation=rotation, + item=item, + duration=duration + ) + except: + raise RuntimeError("error while adding instance to db") + def get_dict(self): return { + 'id':self.id, 'item': self.item.get_dict(), 'duration': self.duration, } @@ -77,7 +164,7 @@ class InfoInstance(models.Model): class Rotation(models.Model): name = models.CharField(max_length=255) - + def get_dict(self): # exclude expired items from rotation (note: using tricky syntax to avoid excluding items with no expire_date) now = timezone.now() @@ -86,9 +173,15 @@ class Rotation(models.Model): instance_list = list(map(lambda i:i.get_dict(), filtered)) return { + 'id':self.id, 'name': self.name, 'instances': instance_list, } + def get_list(self): + return { + 'id':self.id, + 'name':self.name, + } def __str__(self): return self.name @@ -107,3 +200,11 @@ class ABBJob(models.Model): def __str__(self): return self.title + +class ImageUploadForm(forms.Form): + ''' + Form used to handle imageuploads to + infoscreen app + ''' + name = forms.CharField() + image = forms.ImageField() diff --git a/infoscreen/static/html/abb_create.html b/infoscreen/static/html/abb_create.html new file mode 100644 index 0000000..da30aae --- /dev/null +++ b/infoscreen/static/html/abb_create.html @@ -0,0 +1,7 @@ +
+
+ + +
+ +
diff --git a/infoscreen/static/html/generic_external_image_create.html b/infoscreen/static/html/generic_external_image_create.html new file mode 100644 index 0000000..6363240 --- /dev/null +++ b/infoscreen/static/html/generic_external_image_create.html @@ -0,0 +1,11 @@ +
+
+ + +
+
+ + +
+ +
diff --git a/infoscreen/static/html/generic_image_create.html b/infoscreen/static/html/generic_image_create.html new file mode 100644 index 0000000..6a76c50 --- /dev/null +++ b/infoscreen/static/html/generic_image_create.html @@ -0,0 +1,10 @@ +
+
+ + +
+
+ +
+ +
diff --git a/infoscreen/static/html/infoscreen_admin.html b/infoscreen/static/html/infoscreen_admin.html new file mode 100644 index 0000000..df26394 --- /dev/null +++ b/infoscreen/static/html/infoscreen_admin.html @@ -0,0 +1,39 @@ +
+
+

Info items

+ + + + + + + + +
ItemSet durationAdd to rotationDelete
{{i.name}}
+

Create new item

+ Item type + +
+
+
+

Rotations

+ + + + + + +
RotationSelect
{{r.name}}
+

Rotation: {{selected_rot.name}}

+ + + + + +
InstanceDurationDelete
{{i.item.name}}{{i.duration}}s +
+ +
0){ + self.getRotation(self.rotations[0].id); + } + }); + } + this.deleteInstance = function(id){ + $http.delete("/infoscreen/instance/"+id).then(function(response){ + self.getRotation(self.selected_rot.id); + }); + } +}]); +app.service("ItemList", ["$http",'InstanceList', function($http, InstanceList){ + var self = this; + self.items = [] + this.loadItems = function(){ + $http.get("/infoscreen/items").then(function(response){ + self.items = response.data; + }); + }; + this.getItems = function(){ + return self.items; + }; + this.deleteItem = function(type_id, item_id){ + $http.delete("/infoscreen/delete_item/"+type_id+"/"+item_id).then(function(){ + self.loadItems(); + // refresh instances because deleting item may cascade to instances + InstanceList.getRotation(InstanceList.selected_rot.id); + }); + }; + this.refreshCB = function(response){ + self.loadItems(); + }; +}]); + +app.controller('infoadmin_ctrl', function($scope, $http, ItemList, InstanceList){ + // init items + $scope.rotations = []; + $scope.infoitems = []; + $scope.selected_rot= {}; + // helpers + $scope.deleteItem = ItemList.deleteItem; + $scope.selectRotation = InstanceList.getRotation; + $scope.createInstance = InstanceList.createInstance; + $scope.deleteInstance = InstanceList.deleteInstance; + // fetch data + $scope.$watch(InstanceList.listInstances, function(instances){ + $scope.selected_rot = instances; + }); + $scope.$watch(InstanceList.listRotations, function(rotations){ + $scope.rotations = rotations; + }); + InstanceList.getRotations(); + + $http.get("/infoscreen/types").then(function(response){ + $scope.infotypes = response.data; + }); + $scope.$watch(ItemList.getItems, function(items){ + $scope.infoitems = items + }); + ItemList.loadItems(); +}); +app.controller('infoadmin_externalimage_create', function($scope, $http,ItemList){ + $scope.item = {} + $scope.send = function(){ + $http.post("/infoscreen/create_external_image", $scope.item).then(ItemList.loadItems) + } +}); +app.controller('infoadmin_abbitem_create', function($scope, $http,ItemList){ + $scope.item = {} + $scope.send = function(){ + $http.post("/infoscreen/create_abbitem", $scope.item).then(ItemList.loadItems) + } +}); + +app.controller('infoadmin_image_create', ['$scope', 'Upload', '$timeout',"ItemList", function ($scope, Upload, $timeout,ItemList) { + $scope.send = function(file) { + file.upload = Upload.upload({ + url: '/infoscreen/create_image', + data: {name: $scope.name, image: file}, + }).then(function (response) { + $timeout(function () { + file.result = response.data; + ItemList.loadItems(); + $scope.name= "" + $scope.img = {} + }); + },function (response) { + if (response.status > 0) + $scope.errorMsg = response.status + ': ' + response.data; + }, function (evt) { + file.progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total)); + }); + } +}]); diff --git a/infoscreen/static/js/infoscreen_controllers.js b/infoscreen/static/js/infoscreen_controllers.js index 3733d80..259abf0 100644 --- a/infoscreen/static/js/infoscreen_controllers.js +++ b/infoscreen/static/js/infoscreen_controllers.js @@ -1,3 +1,4 @@ + var app = angular.module('infoApp', ['ngAnimate', 'ngRoute']); app.controller('infoscreen_main', function($scope,$http,$timeout){ var templates = []; @@ -27,7 +28,7 @@ app.controller('infoscreen_main', function($scope,$http,$timeout){ for (key in temp.item.options){ $scope[key] = temp.item.options[key] } - } + } }; $timeout($scope.next, temp.duration * 1000); } @@ -38,7 +39,7 @@ app.controller('ABBController', function($scope, $http){ $scope.jobs = response.data; }) }); -app.controller('timetableCtrl', +app.controller('timetableCtrl', function($scope, $http, $interval) { function load(){ $http.get('/static/js/hsl.json') @@ -89,7 +90,7 @@ app.controller('timetableCtrl', if(e){ $scope.arr.push(z); } - + } } function pad(num, size) { @@ -122,4 +123,3 @@ app.controller('timetableCtrl', var z=$interval(load,60000); } ); - diff --git a/infoscreen/static/js/ng-file-upload-bower-12.2.11/.versions b/infoscreen/static/js/ng-file-upload-bower-12.2.11/.versions new file mode 100644 index 0000000..79b42c0 --- /dev/null +++ b/infoscreen/static/js/ng-file-upload-bower-12.2.11/.versions @@ -0,0 +1,4 @@ +angular:angular@1.2.24 +danialf:ng-file-upload@12.2.10 +meteor@1.1.10 +underscore@1.0.4 diff --git a/infoscreen/static/js/ng-file-upload-bower-12.2.11/FileAPI.flash.swf b/infoscreen/static/js/ng-file-upload-bower-12.2.11/FileAPI.flash.swf new file mode 100755 index 0000000000000000000000000000000000000000..65de396c797dcf7a63cc5b05f8424f4f985ba242 GIT binary patch literal 71214 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 EeqW}N^ literal 0 HcmV?d00001 diff --git a/infoscreen/static/js/ng-file-upload-bower-12.2.11/FileAPI.js b/infoscreen/static/js/ng-file-upload-bower-12.2.11/FileAPI.js new file mode 100644 index 0000000..5caa110 --- /dev/null +++ b/infoscreen/static/js/ng-file-upload-bower-12.2.11/FileAPI.js @@ -0,0 +1,4313 @@ +/*! FileAPI 2.0.7 - BSD | git://github.com/mailru/FileAPI.git + * FileAPI — a set of javascript tools for working with files. Multiupload, drag'n'drop and chunked file upload. Images: crop, resize and auto orientation by EXIF. + */ + +/* + * JavaScript Canvas to Blob 2.0.5 + * https://github.com/blueimp/JavaScript-Canvas-to-Blob + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + * + * Based on stackoverflow user Stoive's code snippet: + * http://stackoverflow.com/q/4998908 + */ + +/*jslint nomen: true, regexp: true */ +/*global window, atob, Blob, ArrayBuffer, Uint8Array */ + +(function (window) { + 'use strict'; + var CanvasPrototype = window.HTMLCanvasElement && + window.HTMLCanvasElement.prototype, + hasBlobConstructor = window.Blob && (function () { + try { + return Boolean(new Blob()); + } catch (e) { + return false; + } + }()), + hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array && + (function () { + try { + return new Blob([new Uint8Array(100)]).size === 100; + } catch (e) { + return false; + } + }()), + BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || + window.MozBlobBuilder || window.MSBlobBuilder, + dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob && + window.ArrayBuffer && window.Uint8Array && function (dataURI) { + var byteString, + arrayBuffer, + intArray, + i, + mimeString, + bb; + if (dataURI.split(',')[0].indexOf('base64') >= 0) { + // Convert base64 to raw binary data held in a string: + byteString = atob(dataURI.split(',')[1]); + } else { + // Convert base64/URLEncoded data component to raw binary data: + byteString = decodeURIComponent(dataURI.split(',')[1]); + } + // Write the bytes of the string to an ArrayBuffer: + arrayBuffer = new ArrayBuffer(byteString.length); + intArray = new Uint8Array(arrayBuffer); + for (i = 0; i < byteString.length; i += 1) { + intArray[i] = byteString.charCodeAt(i); + } + // Separate out the mime component: + mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; + // Write the ArrayBuffer (or ArrayBufferView) to a blob: + if (hasBlobConstructor) { + return new Blob( + [hasArrayBufferViewSupport ? intArray : arrayBuffer], + {type: mimeString} + ); + } + bb = new BlobBuilder(); + bb.append(arrayBuffer); + return bb.getBlob(mimeString); + }; + if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) { + if (CanvasPrototype.mozGetAsFile) { + CanvasPrototype.toBlob = function (callback, type, quality) { + if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) { + callback(dataURLtoBlob(this.toDataURL(type, quality))); + } else { + callback(this.mozGetAsFile('blob', type)); + } + }; + } else if (CanvasPrototype.toDataURL && dataURLtoBlob) { + CanvasPrototype.toBlob = function (callback, type, quality) { + callback(dataURLtoBlob(this.toDataURL(type, quality))); + }; + } + } + window.dataURLtoBlob = dataURLtoBlob; +})(window); + +/*jslint evil: true */ +/*global window, URL, webkitURL, ActiveXObject */ + +(function (window, undef){ + 'use strict'; + + var + gid = 1, + noop = function (){}, + + document = window.document, + doctype = document.doctype || {}, + userAgent = window.navigator.userAgent, + + // https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48 + apiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL), + + Blob = window.Blob, + File = window.File, + FileReader = window.FileReader, + FormData = window.FormData, + + + XMLHttpRequest = window.XMLHttpRequest, + jQuery = window.jQuery, + + html5 = !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary))) + && !(/safari\//i.test(userAgent) && !/chrome\//i.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25 + + cors = html5 && ('withCredentials' in (new XMLHttpRequest)), + + chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice), + + // https://github.com/blueimp/JavaScript-Canvas-to-Blob + dataURLtoBlob = window.dataURLtoBlob, + + + _rimg = /img/i, + _rcanvas = /canvas/i, + _rimgcanvas = /img|canvas/i, + _rinput = /input/i, + _rdata = /^data:[^,]+,/, + + _toString = {}.toString, + + + Math = window.Math, + + _SIZE_CONST = function (pow){ + pow = new window.Number(Math.pow(1024, pow)); + pow.from = function (sz){ return Math.round(sz * this); }; + return pow; + }, + + _elEvents = {}, // element event listeners + _infoReader = [], // list of file info processors + + _readerEvents = 'abort progress error load loadend', + _xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '), + + currentTarget = 'currentTarget', // for minimize + preventDefault = 'preventDefault', // and this too + + _isArray = function (ar) { + return ar && ('length' in ar); + }, + + /** + * Iterate over a object or array + */ + _each = function (obj, fn, ctx){ + if( obj ){ + if( _isArray(obj) ){ + for( var i = 0, n = obj.length; i < n; i++ ){ + if( i in obj ){ + fn.call(ctx, obj[i], i, obj); + } + } + } + else { + for( var key in obj ){ + if( obj.hasOwnProperty(key) ){ + fn.call(ctx, obj[key], key, obj); + } + } + } + } + }, + + /** + * Merge the contents of two or more objects together into the first object + */ + _extend = function (dst){ + var args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; }; + for( ; i < args.length; i++ ){ + _each(args[i], _ext); + } + return dst; + }, + + /** + * Add event listener + */ + _on = function (el, type, fn){ + if( el ){ + var uid = api.uid(el); + + if( !_elEvents[uid] ){ + _elEvents[uid] = {}; + } + + var isFileReader = (FileReader && el) && (el instanceof FileReader); + _each(type.split(/\s+/), function (type){ + if( jQuery && !isFileReader){ + jQuery.event.add(el, type, fn); + } else { + if( !_elEvents[uid][type] ){ + _elEvents[uid][type] = []; + } + + _elEvents[uid][type].push(fn); + + if( el.addEventListener ){ el.addEventListener(type, fn, false); } + else if( el.attachEvent ){ el.attachEvent('on'+type, fn); } + else { el['on'+type] = fn; } + } + }); + } + }, + + + /** + * Remove event listener + */ + _off = function (el, type, fn){ + if( el ){ + var uid = api.uid(el), events = _elEvents[uid] || {}; + + var isFileReader = (FileReader && el) && (el instanceof FileReader); + _each(type.split(/\s+/), function (type){ + if( jQuery && !isFileReader){ + jQuery.event.remove(el, type, fn); + } + else { + var fns = events[type] || [], i = fns.length; + + while( i-- ){ + if( fns[i] === fn ){ + fns.splice(i, 1); + break; + } + } + + if( el.addEventListener ){ el.removeEventListener(type, fn, false); } + else if( el.detachEvent ){ el.detachEvent('on'+type, fn); } + else { el['on'+type] = null; } + } + }); + } + }, + + + _one = function(el, type, fn){ + _on(el, type, function _(evt){ + _off(el, type, _); + fn(evt); + }); + }, + + + _fixEvent = function (evt){ + if( !evt.target ){ evt.target = window.event && window.event.srcElement || document; } + if( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; } + return evt; + }, + + + _supportInputAttr = function (attr){ + var input = document.createElement('input'); + input.setAttribute('type', "file"); + return attr in input; + }, + + /** + * FileAPI (core object) + */ + api = { + version: '2.0.7', + + cors: false, + html5: true, + media: false, + formData: true, + multiPassResize: true, + + debug: false, + pingUrl: false, + multiFlash: false, + flashAbortTimeout: 0, + withCredentials: true, + + staticPath: './dist/', + + flashUrl: 0, // @default: './FileAPI.flash.swf' + flashImageUrl: 0, // @default: './FileAPI.flash.image.swf' + + postNameConcat: function (name, idx){ + return name + (idx != null ? '['+ idx +']' : ''); + }, + + ext2mime: { + jpg: 'image/jpeg' + , tif: 'image/tiff' + , txt: 'text/plain' + }, + + // Fallback for flash + accept: { + 'image/*': 'art bm bmp dwg dxf cbr cbz fif fpx gif ico iefs jfif jpe jpeg jpg jps jut mcf nap nif pbm pcx pgm pict pm png pnm qif qtif ras rast rf rp svf tga tif tiff xbm xbm xpm xwd' + , 'audio/*': 'm4a flac aac rm mpa wav wma ogg mp3 mp2 m3u mod amf dmf dsm far gdm imf it m15 med okt s3m stm sfx ult uni xm sid ac3 dts cue aif aiff wpl ape mac mpc mpp shn wv nsf spc gym adplug adx dsp adp ymf ast afc hps xs' + , 'video/*': 'm4v 3gp nsv ts ty strm rm rmvb m3u ifo mov qt divx xvid bivx vob nrg img iso pva wmv asf asx ogm m2v avi bin dat dvr-ms mpg mpeg mp4 mkv avc vp3 svq3 nuv viv dv fli flv wpl' + }, + + uploadRetry : 0, + networkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down + + chunkSize : 0, + chunkUploadRetry : 0, + chunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down + + KB: _SIZE_CONST(1), + MB: _SIZE_CONST(2), + GB: _SIZE_CONST(3), + TB: _SIZE_CONST(4), + + EMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=', + + expando: 'fileapi' + (new Date).getTime(), + + uid: function (obj){ + return obj + ? (obj[api.expando] = obj[api.expando] || api.uid()) + : (++gid, api.expando + gid) + ; + }, + + log: function (){ + // ngf fix for IE8 #1071 + if( api.debug && api._supportConsoleLog ){ + if( api._supportConsoleLogApply ){ + console.log.apply(console, arguments); + } + else { + console.log([].join.call(arguments, ' ')); + } + } + }, + + /** + * Create new image + * + * @param {String} [src] + * @param {Function} [fn] 1. error -- boolean, 2. img -- Image element + * @returns {HTMLElement} + */ + newImage: function (src, fn){ + var img = document.createElement('img'); + if( fn ){ + api.event.one(img, 'error load', function (evt){ + fn(evt.type == 'error', img); + img = null; + }); + } + img.src = src; + return img; + }, + + /** + * Get XHR + * @returns {XMLHttpRequest} + */ + getXHR: function (){ + var xhr; + + if( XMLHttpRequest ){ + xhr = new XMLHttpRequest; + } + else if( window.ActiveXObject ){ + try { + xhr = new ActiveXObject('MSXML2.XMLHttp.3.0'); + } catch (e) { + xhr = new ActiveXObject('Microsoft.XMLHTTP'); + } + } + + return xhr; + }, + + isArray: _isArray, + + support: { + dnd: cors && ('ondrop' in document.createElement('div')), + cors: cors, + html5: html5, + chunked: chunked, + dataURI: true, + accept: _supportInputAttr('accept'), + multiple: _supportInputAttr('multiple') + }, + + event: { + on: _on + , off: _off + , one: _one + , fix: _fixEvent + }, + + + throttle: function(fn, delay) { + var id, args; + + return function _throttle(){ + args = arguments; + + if( !id ){ + fn.apply(window, args); + id = setTimeout(function (){ + id = 0; + fn.apply(window, args); + }, delay); + } + }; + }, + + + F: function (){}, + + + parseJSON: function (str){ + var json; + if( window.JSON && JSON.parse ){ + json = JSON.parse(str); + } + else { + json = (new Function('return ('+str.replace(/([\r\n])/g, '\\$1')+');'))(); + } + return json; + }, + + + trim: function (str){ + str = String(str); + return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); + }, + + /** + * Simple Defer + * @return {Object} + */ + defer: function (){ + var + list = [] + , result + , error + , defer = { + resolve: function (err, res){ + defer.resolve = noop; + error = err || false; + result = res; + + while( res = list.shift() ){ + res(error, result); + } + }, + + then: function (fn){ + if( error !== undef ){ + fn(error, result); + } else { + list.push(fn); + } + } + }; + + return defer; + }, + + queue: function (fn){ + var + _idx = 0 + , _length = 0 + , _fail = false + , _end = false + , queue = { + inc: function (){ + _length++; + }, + + next: function (){ + _idx++; + setTimeout(queue.check, 0); + }, + + check: function (){ + (_idx >= _length) && !_fail && queue.end(); + }, + + isFail: function (){ + return _fail; + }, + + fail: function (){ + !_fail && fn(_fail = true); + }, + + end: function (){ + if( !_end ){ + _end = true; + fn(); + } + } + } + ; + return queue; + }, + + + /** + * For each object + * + * @param {Object|Array} obj + * @param {Function} fn + * @param {*} [ctx] + */ + each: _each, + + + /** + * Async for + * @param {Array} array + * @param {Function} callback + */ + afor: function (array, callback){ + var i = 0, n = array.length; + + if( _isArray(array) && n-- ){ + (function _next(){ + callback(n != i && _next, array[i], i++); + })(); + } + else { + callback(false); + } + }, + + + /** + * Merge the contents of two or more objects together into the first object + * + * @param {Object} dst + * @return {Object} + */ + extend: _extend, + + + /** + * Is file? + * @param {File} file + * @return {Boolean} + */ + isFile: function (file){ + return _toString.call(file) === '[object File]'; + }, + + + /** + * Is blob? + * @param {Blob} blob + * @returns {Boolean} + */ + isBlob: function (blob) { + return this.isFile(blob) || (_toString.call(blob) === '[object Blob]'); + }, + + + /** + * Is canvas element + * + * @param {HTMLElement} el + * @return {Boolean} + */ + isCanvas: function (el){ + return el && _rcanvas.test(el.nodeName); + }, + + + getFilesFilter: function (filter){ + filter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || ''); + return filter ? new RegExp('('+ filter.replace(/\./g, '\\.').replace(/,/g, '|') +')$', 'i') : /./; + }, + + + + /** + * Read as DataURL + * + * @param {File|Element} file + * @param {Function} fn + */ + readAsDataURL: function (file, fn){ + if( api.isCanvas(file) ){ + _emit(file, fn, 'load', api.toDataURL(file)); + } + else { + _readAs(file, fn, 'DataURL'); + } + }, + + + /** + * Read as Binary string + * + * @param {File} file + * @param {Function} fn + */ + readAsBinaryString: function (file, fn){ + if( _hasSupportReadAs('BinaryString') ){ + _readAs(file, fn, 'BinaryString'); + } else { + // Hello IE10! + _readAs(file, function (evt){ + if( evt.type == 'load' ){ + try { + // dataURL -> binaryString + evt.result = api.toBinaryString(evt.result); + } catch (e){ + evt.type = 'error'; + evt.message = e.toString(); + } + } + fn(evt); + }, 'DataURL'); + } + }, + + + /** + * Read as ArrayBuffer + * + * @param {File} file + * @param {Function} fn + */ + readAsArrayBuffer: function(file, fn){ + _readAs(file, fn, 'ArrayBuffer'); + }, + + + /** + * Read as text + * + * @param {File} file + * @param {String} encoding + * @param {Function} [fn] + */ + readAsText: function(file, encoding, fn){ + if( !fn ){ + fn = encoding; + encoding = 'utf-8'; + } + + _readAs(file, fn, 'Text', encoding); + }, + + + /** + * Convert image or canvas to DataURL + * + * @param {Element} el Image or Canvas element + * @param {String} [type] mime-type + * @return {String} + */ + toDataURL: function (el, type){ + if( typeof el == 'string' ){ + return el; + } + else if( el.toDataURL ){ + return el.toDataURL(type || 'image/png'); + } + }, + + + /** + * Canvert string, image or canvas to binary string + * + * @param {String|Element} val + * @return {String} + */ + toBinaryString: function (val){ + return window.atob(api.toDataURL(val).replace(_rdata, '')); + }, + + + /** + * Read file or DataURL as ImageElement + * + * @param {File|String} file + * @param {Function} fn + * @param {Boolean} [progress] + */ + readAsImage: function (file, fn, progress){ + if( api.isFile(file) ){ + if( apiURL ){ + /** @namespace apiURL.createObjectURL */ + var data = apiURL.createObjectURL(file); + if( data === undef ){ + _emit(file, fn, 'error'); + } + else { + api.readAsImage(data, fn, progress); + } + } + else { + api.readAsDataURL(file, function (evt){ + if( evt.type == 'load' ){ + api.readAsImage(evt.result, fn, progress); + } + else if( progress || evt.type == 'error' ){ + _emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total }); + } + }); + } + } + else if( api.isCanvas(file) ){ + _emit(file, fn, 'load', file); + } + else if( _rimg.test(file.nodeName) ){ + if( file.complete ){ + _emit(file, fn, 'load', file); + } + else { + var events = 'error abort load'; + _one(file, events, function _fn(evt){ + if( evt.type == 'load' && apiURL ){ + /** @namespace apiURL.revokeObjectURL */ + apiURL.revokeObjectURL(file.src); + } + + _off(file, events, _fn); + _emit(file, fn, evt, file); + }); + } + } + else if( file.iframe ){ + _emit(file, fn, { type: 'error' }); + } + else { + // Created image + var img = api.newImage(file.dataURL || file); + api.readAsImage(img, fn, progress); + } + }, + + + /** + * Make file by name + * + * @param {String} name + * @return {Array} + */ + checkFileObj: function (name){ + var file = {}, accept = api.accept; + + if( typeof name == 'object' ){ + file = name; + } + else { + file.name = (name + '').split(/\\|\//g).pop(); + } + + if( file.type == null ){ + file.type = file.name.split('.').pop(); + } + + _each(accept, function (ext, type){ + ext = new RegExp(ext.replace(/\s/g, '|'), 'i'); + if( ext.test(file.type) || api.ext2mime[file.type] ){ + file.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type); + } + }); + + return file; + }, + + + /** + * Get drop files + * + * @param {Event} evt + * @param {Function} callback + */ + getDropFiles: function (evt, callback){ + var + files = [] + , dataTransfer = _getDataTransfer(evt) + , entrySupport = _isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0]) + , queue = api.queue(function (){ callback(files); }) + ; + + _each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){ + queue.inc(); + + try { + if( entrySupport ){ + _readEntryAsFiles(item, function (err, entryFiles){ + if( err ){ + api.log('[err] getDropFiles:', err); + } else { + files.push.apply(files, entryFiles); + } + queue.next(); + }); + } + else { + _isRegularFile(item, function (yes){ + yes && files.push(item); + queue.next(); + }); + } + } + catch( err ){ + queue.next(); + api.log('[err] getDropFiles: ', err); + } + }); + + queue.check(); + }, + + + /** + * Get file list + * + * @param {HTMLInputElement|Event} input + * @param {String|Function} [filter] + * @param {Function} [callback] + * @return {Array|Null} + */ + getFiles: function (input, filter, callback){ + var files = []; + + if( callback ){ + api.filterFiles(api.getFiles(input), filter, callback); + return null; + } + + if( input.jquery ){ + // jQuery object + input.each(function (){ + files = files.concat(api.getFiles(this)); + }); + input = files; + files = []; + } + + if( typeof filter == 'string' ){ + filter = api.getFilesFilter(filter); + } + + if( input.originalEvent ){ + // jQuery event + input = _fixEvent(input.originalEvent); + } + else if( input.srcElement ){ + // IE Event + input = _fixEvent(input); + } + + + if( input.dataTransfer ){ + // Drag'n'Drop + input = input.dataTransfer; + } + else if( input.target ){ + // Event + input = input.target; + } + + if( input.files ){ + // Input[type="file"] + files = input.files; + + if( !html5 ){ + // Partial support for file api + files[0].blob = input; + files[0].iframe = true; + } + } + else if( !html5 && isInputFile(input) ){ + if( api.trim(input.value) ){ + files = [api.checkFileObj(input.value)]; + files[0].blob = input; + files[0].iframe = true; + } + } + else if( _isArray(input) ){ + files = input; + } + + return api.filter(files, function (file){ return !filter || filter.test(file.name); }); + }, + + + /** + * Get total file size + * @param {Array} files + * @return {Number} + */ + getTotalSize: function (files){ + var size = 0, i = files && files.length; + while( i-- ){ + size += files[i].size; + } + return size; + }, + + + /** + * Get image information + * + * @param {File} file + * @param {Function} fn + */ + getInfo: function (file, fn){ + var info = {}, readers = _infoReader.concat(); + + if( api.isFile(file) ){ + (function _next(){ + var reader = readers.shift(); + if( reader ){ + if( reader.test(file.type) ){ + reader(file, function (err, res){ + if( err ){ + fn(err); + } + else { + _extend(info, res); + _next(); + } + }); + } + else { + _next(); + } + } + else { + fn(false, info); + } + })(); + } + else { + fn('not_support_info', info); + } + }, + + + /** + * Add information reader + * + * @param {RegExp} mime + * @param {Function} fn + */ + addInfoReader: function (mime, fn){ + fn.test = function (type){ return mime.test(type); }; + _infoReader.push(fn); + }, + + + /** + * Filter of array + * + * @param {Array} input + * @param {Function} fn + * @return {Array} + */ + filter: function (input, fn){ + var result = [], i = 0, n = input.length, val; + + for( ; i < n; i++ ){ + if( i in input ){ + val = input[i]; + if( fn.call(val, val, i, input) ){ + result.push(val); + } + } + } + + return result; + }, + + + /** + * Filter files + * + * @param {Array} files + * @param {Function} eachFn + * @param {Function} resultFn + */ + filterFiles: function (files, eachFn, resultFn){ + if( files.length ){ + // HTML5 or Flash + var queue = files.concat(), file, result = [], deleted = []; + + (function _next(){ + if( queue.length ){ + file = queue.shift(); + api.getInfo(file, function (err, info){ + (eachFn(file, err ? false : info) ? result : deleted).push(file); + _next(); + }); + } + else { + resultFn(result, deleted); + } + })(); + } + else { + resultFn([], files); + } + }, + + + upload: function (options){ + options = _extend({ + jsonp: 'callback' + , prepare: api.F + , beforeupload: api.F + , upload: api.F + , fileupload: api.F + , fileprogress: api.F + , filecomplete: api.F + , progress: api.F + , complete: api.F + , pause: api.F + , imageOriginal: true + , chunkSize: api.chunkSize + , chunkUploadRetry: api.chunkUploadRetry + , uploadRetry: api.uploadRetry + }, options); + + + if( options.imageAutoOrientation && !options.imageTransform ){ + options.imageTransform = { rotate: 'auto' }; + } + + + var + proxyXHR = new api.XHR(options) + , dataArray = this._getFilesDataArray(options.files) + , _this = this + , _total = 0 + , _loaded = 0 + , _nextFile + , _complete = false + ; + + + // calc total size + _each(dataArray, function (data){ + _total += data.size; + }); + + // Array of files + proxyXHR.files = []; + _each(dataArray, function (data){ + proxyXHR.files.push(data.file); + }); + + // Set upload status props + proxyXHR.total = _total; + proxyXHR.loaded = 0; + proxyXHR.filesLeft = dataArray.length; + + // emit "beforeupload" event + options.beforeupload(proxyXHR, options); + + // Upload by file + _nextFile = function (){ + var + data = dataArray.shift() + , _file = data && data.file + , _fileLoaded = false + , _fileOptions = _simpleClone(options) + ; + + proxyXHR.filesLeft = dataArray.length; + + if( _file && _file.name === api.expando ){ + _file = null; + api.log('[warn] FileAPI.upload() — called without files'); + } + + if( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){ + // Mark active job + _complete = false; + + // Set current upload file + proxyXHR.currentFile = _file; + + // Prepare file options + if (_file && options.prepare(_file, _fileOptions) === false) { + _nextFile.call(_this); + return; + } + _fileOptions.file = _file; + + _this._getFormData(_fileOptions, data, function (form){ + if( !_loaded ){ + // emit "upload" event + options.upload(proxyXHR, options); + } + + var xhr = new api.XHR(_extend({}, _fileOptions, { + + upload: _file ? function (){ + // emit "fileupload" event + options.fileupload(_file, xhr, _fileOptions); + } : noop, + + progress: _file ? function (evt){ + if( !_fileLoaded ){ + // For ignore the double calls. + _fileLoaded = (evt.loaded === evt.total); + + // emit "fileprogress" event + options.fileprogress({ + type: 'progress' + , total: data.total = evt.total + , loaded: data.loaded = evt.loaded + }, _file, xhr, _fileOptions); + + // emit "progress" event + options.progress({ + type: 'progress' + , total: _total + , loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total))|0 + }, _file, xhr, _fileOptions); + } + } : noop, + + complete: function (err){ + _each(_xhrPropsExport, function (name){ + proxyXHR[name] = xhr[name]; + }); + + if( _file ){ + data.total = (data.total || data.size); + data.loaded = data.total; + + if( !err ) { + // emulate 100% "progress" + this.progress(data); + + // fixed throttle event + _fileLoaded = true; + + // bytes loaded + _loaded += data.size; // data.size != data.total, it's desirable fix this + proxyXHR.loaded = _loaded; + } + + // emit "filecomplete" event + options.filecomplete(err, xhr, _file, _fileOptions); + } + + // upload next file + setTimeout(function () {_nextFile.call(_this);}, 0); + } + })); // xhr + + + // ... + proxyXHR.abort = function (current){ + if (!current) { dataArray.length = 0; } + this.current = current; + xhr.abort(); + }; + + // Start upload + xhr.send(form); + }); + } + else { + var successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204; + options.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options); + // Mark done state + _complete = true; + } + }; + + + // Next tick + setTimeout(_nextFile, 0); + + + // Append more files to the existing request + // first - add them to the queue head/tail + proxyXHR.append = function (files, first) { + files = api._getFilesDataArray([].concat(files)); + + _each(files, function (data) { + _total += data.size; + proxyXHR.files.push(data.file); + if (first) { + dataArray.unshift(data); + } else { + dataArray.push(data); + } + }); + + proxyXHR.statusText = ""; + + if( _complete ){ + _nextFile.call(_this); + } + }; + + + // Removes file from queue by file reference and returns it + proxyXHR.remove = function (file) { + var i = dataArray.length, _file; + while( i-- ){ + if( dataArray[i].file == file ){ + _file = dataArray.splice(i, 1); + _total -= _file.size; + } + } + return _file; + }; + + return proxyXHR; + }, + + + _getFilesDataArray: function (data){ + var files = [], oFiles = {}; + + if( isInputFile(data) ){ + var tmp = api.getFiles(data); + oFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0]; + } + else if( _isArray(data) && isInputFile(data[0]) ){ + _each(data, function (input){ + oFiles[input.name || 'file'] = api.getFiles(input); + }); + } + else { + oFiles = data; + } + + _each(oFiles, function add(file, name){ + if( _isArray(file) ){ + _each(file, function (file){ + add(file, name); + }); + } + else if( file && (file.name || file.image) ){ + files.push({ + name: name + , file: file + , size: file.size + , total: file.size + , loaded: 0 + }); + } + }); + + if( !files.length ){ + // Create fake `file` object + files.push({ file: { name: api.expando } }); + } + + return files; + }, + + + _getFormData: function (options, data, fn){ + var + file = data.file + , name = data.name + , filename = file.name + , filetype = file.type + , trans = api.support.transform && options.imageTransform + , Form = new api.Form + , queue = api.queue(function (){ fn(Form); }) + , isOrignTrans = trans && _isOriginTransform(trans) + , postNameConcat = api.postNameConcat + ; + + // Append data + _each(options.data, function add(val, name){ + if( typeof val == 'object' ){ + _each(val, function (v, i){ + add(v, postNameConcat(name, i)); + }); + } + else { + Form.append(name, val); + } + }); + + (function _addFile(file/**Object*/){ + if( file.image ){ // This is a FileAPI.Image + queue.inc(); + + file.toData(function (err, image){ + // @todo: error + filename = filename || (new Date).getTime()+'.png'; + + _addFile(image); + queue.next(); + }); + } + else if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){ + queue.inc(); + + if( isOrignTrans ){ + // Convert to array for transform function + trans = [trans]; + } + + api.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){ + if( isOrignTrans && !err ){ + if( !dataURLtoBlob && !api.flashEngine ){ + // Canvas.toBlob or Flash not supported, use multipart + Form.multipart = true; + } + + Form.append(name, images[0], filename, trans[0].type || filetype); + } + else { + var addOrigin = 0; + + if( !err ){ + _each(images, function (image, idx){ + if( !dataURLtoBlob && !api.flashEngine ){ + Form.multipart = true; + } + + if( !trans[idx].postName ){ + addOrigin = 1; + } + + Form.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype); + }); + } + + if( err || options.imageOriginal ){ + Form.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype); + } + } + + queue.next(); + }); + } + else if( filename !== api.expando ){ + Form.append(name, file, filename); + } + })(file); + + queue.check(); + }, + + + reset: function (inp, notRemove){ + var parent, clone; + + if( jQuery ){ + clone = jQuery(inp).clone(true).insertBefore(inp).val('')[0]; + if( !notRemove ){ + jQuery(inp).remove(); + } + } else { + parent = inp.parentNode; + clone = parent.insertBefore(inp.cloneNode(true), inp); + clone.value = ''; + + if( !notRemove ){ + parent.removeChild(inp); + } + + _each(_elEvents[api.uid(inp)], function (fns, type){ + _each(fns, function (fn){ + _off(inp, type, fn); + _on(clone, type, fn); + }); + }); + } + + return clone; + }, + + + /** + * Load remote file + * + * @param {String} url + * @param {Function} fn + * @return {XMLHttpRequest} + */ + load: function (url, fn){ + var xhr = api.getXHR(); + if( xhr ){ + xhr.open('GET', url, true); + + if( xhr.overrideMimeType ){ + xhr.overrideMimeType('text/plain; charset=x-user-defined'); + } + + _on(xhr, 'progress', function (/**Event*/evt){ + /** @namespace evt.lengthComputable */ + if( evt.lengthComputable ){ + fn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr); + } + }); + + xhr.onreadystatechange = function(){ + if( xhr.readyState == 4 ){ + xhr.onreadystatechange = null; + if( xhr.status == 200 ){ + url = url.split('/'); + /** @namespace xhr.responseBody */ + var file = { + name: url[url.length-1] + , size: xhr.getResponseHeader('Content-Length') + , type: xhr.getResponseHeader('Content-Type') + }; + file.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText); + fn({ type: 'load', result: file }, xhr); + } + else { + fn({ type: 'error' }, xhr); + } + } + }; + xhr.send(null); + } else { + fn({ type: 'error' }); + } + + return xhr; + }, + + encode64: function (str){ + var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0; + + if( typeof str !== 'string' ){ + str = String(str); + } + + while( i < str.length ){ + //all three "& 0xff" added below are there to fix a known bug + //with bytes returned by xhr.responseText + var + byte1 = str.charCodeAt(i++) & 0xff + , byte2 = str.charCodeAt(i++) & 0xff + , byte3 = str.charCodeAt(i++) & 0xff + , enc1 = byte1 >> 2 + , enc2 = ((byte1 & 3) << 4) | (byte2 >> 4) + , enc3, enc4 + ; + + if( isNaN(byte2) ){ + enc3 = enc4 = 64; + } else { + enc3 = ((byte2 & 15) << 2) | (byte3 >> 6); + enc4 = isNaN(byte3) ? 64 : byte3 & 63; + } + + outStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4); + } + + return outStr; + } + + } // api + ; + + + function _emit(target, fn, name, res, ext){ + var evt = { + type: name.type || name + , target: target + , result: res + }; + _extend(evt, ext); + fn(evt); + } + + + function _hasSupportReadAs(as){ + return FileReader && !!FileReader.prototype['readAs'+as]; + } + + + function _readAs(file, fn, as, encoding){ + if( api.isBlob(file) && _hasSupportReadAs(as) ){ + var Reader = new FileReader; + + // Add event listener + _on(Reader, _readerEvents, function _fn(evt){ + var type = evt.type; + if( type == 'progress' ){ + _emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total }); + } + else if( type == 'loadend' ){ + _off(Reader, _readerEvents, _fn); + Reader = null; + } + else { + _emit(file, fn, evt, evt.target.result); + } + }); + + + try { + // ReadAs ... + if( encoding ){ + Reader['readAs'+as](file, encoding); + } + else { + Reader['readAs'+as](file); + } + } + catch (err){ + _emit(file, fn, 'error', undef, { error: err.toString() }); + } + } + else { + _emit(file, fn, 'error', undef, { error: 'FileReader_not_support_'+as }); + } + } + + + function _isRegularFile(file, callback){ + // http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects + if( !file.type && (file.size % 4096) === 0 && (file.size <= 102400) ){ + if( FileReader ){ + try { + var Reader = new FileReader(); + + _one(Reader, _readerEvents, function (evt){ + var isFile = evt.type != 'error'; + callback(isFile); + if( isFile ){ + Reader.abort(); + } + }); + + Reader.readAsDataURL(file); + } catch( err ){ + callback(false); + } + } + else { + callback(null); + } + } + else { + callback(true); + } + } + + + function _getAsEntry(item){ + var entry; + if( item.getAsEntry ){ entry = item.getAsEntry(); } + else if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); } + return entry; + } + + + function _readEntryAsFiles(entry, callback){ + if( !entry ){ + // error + callback('invalid entry'); + } + else if( entry.isFile ){ + // Read as file + entry.file(function(file){ + // success + file.fullPath = entry.fullPath; + callback(false, [file]); + }, function (err){ + // error + callback('FileError.code: '+err.code); + }); + } + else if( entry.isDirectory ){ + var reader = entry.createReader(), result = []; + + reader.readEntries(function(entries){ + // success + api.afor(entries, function (next, entry){ + _readEntryAsFiles(entry, function (err, files){ + if( err ){ + api.log(err); + } + else { + result = result.concat(files); + } + + if( next ){ + next(); + } + else { + callback(false, result); + } + }); + }); + }, function (err){ + // error + callback('directory_reader: ' + err); + }); + } + else { + _readEntryAsFiles(_getAsEntry(entry), callback); + } + } + + + function _simpleClone(obj){ + var copy = {}; + _each(obj, function (val, key){ + if( val && (typeof val === 'object') && (val.nodeType === void 0) ){ + val = _extend({}, val); + } + copy[key] = val; + }); + return copy; + } + + + function isInputFile(el){ + return _rinput.test(el && el.tagName); + } + + + function _getDataTransfer(evt){ + return (evt.originalEvent || evt || '').dataTransfer || {}; + } + + + function _isOriginTransform(trans){ + var key; + for( key in trans ){ + if( trans.hasOwnProperty(key) ){ + if( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){ + return true; + } + } + } + return false; + } + + + // Add default image info reader + api.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){ + if( !file.__dimensions ){ + var defer = file.__dimensions = api.defer(); + + api.readAsImage(file, function (evt){ + var img = evt.target; + defer.resolve(evt.type == 'load' ? false : 'error', { + width: img.width + , height: img.height + }); + img.src = api.EMPTY_PNG; + img = null; + }); + } + + file.__dimensions.then(callback); + }); + + + /** + * Drag'n'Drop special event + * + * @param {HTMLElement} el + * @param {Function} onHover + * @param {Function} onDrop + */ + api.event.dnd = function (el, onHover, onDrop){ + var _id, _type; + + if( !onDrop ){ + onDrop = onHover; + onHover = api.F; + } + + if( FileReader ){ + // Hover + _on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || function (evt){ + var + types = _getDataTransfer(evt).types + , i = types && types.length + , debounceTrigger = false + ; + + while( i-- ){ + if( ~types[i].indexOf('File') ){ + evt[preventDefault](); + + if( _type !== evt.type ){ + _type = evt.type; // Store current type of event + + if( _type != 'dragleave' ){ + onHover.call(evt[currentTarget], true, evt); + } + + debounceTrigger = true; + } + + break; // exit from "while" + } + } + + if( debounceTrigger ){ + clearTimeout(_id); + _id = setTimeout(function (){ + onHover.call(evt[currentTarget], _type != 'dragleave', evt); + }, 50); + } + }); + + + // Drop + _on(el, 'drop', onDrop.ff = onDrop.ff || function (evt){ + evt[preventDefault](); + + _type = 0; + onHover.call(evt[currentTarget], false, evt); + + api.getDropFiles(evt, function (files){ + onDrop.call(evt[currentTarget], files, evt); + }); + }); + } + else { + api.log("Drag'n'Drop -- not supported"); + } + }; + + + /** + * Remove drag'n'drop + * @param {HTMLElement} el + * @param {Function} onHover + * @param {Function} onDrop + */ + api.event.dnd.off = function (el, onHover, onDrop){ + _off(el, 'dragenter dragleave dragover', onHover.ff); + _off(el, 'drop', onDrop.ff); + }; + + + // Support jQuery + if( jQuery && !jQuery.fn.dnd ){ + jQuery.fn.dnd = function (onHover, onDrop){ + return this.each(function (){ + api.event.dnd(this, onHover, onDrop); + }); + }; + + jQuery.fn.offdnd = function (onHover, onDrop){ + return this.each(function (){ + api.event.dnd.off(this, onHover, onDrop); + }); + }; + } + + // @export + window.FileAPI = _extend(api, window.FileAPI); + + + // Debug info + api.log('FileAPI: ' + api.version); + api.log('protocol: ' + window.location.protocol); + api.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId); + + + // @detect 'x-ua-compatible' + _each(document.getElementsByTagName('meta'), function (meta){ + if( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){ + api.log('meta.http-equiv: ' + meta.getAttribute('content')); + } + }); + + + // configuration + try { + api._supportConsoleLog = !!console.log; + api._supportConsoleLogApply = !!console.log.apply; + } catch (err) {} + + if( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; } + if( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; } + if( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; } +})(window, void 0); + +/*global window, FileAPI, document */ + +(function (api, document, undef) { + 'use strict'; + + var + min = Math.min, + round = Math.round, + getCanvas = function () { return document.createElement('canvas'); }, + support = false, + exifOrientation = { + 8: 270 + , 3: 180 + , 6: 90 + , 7: 270 + , 4: 180 + , 5: 90 + } + ; + + try { + support = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1; + } + catch (e){} + + + function Image(file){ + if( file instanceof Image ){ + var img = new Image(file.file); + api.extend(img.matrix, file.matrix); + return img; + } + else if( !(this instanceof Image) ){ + return new Image(file); + } + + this.file = file; + this.size = file.size || 100; + + this.matrix = { + sx: 0, + sy: 0, + sw: 0, + sh: 0, + dx: 0, + dy: 0, + dw: 0, + dh: 0, + resize: 0, // min, max OR preview + deg: 0, + quality: 1, // jpeg quality + filter: 0 + }; + } + + + Image.prototype = { + image: true, + constructor: Image, + + set: function (attrs){ + api.extend(this.matrix, attrs); + return this; + }, + + crop: function (x, y, w, h){ + if( w === undef ){ + w = x; + h = y; + x = y = 0; + } + return this.set({ sx: x, sy: y, sw: w, sh: h || w }); + }, + + resize: function (w, h, strategy){ + if( /min|max/.test(h) ){ + strategy = h; + h = w; + } + + return this.set({ dw: w, dh: h || w, resize: strategy }); + }, + + preview: function (w, h){ + return this.resize(w, h || w, 'preview'); + }, + + rotate: function (deg){ + return this.set({ deg: deg }); + }, + + filter: function (filter){ + return this.set({ filter: filter }); + }, + + overlay: function (images){ + return this.set({ overlay: images }); + }, + + clone: function (){ + return new Image(this); + }, + + _load: function (image, fn){ + var self = this; + + if( /img|video/i.test(image.nodeName) ){ + fn.call(self, null, image); + } + else { + api.readAsImage(image, function (evt){ + fn.call(self, evt.type != 'load', evt.result); + }); + } + }, + + _apply: function (image, fn){ + var + canvas = getCanvas() + , m = this.getMatrix(image) + , ctx = canvas.getContext('2d') + , width = image.videoWidth || image.width + , height = image.videoHeight || image.height + , deg = m.deg + , dw = m.dw + , dh = m.dh + , w = width + , h = height + , filter = m.filter + , copy // canvas copy + , buffer = image + , overlay = m.overlay + , queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); }) + , renderImageToCanvas = api.renderImageToCanvas + ; + + // Normalize angle + deg = deg - Math.floor(deg/360)*360; + + // For `renderImageToCanvas` + image._type = this.file.type; + + while(m.multipass && min(w/dw, h/dh) > 2 ){ + w = (w/2 + 0.5)|0; + h = (h/2 + 0.5)|0; + + copy = getCanvas(); + copy.width = w; + copy.height = h; + + if( buffer !== image ){ + renderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h); + buffer = copy; + } + else { + buffer = copy; + renderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h); + m.sx = m.sy = m.sw = m.sh = 0; + } + } + + + canvas.width = (deg % 180) ? dh : dw; + canvas.height = (deg % 180) ? dw : dh; + + canvas.type = m.type; + canvas.quality = m.quality; + + ctx.rotate(deg * Math.PI / 180); + renderImageToCanvas(ctx.canvas, buffer + , m.sx, m.sy + , m.sw || buffer.width + , m.sh || buffer.height + , (deg == 180 || deg == 270 ? -dw : 0) + , (deg == 90 || deg == 180 ? -dh : 0) + , dw, dh + ); + dw = canvas.width; + dh = canvas.height; + + // Apply overlay + overlay && api.each([].concat(overlay), function (over){ + queue.inc(); + // preload + var img = new window.Image, fn = function (){ + var + x = over.x|0 + , y = over.y|0 + , w = over.w || img.width + , h = over.h || img.height + , rel = over.rel + ; + + // center | right | left + x = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x); + + // center | bottom | top + y = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y); + + api.event.off(img, 'error load abort', fn); + + try { + ctx.globalAlpha = over.opacity || 1; + ctx.drawImage(img, x, y, w, h); + } + catch (er){} + + queue.next(); + }; + + api.event.on(img, 'error load abort', fn); + img.src = over.src; + + if( img.complete ){ + fn(); + } + }); + + if( filter ){ + queue.inc(); + Image.applyFilter(canvas, filter, queue.next); + } + + queue.check(); + }, + + getMatrix: function (image){ + var + m = api.extend({}, this.matrix) + , sw = m.sw = m.sw || image.videoWidth || image.naturalWidth || image.width + , sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height + , dw = m.dw = m.dw || sw + , dh = m.dh = m.dh || sh + , sf = sw/sh, df = dw/dh + , strategy = m.resize + ; + + if( strategy == 'preview' ){ + if( dw != sw || dh != sh ){ + // Make preview + var w, h; + + if( df >= sf ){ + w = sw; + h = w / df; + } else { + h = sh; + w = h * df; + } + + if( w != sw || h != sh ){ + m.sx = ~~((sw - w)/2); + m.sy = ~~((sh - h)/2); + sw = w; + sh = h; + } + } + } + else if( strategy ){ + if( !(sw > dw || sh > dh) ){ + dw = sw; + dh = sh; + } + else if( strategy == 'min' ){ + dw = round(sf < df ? min(sw, dw) : dh*sf); + dh = round(sf < df ? dw/sf : min(sh, dh)); + } + else { + dw = round(sf >= df ? min(sw, dw) : dh*sf); + dh = round(sf >= df ? dw/sf : min(sh, dh)); + } + } + + m.sw = sw; + m.sh = sh; + m.dw = dw; + m.dh = dh; + m.multipass = api.multiPassResize; + return m; + }, + + _trans: function (fn){ + this._load(this.file, function (err, image){ + if( err ){ + fn(err); + } + else { + try { + this._apply(image, fn); + } catch (err){ + api.log('[err] FileAPI.Image.fn._apply:', err); + fn(err); + } + } + }); + }, + + + get: function (fn){ + if( api.support.transform ){ + var _this = this, matrix = _this.matrix; + + if( matrix.deg == 'auto' ){ + api.getInfo(_this.file, function (err, info){ + // rotate by exif orientation + matrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0; + _this._trans(fn); + }); + } + else { + _this._trans(fn); + } + } + else { + fn('not_support_transform'); + } + + return this; + }, + + + toData: function (fn){ + return this.get(fn); + } + + }; + + + Image.exifOrientation = exifOrientation; + + + Image.transform = function (file, transform, autoOrientation, fn){ + function _transform(err, img){ + // img -- info object + var + images = {} + , queue = api.queue(function (err){ + fn(err, images); + }) + ; + + if( !err ){ + api.each(transform, function (params, name){ + if( !queue.isFail() ){ + var ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function'; + + if( isFn ){ + params(img, ImgTrans); + } + else if( params.width ){ + ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy); + } + else { + if( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){ + ImgTrans.resize(params.maxWidth, params.maxHeight, 'max'); + } + } + + if( params.crop ){ + var crop = params.crop; + ImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height); + } + + if( params.rotate === undef && autoOrientation ){ + params.rotate = 'auto'; + } + + ImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' }); + + if( !isFn ){ + ImgTrans.set({ + deg: params.rotate + , overlay: params.overlay + , filter: params.filter + , quality: params.quality || 1 + }); + } + + queue.inc(); + ImgTrans.toData(function (err, image){ + if( err ){ + queue.fail(); + } + else { + images[name] = image; + queue.next(); + } + }); + } + }); + } + else { + queue.fail(); + } + } + + + // @todo: Оло-ло, нужно рефакторить это место + if( file.width ){ + _transform(false, file); + } else { + api.getInfo(file, _transform); + } + }; + + + // @const + api.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){ + api.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){ + Image[x+'_'+y] = i*3 + j; + Image[y+'_'+x] = i*3 + j; + }); + }); + + + /** + * Trabsform element to canvas + * + * @param {Image|HTMLVideoElement} el + * @returns {Canvas} + */ + Image.toCanvas = function(el){ + var canvas = document.createElement('canvas'); + canvas.width = el.videoWidth || el.width; + canvas.height = el.videoHeight || el.height; + canvas.getContext('2d').drawImage(el, 0, 0); + return canvas; + }; + + + /** + * Create image from DataURL + * @param {String} dataURL + * @param {Object} size + * @param {Function} callback + */ + Image.fromDataURL = function (dataURL, size, callback){ + var img = api.newImage(dataURL); + api.extend(img, size); + callback(img); + }; + + + /** + * Apply filter (caman.js) + * + * @param {Canvas|Image} canvas + * @param {String|Function} filter + * @param {Function} doneFn + */ + Image.applyFilter = function (canvas, filter, doneFn){ + if( typeof filter == 'function' ){ + filter(canvas, doneFn); + } + else if( window.Caman ){ + // http://camanjs.com/guides/ + window.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){ + if( typeof filter == 'string' ){ + this[filter](); + } + else { + api.each(filter, function (val, method){ + this[method](val); + }, this); + } + this.render(doneFn); + }); + } + }; + + + /** + * For load-image-ios.js + */ + api.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){ + try { + return canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh); + } catch (ex) { + api.log('renderImageToCanvas failed'); + throw ex; + } + }; + + + // @export + api.support.canvas = api.support.transform = support; + api.Image = Image; +})(FileAPI, document); + +/* + * JavaScript Load Image iOS scaling fixes 1.0.3 + * https://github.com/blueimp/JavaScript-Load-Image + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * iOS image scaling fixes based on + * https://github.com/stomita/ios-imagefile-megapixel + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, bitwise: true */ +/*global FileAPI, window, document */ + +(function (factory) { + 'use strict'; + factory(FileAPI); +}(function (loadImage) { + 'use strict'; + + // Only apply fixes on the iOS platform: + if (!window.navigator || !window.navigator.platform || + !(/iP(hone|od|ad)/).test(window.navigator.platform)) { + return; + } + + var originalRenderMethod = loadImage.renderImageToCanvas; + + // Detects subsampling in JPEG images: + loadImage.detectSubsampling = function (img) { + var canvas, + context; + if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images + canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + context = canvas.getContext('2d'); + context.drawImage(img, -img.width + 1, 0); + // subsampled image becomes half smaller in rendering size. + // check alpha channel value to confirm image is covering edge pixel or not. + // if alpha value is 0 image is not covering, hence subsampled. + return context.getImageData(0, 0, 1, 1).data[3] === 0; + } + return false; + }; + + // Detects vertical squash in JPEG images: + loadImage.detectVerticalSquash = function (img, subsampled) { + var naturalHeight = img.naturalHeight || img.height, + canvas = document.createElement('canvas'), + context = canvas.getContext('2d'), + data, + sy, + ey, + py, + alpha; + if (subsampled) { + naturalHeight /= 2; + } + canvas.width = 1; + canvas.height = naturalHeight; + context.drawImage(img, 0, 0); + data = context.getImageData(0, 0, 1, naturalHeight).data; + // search image edge pixel position in case it is squashed vertically: + sy = 0; + ey = naturalHeight; + py = naturalHeight; + while (py > sy) { + alpha = data[(py - 1) * 4 + 3]; + if (alpha === 0) { + ey = py; + } else { + sy = py; + } + py = (ey + sy) >> 1; + } + return (py / naturalHeight) || 1; + }; + + // Renders image to canvas while working around iOS image scaling bugs: + // https://github.com/blueimp/JavaScript-Load-Image/issues/13 + loadImage.renderImageToCanvas = function ( + canvas, + img, + sourceX, + sourceY, + sourceWidth, + sourceHeight, + destX, + destY, + destWidth, + destHeight + ) { + if (img._type === 'image/jpeg') { + var context = canvas.getContext('2d'), + tmpCanvas = document.createElement('canvas'), + tileSize = 1024, + tmpContext = tmpCanvas.getContext('2d'), + subsampled, + vertSquashRatio, + tileX, + tileY; + tmpCanvas.width = tileSize; + tmpCanvas.height = tileSize; + context.save(); + subsampled = loadImage.detectSubsampling(img); + if (subsampled) { + sourceX /= 2; + sourceY /= 2; + sourceWidth /= 2; + sourceHeight /= 2; + } + vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled); + if (subsampled || vertSquashRatio !== 1) { + sourceY *= vertSquashRatio; + destWidth = Math.ceil(tileSize * destWidth / sourceWidth); + destHeight = Math.ceil( + tileSize * destHeight / sourceHeight / vertSquashRatio + ); + destY = 0; + tileY = 0; + while (tileY < sourceHeight) { + destX = 0; + tileX = 0; + while (tileX < sourceWidth) { + tmpContext.clearRect(0, 0, tileSize, tileSize); + tmpContext.drawImage( + img, + sourceX, + sourceY, + sourceWidth, + sourceHeight, + -tileX, + -tileY, + sourceWidth, + sourceHeight + ); + context.drawImage( + tmpCanvas, + 0, + 0, + tileSize, + tileSize, + destX, + destY, + destWidth, + destHeight + ); + tileX += tileSize; + destX += destWidth; + } + tileY += tileSize; + destY += destHeight; + } + context.restore(); + return canvas; + } + } + return originalRenderMethod( + canvas, + img, + sourceX, + sourceY, + sourceWidth, + sourceHeight, + destX, + destY, + destWidth, + destHeight + ); + }; + +})); + +/*global window, FileAPI */ + +(function (api, window){ + "use strict"; + + var + document = window.document + , FormData = window.FormData + , Form = function (){ this.items = []; } + , encodeURIComponent = window.encodeURIComponent + ; + + + Form.prototype = { + + append: function (name, blob, file, type){ + this.items.push({ + name: name + , blob: blob && blob.blob || (blob == void 0 ? '' : blob) + , file: blob && (file || blob.name) + , type: blob && (type || blob.type) + }); + }, + + each: function (fn){ + var i = 0, n = this.items.length; + for( ; i < n; i++ ){ + fn.call(this, this.items[i]); + } + }, + + toData: function (fn, options){ + // allow chunked transfer if we have only one file to send + // flag is used below and in XHR._send + options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1; + + if( !api.support.html5 ){ + api.log('FileAPI.Form.toHtmlData'); + this.toHtmlData(fn); + } + else if( !api.formData || this.multipart || !FormData ){ + api.log('FileAPI.Form.toMultipartData'); + this.toMultipartData(fn); + } + else if( options._chunked ){ + api.log('FileAPI.Form.toPlainData'); + this.toPlainData(fn); + } + else { + api.log('FileAPI.Form.toFormData'); + this.toFormData(fn); + } + }, + + _to: function (data, complete, next, arg){ + var queue = api.queue(function (){ + complete(data); + }); + + this.each(function (file){ + next(file, data, queue, arg); + }); + + queue.check(); + }, + + + toHtmlData: function (fn){ + this._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){ + var blob = file.blob, hidden; + + if( file.file ){ + api.reset(blob, true); + // set new name + blob.name = file.name; + blob.disabled = false; + data.appendChild(blob); + } + else { + hidden = document.createElement('input'); + hidden.name = file.name; + hidden.type = 'hidden'; + hidden.value = blob; + data.appendChild(hidden); + } + }); + }, + + toPlainData: function (fn){ + this._to({}, fn, function (file, data, queue){ + if( file.file ){ + data.type = file.file; + } + + if( file.blob.toBlob ){ + // canvas + queue.inc(); + _convertFile(file, function (file, blob){ + data.name = file.name; + data.file = blob; + data.size = blob.length; + data.type = file.type; + queue.next(); + }); + } + else if( file.file ){ + // file + data.name = file.blob.name; + data.file = file.blob; + data.size = file.blob.size; + data.type = file.type; + } + else { + // additional data + if( !data.params ){ + data.params = []; + } + data.params.push(encodeURIComponent(file.name) +"="+ encodeURIComponent(file.blob)); + } + + data.start = -1; + data.end = data.file && data.file.FileAPIReadPosition || -1; + data.retry = 0; + }); + }, + + toFormData: function (fn){ + this._to(new FormData, fn, function (file, data, queue){ + if( file.blob && file.blob.toBlob ){ + queue.inc(); + _convertFile(file, function (file, blob){ + data.append(file.name, blob, file.file); + queue.next(); + }); + } + else if( file.file ){ + data.append(file.name, file.blob, file.file); + } + else { + data.append(file.name, file.blob); + } + + if( file.file ){ + data.append('_'+file.name, file.file); + } + }); + }, + + + toMultipartData: function (fn){ + this._to([], fn, function (file, data, queue, boundary){ + queue.inc(); + _convertFile(file, function (file, blob){ + data.push( + '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (file.file ? '; filename="'+ encodeURIComponent(file.file) +'"' : '') + + (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '') + + '\r\n' + + '\r\n'+ (file.file ? blob : encodeURIComponent(blob)) + + '\r\n') + ); + queue.next(); + }, true); + }, api.expando); + } + }; + + + function _convertFile(file, fn, useBinaryString){ + var blob = file.blob, filename = file.file; + + if( filename ){ + if( !blob.toDataURL ){ + // The Blob is not an image. + api.readAsBinaryString(blob, function (evt){ + if( evt.type == 'load' ){ + fn(file, evt.result); + } + }); + return; + } + + var + mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' } + , type = mime[file.type] ? file.type : 'image/png' + , ext = mime[type] || '.png' + , quality = blob.quality || 1 + ; + + if( !filename.match(new RegExp(ext+'$', 'i')) ){ + // Does not change the current extension, but add a new one. + filename += ext.replace('?', ''); + } + + file.file = filename; + file.type = type; + + if( !useBinaryString && blob.toBlob ){ + blob.toBlob(function (blob){ + fn(file, blob); + }, type, quality); + } + else { + fn(file, api.toBinaryString(blob.toDataURL(type, quality))); + } + } + else { + fn(file, blob); + } + } + + + // @export + api.Form = Form; +})(FileAPI, window); + +/*global window, FileAPI, Uint8Array */ + +(function (window, api){ + "use strict"; + + var + noop = function (){} + , document = window.document + + , XHR = function (options){ + this.uid = api.uid(); + this.xhr = { + abort: noop + , getResponseHeader: noop + , getAllResponseHeaders: noop + }; + this.options = options; + }, + + _xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 } + ; + + + XHR.prototype = { + status: 0, + statusText: '', + constructor: XHR, + + getResponseHeader: function (name){ + return this.xhr.getResponseHeader(name); + }, + + getAllResponseHeaders: function (){ + return this.xhr.getAllResponseHeaders() || {}; + }, + + end: function (status, statusText){ + var _this = this, options = _this.options; + + _this.end = + _this.abort = noop; + _this.status = status; + + if( statusText ){ + _this.statusText = statusText; + } + + api.log('xhr.end:', status, statusText); + options.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _this); + + if( _this.xhr && _this.xhr.node ){ + setTimeout(function (){ + var node = _this.xhr.node; + try { node.parentNode.removeChild(node); } catch (e){} + try { delete window[_this.uid]; } catch (e){} + window[_this.uid] = _this.xhr.node = null; + }, 9); + } + }, + + abort: function (){ + this.end(0, 'abort'); + + if( this.xhr ){ + this.xhr.aborted = true; + this.xhr.abort(); + } + }, + + send: function (FormData){ + var _this = this, options = this.options; + + FormData.toData(function (data){ + // Start uploading + options.upload(options, _this); + _this._send.call(_this, options, data); + }, options); + }, + + _send: function (options, data){ + var _this = this, xhr, uid = _this.uid, onloadFuncName = _this.uid + "Load", url = options.url; + + api.log('XHR._send:', data); + + if( !options.cache ){ + // No cache + url += (~url.indexOf('?') ? '&' : '?') + api.uid(); + } + + if( data.nodeName ){ + var jsonp = options.jsonp; + + // prepare callback in GET + url = url.replace(/([a-z]+)=(\?)/i, '$1='+uid); + + // legacy + options.upload(options, _this); + + var + onPostMessage = function (evt){ + if( ~url.indexOf(evt.origin) ){ + try { + var result = api.parseJSON(evt.data); + if( result.id == uid ){ + complete(result.status, result.statusText, result.response); + } + } catch( err ){ + complete(0, err.message); + } + } + }, + + // jsonp-callack + complete = window[uid] = function (status, statusText, response){ + _this.readyState = 4; + _this.responseText = response; + _this.end(status, statusText); + + api.event.off(window, 'message', onPostMessage); + window[uid] = xhr = transport = window[onloadFuncName] = null; + } + ; + + _this.xhr.abort = function (){ + try { + if( transport.stop ){ transport.stop(); } + else if( transport.contentWindow.stop ){ transport.contentWindow.stop(); } + else { transport.contentWindow.document.execCommand('Stop'); } + } + catch (er) {} + complete(0, "abort"); + }; + + api.event.on(window, 'message', onPostMessage); + + window[onloadFuncName] = function (){ + try { + var + win = transport.contentWindow + , doc = win.document + , result = win.result || api.parseJSON(doc.body.innerHTML) + ; + complete(result.status, result.statusText, result.response); + } catch (e){ + api.log('[transport.onload]', e); + } + }; + + xhr = document.createElement('div'); + xhr.innerHTML = '

' + + '' + + (jsonp && (options.url.indexOf('=?') < 0) ? '' : '') + + '
' + ; + + // get form-data & transport + var + form = xhr.getElementsByTagName('form')[0] + , transport = xhr.getElementsByTagName('iframe')[0] + ; + + form.appendChild(data); + + api.log(form.parentNode.innerHTML); + + // append to DOM + document.body.appendChild(xhr); + + // keep a reference to node-transport + _this.xhr.node = xhr; + + // send + _this.readyState = 2; // loaded + form.submit(); + form = null; + } + else { + // Clean url + url = url.replace(/([a-z]+)=(\?)&?/i, ''); + + // html5 + if (this.xhr && this.xhr.aborted) { + api.log("Error: already aborted"); + return; + } + xhr = _this.xhr = api.getXHR(); + + if (data.params) { + url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&"); + } + + xhr.open('POST', url, true); + + if( api.withCredentials ){ + xhr.withCredentials = "true"; + } + + if( !options.headers || !options.headers['X-Requested-With'] ){ + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + } + + api.each(options.headers, function (val, key){ + xhr.setRequestHeader(key, val); + }); + + + if ( options._chunked ) { + // chunked upload + if( xhr.upload ){ + xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){ + if (!data.retry) { + // show progress only for correct chunk uploads + options.progress({ + type: evt.type + , total: data.size + , loaded: data.start + evt.loaded + , totalSize: data.size + }, _this, options); + } + }, 100), false); + } + + xhr.onreadystatechange = function (){ + var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10); + + _this.status = xhr.status; + _this.statusText = xhr.statusText; + _this.readyState = xhr.readyState; + + if( xhr.readyState == 4 ){ + try { + for( var k in _xhrResponsePostfix ){ + _this['response'+k] = xhr['response'+k]; + } + }catch(_){} + xhr.onreadystatechange = null; + + if (!xhr.status || xhr.status - 201 > 0) { + api.log("Error: " + xhr.status); + // some kind of error + // 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action + // up - server error + if (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) { + // let's try again the same chunk + // only applicable for recoverable error codes 500 && 416 + var delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout; + + // inform about recoverable problems + options.pause(data.file, options); + + // smart restart if server reports about the last known byte + api.log("X-Last-Known-Byte: " + lkb); + if (lkb) { + data.end = lkb; + } else { + data.end = data.start - 1; + if (416 == xhr.status) { + data.end = data.end - options.chunkSize; + } + } + + setTimeout(function () { + _this._send(options, data); + }, delay); + } else { + // no mo retries + _this.end(xhr.status); + } + } else { + // success + data.retry = 0; + + if (data.end == data.size - 1) { + // finished + _this.end(xhr.status); + } else { + // next chunk + + // shift position if server reports about the last known byte + api.log("X-Last-Known-Byte: " + lkb); + if (lkb) { + data.end = lkb; + } + data.file.FileAPIReadPosition = data.end; + + setTimeout(function () { + _this._send(options, data); + }, 0); + } + } + + xhr = null; + } + }; + + data.start = data.end + 1; + data.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start); + + // Retrieve a slice of file + var + file = data.file + , slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1) + ; + + if( data.size && !slice.size ){ + setTimeout(function (){ + _this.end(-1); + }); + } else { + xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size); + xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + encodeURIComponent(data.name)); + xhr.setRequestHeader("Content-Type", data.type || "application/octet-stream"); + + xhr.send(slice); + } + + file = slice = null; + } else { + // single piece upload + if( xhr.upload ){ + // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29 + xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){ + options.progress(evt, _this, options); + }, 100), false); + } + + xhr.onreadystatechange = function (){ + _this.status = xhr.status; + _this.statusText = xhr.statusText; + _this.readyState = xhr.readyState; + + if( xhr.readyState == 4 ){ + for( var k in _xhrResponsePostfix ){ + _this['response'+k] = xhr['response'+k]; + } + xhr.onreadystatechange = null; + + if (!xhr.status || xhr.status > 201) { + api.log("Error: " + xhr.status); + if (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) { + options.retry = (options.retry || 0) + 1; + var delay = api.networkDownRetryTimeout; + + // inform about recoverable problems + options.pause(options.file, options); + + setTimeout(function () { + _this._send(options, data); + }, delay); + } else { + //success + _this.end(xhr.status); + } + } else { + //success + _this.end(xhr.status); + } + + xhr = null; + } + }; + + if( api.isArray(data) ){ + // multipart + xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando); + var rawData = data.join('') +'--_'+ api.expando +'--'; + + /** @namespace xhr.sendAsBinary https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */ + if( xhr.sendAsBinary ){ + xhr.sendAsBinary(rawData); + } + else { + var bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; }); + xhr.send(new Uint8Array(bytes).buffer); + + } + } else { + // FormData + xhr.send(data); + } + } + } + } + }; + + + // @export + api.XHR = XHR; +})(window, FileAPI); + +/** + * @class FileAPI.Camera + * @author RubaXa + * @support Chrome 21+, FF 18+, Opera 12+ + */ + +/*global window, FileAPI, jQuery */ +/** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */ +(function (window, api){ + "use strict"; + + var + URL = window.URL || window.webkitURL, + + document = window.document, + navigator = window.navigator, + + getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia, + + html5 = !!getMedia + ; + + + // Support "media" + api.support.media = html5; + + + var Camera = function (video){ + this.video = video; + }; + + + Camera.prototype = { + isActive: function (){ + return !!this._active; + }, + + + /** + * Start camera streaming + * @param {Function} callback + */ + start: function (callback){ + var + _this = this + , video = _this.video + , _successId + , _failId + , _complete = function (err){ + _this._active = !err; + clearTimeout(_failId); + clearTimeout(_successId); +// api.event.off(video, 'loadedmetadata', _complete); + callback && callback(err, _this); + } + ; + + getMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){ + // Success + _this.stream = stream; + +// api.event.on(video, 'loadedmetadata', function (){ +// _complete(null); +// }); + + // Set camera stream + video.src = URL.createObjectURL(stream); + + // Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia. + // See crbug.com/110938. + _successId = setInterval(function (){ + if( _detectVideoSignal(video) ){ + _complete(null); + } + }, 1000); + + _failId = setTimeout(function (){ + _complete('timeout'); + }, 5000); + + // Go-go-go! + video.play(); + }, _complete/*error*/); + }, + + + /** + * Stop camera streaming + */ + stop: function (){ + try { + this._active = false; + this.video.pause(); + this.stream.stop(); + } catch( err ){ } + }, + + + /** + * Create screenshot + * @return {FileAPI.Camera.Shot} + */ + shot: function (){ + return new Shot(this.video); + } + }; + + + /** + * Get camera element from container + * + * @static + * @param {HTMLElement} el + * @return {Camera} + */ + Camera.get = function (el){ + return new Camera(el.firstChild); + }; + + + /** + * Publish camera element into container + * + * @static + * @param {HTMLElement} el + * @param {Object} options + * @param {Function} [callback] + */ + Camera.publish = function (el, options, callback){ + if( typeof options == 'function' ){ + callback = options; + options = {}; + } + + // Dimensions of "camera" + options = api.extend({}, { + width: '100%' + , height: '100%' + , start: true + }, options); + + + if( el.jquery ){ + // Extract first element, from jQuery collection + el = el[0]; + } + + + var doneFn = function (err){ + if( err ){ + callback(err); + } + else { + // Get camera + var cam = Camera.get(el); + if( options.start ){ + cam.start(callback); + } + else { + callback(null, cam); + } + } + }; + + + el.style.width = _px(options.width); + el.style.height = _px(options.height); + + + if( api.html5 && html5 ){ + // Create video element + var video = document.createElement('video'); + + // Set dimensions + video.style.width = _px(options.width); + video.style.height = _px(options.height); + + // Clean container + if( window.jQuery ){ + jQuery(el).empty(); + } else { + el.innerHTML = ''; + } + + // Add "camera" to container + el.appendChild(video); + + // end + doneFn(); + } + else { + Camera.fallback(el, options, doneFn); + } + }; + + + Camera.fallback = function (el, options, callback){ + callback('not_support_camera'); + }; + + + /** + * @class FileAPI.Camera.Shot + */ + var Shot = function (video){ + var canvas = video.nodeName ? api.Image.toCanvas(video) : video; + var shot = api.Image(canvas); + shot.type = 'image/png'; + shot.width = canvas.width; + shot.height = canvas.height; + shot.size = canvas.width * canvas.height * 4; + return shot; + }; + + + /** + * Add "px" postfix, if value is a number + * + * @private + * @param {*} val + * @return {String} + */ + function _px(val){ + return val >= 0 ? val + 'px' : val; + } + + + /** + * @private + * @param {HTMLVideoElement} video + * @return {Boolean} + */ + function _detectVideoSignal(video){ + var canvas = document.createElement('canvas'), ctx, res = false; + try { + ctx = canvas.getContext('2d'); + ctx.drawImage(video, 0, 0, 1, 1); + res = ctx.getImageData(0, 0, 1, 1).data[4] != 255; + } + catch( e ){} + return res; + } + + + // @export + Camera.Shot = Shot; + api.Camera = Camera; +})(window, FileAPI); + +/** + * FileAPI fallback to Flash + * + * @flash-developer "Vladimir Demidov" + */ + +/*global window, ActiveXObject, FileAPI */ +(function (window, jQuery, api) { + "use strict"; + + var + document = window.document + , location = window.location + , navigator = window.navigator + , _each = api.each + ; + + + api.support.flash = (function (){ + var mime = navigator.mimeTypes, has = false; + + if( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){ + has = navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin); + } + else { + try { + has = !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash')); + } + catch(er){ + api.log('Flash -- does not supported.'); + } + } + + if( has && /^file:/i.test(location) ){ + api.log('[warn] Flash does not work on `file:` protocol.'); + } + + return has; + })(); + + + api.support.flash + && (0 + || !api.html5 || !api.support.html5 + || (api.cors && !api.support.cors) + || (api.media && !api.support.media) + ) + && (function (){ + var + _attr = api.uid() + , _retry = 0 + , _files = {} + , _rhttp = /^https?:/i + + , flash = { + _fn: {}, + + + /** + * Publish flash-object + * + * @param {HTMLElement} el + * @param {String} id + * @param {Object} [opts] + */ + publish: function (el, id, opts){ + opts = opts || {}; + el.innerHTML = _makeFlashHTML({ + id: id + , src: _getUrl(api.flashUrl, 'r=' + api.version) +// , src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1') + , wmode: opts.camera ? '' : 'transparent' + , flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent') + + '&flashId='+ id + + '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.version + + (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : '')) + + '&timeout='+api.flashAbortTimeout + + (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '') + + '&debug='+(api.debug?"1":"") + }, opts); + }, + + + /** + * Initialization & preload flash object + */ + init: function (){ + var child = document.body && document.body.firstChild; + + if( child ){ + do { + if( child.nodeType == 1 ){ + api.log('FlashAPI.state: awaiting'); + + var dummy = document.createElement('div'); + + dummy.id = '_' + _attr; + + _css(dummy, { + top: 1 + , right: 1 + , width: 5 + , height: 5 + , position: 'absolute' + , zIndex: 1e6+'' // set max zIndex + }); + + child.parentNode.insertBefore(dummy, child); + flash.publish(dummy, _attr); + + return; + } + } + while( child = child.nextSibling ); + } + + if( _retry < 10 ){ + setTimeout(flash.init, ++_retry*50); + } + }, + + + ready: function (){ + api.log('FlashAPI.state: ready'); + + flash.ready = api.F; + flash.isReady = true; + flash.patch(); + flash.patchCamera && flash.patchCamera(); + api.event.on(document, 'mouseover', flash.mouseover); + api.event.on(document, 'click', function (evt){ + if( flash.mouseover(evt) ){ + evt.preventDefault + ? evt.preventDefault() + : (evt.returnValue = true) + ; + } + }); + }, + + + getEl: function (){ + return document.getElementById('_'+_attr); + }, + + + getWrapper: function (node){ + do { + if( /js-fileapi-wrapper/.test(node.className) ){ + return node; + } + } + while( (node = node.parentNode) && (node !== document.body) ); + }, + + disableMouseover: false, + + mouseover: function (evt){ + if (!flash.disableMouseover) { + var target = api.event.fix(evt).target; + + if( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){ + var + state = target.getAttribute(_attr) + , wrapper = flash.getWrapper(target) + ; + + if( api.multiFlash ){ + // check state: + // i — published + // i — initialization + // r — ready + if( state == 'i' || state == 'r' ){ + // publish fail + return false; + } + else if( state != 'p' ){ + // set "init" state + target.setAttribute(_attr, 'i'); + + var dummy = document.createElement('div'); + + if( !wrapper ){ + api.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found'); + return; + } + + _css(dummy, { + top: 0 + , left: 0 + , width: target.offsetWidth + , height: target.offsetHeight + , zIndex: 1e6+'' // set max zIndex + , position: 'absolute' + }); + + wrapper.appendChild(dummy); + flash.publish(dummy, api.uid()); + + // set "publish" state + target.setAttribute(_attr, 'p'); + } + + return true; + } + else if( wrapper ){ + // Use one flash element + var box = _getDimensions(wrapper); + _css(flash.getEl(), box); + + // Set current input + flash.curInp = target; + } + } + else if( !/object|embed/i.test(target.nodeName) ){ + _css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 }); + } + } + }, + + onEvent: function (evt){ + var type = evt.type; + + if( type == 'ready' ){ + try { + // set "ready" state + flash.getInput(evt.flashId).setAttribute(_attr, 'r'); + } catch (e){ + } + + flash.ready(); + setTimeout(function (){ flash.mouseenter(evt); }, 50); + return true; + } + else if( type === 'ping' ){ + api.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error); + } + else if( type === 'log' ){ + api.log('(flash -> js).log:', evt.target); + } + else if( type in flash ){ + setTimeout(function (){ + api.log('FlashAPI.event.'+evt.type+':', evt); + flash[type](evt); + }, 1); + } + }, + mouseDown: function(evt) { + flash.disableMouseover = true; + }, + cancel: function(evt) { + flash.disableMouseover = false; + }, + mouseenter: function (evt){ + var node = flash.getInput(evt.flashId); + + if( node ){ + // Set multiple mode + flash.cmd(evt, 'multiple', node.getAttribute('multiple') != null); + + + // Set files filter + var accept = [], exts = {}; + + _each((node.getAttribute('accept') || '').split(/,\s*/), function (mime){ + api.accept[mime] && _each(api.accept[mime].split(' '), function (ext){ + exts[ext] = 1; + }); + }); + + _each(exts, function (i, ext){ + accept.push( ext ); + }); + + flash.cmd(evt, 'accept', accept.length ? accept.join(',')+','+accept.join(',').toUpperCase() : '*'); + } + }, + + + get: function (id){ + return document[id] || window[id] || document.embeds[id]; + }, + + + getInput: function (id){ + if( api.multiFlash ){ + try { + var node = flash.getWrapper(flash.get(id)); + if( node ){ + return node.getElementsByTagName('input')[0]; + } + } catch (e){ + api.log('[err] Can not find "input" by flashId:', id, e); + } + } else { + return flash.curInp; + } + }, + + + select: function (evt){ + try { + var + inp = flash.getInput(evt.flashId) + , uid = api.uid(inp) + , files = evt.target.files + , event + ; + _each(files, function (file){ + api.checkFileObj(file); + }); + + _files[uid] = files; + + if( document.createEvent ){ + event = document.createEvent('Event'); + event.files = files; + event.initEvent('change', true, true); + inp.dispatchEvent(event); + } + else if( jQuery ){ + jQuery(inp).trigger({ type: 'change', files: files }); + } + else { + event = document.createEventObject(); + event.files = files; + inp.fireEvent('onchange', event); + } + } finally { + flash.disableMouseover = false; + } + }, + + interval: null, + cmd: function (id, name, data, last) { + if (flash.uploadInProgress && flash.readInProgress) { + setTimeout(function() { + flash.cmd(id, name, data, last); + }, 100); + } else { + this.cmdFn(id, name, data, last); + } + }, + + cmdFn: function(id, name, data, last) { + try { + api.log('(js -> flash).'+name+':', data); + return flash.get(id.flashId || id).cmd(name, data); + } catch (e){ + api.log('(js -> flash).onError:', e); + if( !last ){ + // try again + setTimeout(function (){ flash.cmd(id, name, data, true); }, 50); + } + } + }, + + patch: function (){ + api.flashEngine = true; + + // FileAPI + _inherit(api, { + readAsDataURL: function (file, callback){ + if( _isHtmlFile(file) ){ + this.parent.apply(this, arguments); + } + else { + api.log('FlashAPI.readAsBase64'); + flash.readInProgress = true; + flash.cmd(file, 'readAsBase64', { + id: file.id, + callback: _wrap(function _(err, base64){ + flash.readInProgress = false; + _unwrap(_); + + api.log('FlashAPI.readAsBase64:', err); + + callback({ + type: err ? 'error' : 'load' + , error: err + , result: 'data:'+ file.type +';base64,'+ base64 + }); + }) + }); + } + }, + + readAsText: function (file, encoding, callback){ + if( callback ){ + api.log('[warn] FlashAPI.readAsText not supported `encoding` param'); + } else { + callback = encoding; + } + + api.readAsDataURL(file, function (evt){ + if( evt.type == 'load' ){ + try { + evt.result = window.atob(evt.result.split(';base64,')[1]); + } catch( err ){ + evt.type = 'error'; + evt.error = err.toString(); + } + } + callback(evt); + }); + }, + + getFiles: function (input, filter, callback){ + if( callback ){ + api.filterFiles(api.getFiles(input), filter, callback); + return null; + } + + var files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)]; + + + if( !files ){ + // Файлов нету, вызываем родительский метод + return this.parent.apply(this, arguments); + } + + + if( filter ){ + filter = api.getFilesFilter(filter); + files = api.filter(files, function (file){ return filter.test(file.name); }); + } + + return files; + }, + + + getInfo: function (file, fn){ + if( _isHtmlFile(file) ){ + this.parent.apply(this, arguments); + } + else if( file.isShot ){ + fn(null, file.info = { + width: file.width, + height: file.height + }); + } + else { + if( !file.__info ){ + var defer = file.__info = api.defer(); + +// flash.cmd(file, 'getFileInfo', { +// id: file.id +// , callback: _wrap(function _(err, info){ +// _unwrap(_); +// defer.resolve(err, file.info = info); +// }) +// }); + defer.resolve(null, file.info = null); + + } + + file.__info.then(fn); + } + } + }); + + + // FileAPI.Image + api.support.transform = true; + api.Image && _inherit(api.Image.prototype, { + get: function (fn, scaleMode){ + this.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit + return this.parent(fn); + }, + + _load: function (file, fn){ + api.log('FlashAPI.Image._load:', file); + + if( _isHtmlFile(file) ){ + this.parent.apply(this, arguments); + } + else { + var _this = this; + api.getInfo(file, function (err){ + fn.call(_this, err, file); + }); + } + }, + + _apply: function (file, fn){ + api.log('FlashAPI.Image._apply:', file); + + if( _isHtmlFile(file) ){ + this.parent.apply(this, arguments); + } + else { + var m = this.getMatrix(file.info), doneFn = fn; + + flash.cmd(file, 'imageTransform', { + id: file.id + , matrix: m + , callback: _wrap(function _(err, base64){ + api.log('FlashAPI.Image._apply.callback:', err); + _unwrap(_); + + if( err ){ + doneFn(err); + } + else if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){ + _makeFlashImage({ + width: (m.deg % 180) ? m.dh : m.dw + , height: (m.deg % 180) ? m.dw : m.dh + , scale: m.scaleMode + }, base64, doneFn); + } + else { + if( m.filter ){ + doneFn = function (err, img){ + if( err ){ + fn(err); + } + else { + api.Image.applyFilter(img, m.filter, function (){ + fn(err, this.canvas); + }); + } + }; + } + + api.newImage('data:'+ file.type +';base64,'+ base64, doneFn); + } + }) + }); + } + }, + + toData: function (fn){ + var + file = this.file + , info = file.info + , matrix = this.getMatrix(info) + ; + api.log('FlashAPI.Image.toData'); + + if( _isHtmlFile(file) ){ + this.parent.apply(this, arguments); + } + else { + if( matrix.deg == 'auto' ){ + matrix.deg = api.Image.exifOrientation[info && info.exif && info.exif.Orientation] || 0; + } + + fn.call(this, !file.info, { + id: file.id + , flashId: file.flashId + , name: file.name + , type: file.type + , matrix: matrix + }); + } + } + }); + + + api.Image && _inherit(api.Image, { + fromDataURL: function (dataURL, size, callback){ + if( !api.support.dataURI || dataURL.length > 3e4 ){ + _makeFlashImage( + api.extend({ scale: 'exactFit' }, size) + , dataURL.replace(/^data:[^,]+,/, '') + , function (err, el){ callback(el); } + ); + } + else { + this.parent(dataURL, size, callback); + } + } + }); + + // FileAPI.Form + _inherit(api.Form.prototype, { + toData: function (fn){ + var items = this.items, i = items.length; + + for( ; i--; ){ + if( items[i].file && _isHtmlFile(items[i].blob) ){ + return this.parent.apply(this, arguments); + } + } + + api.log('FlashAPI.Form.toData'); + fn(items); + } + }); + + + // FileAPI.XHR + _inherit(api.XHR.prototype, { + _send: function (options, formData){ + if( + formData.nodeName + || formData.append && api.support.html5 + || api.isArray(formData) && (typeof formData[0] === 'string') + ){ + // HTML5, Multipart or IFrame + return this.parent.apply(this, arguments); + } + + + var + data = {} + , files = {} + , _this = this + , flashId + , fileId + ; + + _each(formData, function (item){ + if( item.file ){ + files[item.name] = item = _getFileDescr(item.blob); + fileId = item.id; + flashId = item.flashId; + } + else { + data[item.name] = item.blob; + } + }); + + if( !fileId ){ + flashId = _attr; + } + + if( !flashId ){ + api.log('[err] FlashAPI._send: flashId -- undefined'); + return this.parent.apply(this, arguments); + } + else { + api.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId); + } + + _this.xhr = { + headers: {}, + abort: function (){ flash.uploadInProgress = false; flash.cmd(flashId, 'abort', { id: fileId }); }, + getResponseHeader: function (name){ return this.headers[name]; }, + getAllResponseHeaders: function (){ return this.headers; } + }; + + var queue = api.queue(function (){ + flash.uploadInProgress = true; + flash.cmd(flashId, 'upload', { + url: _getUrl(options.url.replace(/([a-z]+)=(\?)&?/i, '')) + , data: data + , files: fileId ? files : null + , headers: options.headers || {} + , callback: _wrap(function upload(evt){ + var type = evt.type, result = evt.result; + + api.log('FlashAPI.upload.'+type); + + if( type == 'progress' ){ + evt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme + evt.lengthComputable = true; + options.progress(evt); + } + else if( type == 'complete' ){ + flash.uploadInProgress = false; + _unwrap(upload); + + if( typeof result == 'string' ){ + _this.responseText = result.replace(/%22/g, "\"").replace(/%5c/g, "\\").replace(/%26/g, "&").replace(/%25/g, "%"); + } + + _this.end(evt.status || 200); + } + else if( type == 'abort' || type == 'error' ){ + flash.uploadInProgress = false; + _this.end(evt.status || 0, evt.message); + _unwrap(upload); + } + }) + }); + }); + + + // #2174: FileReference.load() call while FileReference.upload() or vice versa + _each(files, function (file){ + queue.inc(); + api.getInfo(file, queue.next); + }); + + queue.check(); + } + }); + } + } + ; + + + function _makeFlashHTML(opts){ + return ('' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '').replace(/#(\w+)#/ig, function (a, name){ return opts[name]; }) + ; + } + + + function _css(el, css){ + if( el && el.style ){ + var key, val; + for( key in css ){ + val = css[key]; + if( typeof val == 'number' ){ + val += 'px'; + } + try { el.style[key] = val; } catch (e) {} + } + + } + } + + + function _inherit(obj, methods){ + _each(methods, function (fn, name){ + var prev = obj[name]; + obj[name] = function (){ + this.parent = prev; + return fn.apply(this, arguments); + }; + }); + } + + function _isHtmlFile(file){ + return file && !file.flashId; + } + + function _wrap(fn){ + var id = fn.wid = api.uid(); + flash._fn[id] = fn; + return 'FileAPI.Flash._fn.'+id; + } + + + function _unwrap(fn){ + try { + flash._fn[fn.wid] = null; + delete flash._fn[fn.wid]; + } + catch(e){} + } + + + function _getUrl(url, params){ + if( !_rhttp.test(url) ){ + if( /^\.\//.test(url) || '/' != url.charAt(0) ){ + var path = location.pathname; + path = path.substr(0, path.lastIndexOf('/')); + url = (path +'/'+ url).replace('/./', '/'); + } + + if( '//' != url.substr(0, 2) ){ + url = '//' + location.host + url; + } + + if( !_rhttp.test(url) ){ + url = location.protocol + url; + } + } + + if( params ){ + url += (/\?/.test(url) ? '&' : '?') + params; + } + + return url; + } + + + function _makeFlashImage(opts, base64, fn){ + var + key + , flashId = api.uid() + , el = document.createElement('div') + , attempts = 10 + ; + + for( key in opts ){ + el.setAttribute(key, opts[key]); + el[key] = opts[key]; + } + + _css(el, opts); + + opts.width = '100%'; + opts.height = '100%'; + + el.innerHTML = _makeFlashHTML(api.extend({ + id: flashId + , src: _getUrl(api.flashImageUrl, 'r='+ api.uid()) + , wmode: 'opaque' + , flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){ + _unwrap(_); + if( --attempts > 0 ){ + _setImage(); + } + return true; + }) + }, opts)); + + function _setImage(){ + try { + // Get flash-object by id + var img = flash.get(flashId); + img.setImage(base64); + } catch (e){ + api.log('[err] FlashAPI.Preview.setImage -- can not set "base64":', e); + } + } + + fn(false, el); + el = null; + } + + + function _getFileDescr(file){ + return { + id: file.id + , name: file.name + , matrix: file.matrix + , flashId: file.flashId + }; + } + + + function _getDimensions(el){ + var + box = el.getBoundingClientRect() + , body = document.body + , docEl = (el && el.ownerDocument).documentElement + ; + + function getOffset(obj) { + var left, top; + left = top = 0; + if (obj.offsetParent) { + do { + left += obj.offsetLeft; + top += obj.offsetTop; + } while (obj = obj.offsetParent); + } + return { + left : left, + top : top + }; + }; + + return { + top: getOffset(el).top + , left: getOffset(el).left + , width: el.offsetWidth + , height: el.offsetHeight + }; + } + + // @export + api.Flash = flash; + + + // Check dataURI support + api.newImage('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', function (err, img){ + api.support.dataURI = !(img.width != 1 || img.height != 1); + flash.init(); + }); + })(); +})(window, window.jQuery, FileAPI); + +/** + * FileAPI fallback to Flash + * + * @flash-developer "Vladimir Demidov" + */ + +/*global window, FileAPI */ +(function (window, jQuery, api) { + "use strict"; + + var _each = api.each, + _cameraQueue = []; + + + if (api.support.flash && (api.media && !api.support.media)) { + (function () { + + function _wrap(fn) { + var id = fn.wid = api.uid(); + api.Flash._fn[id] = fn; + return 'FileAPI.Flash._fn.' + id; + } + + + function _unwrap(fn) { + try { + api.Flash._fn[fn.wid] = null; + delete api.Flash._fn[fn.wid]; + } catch (e) { + } + } + + var flash = api.Flash; + api.extend(api.Flash, { + + patchCamera: function () { + api.Camera.fallback = function (el, options, callback) { + var camId = api.uid(); + api.log('FlashAPI.Camera.publish: ' + camId); + flash.publish(el, camId, api.extend(options, { + camera: true, + onEvent: _wrap(function _(evt) { + if (evt.type === 'camera') { + _unwrap(_); + + if (evt.error) { + api.log('FlashAPI.Camera.publish.error: ' + evt.error); + callback(evt.error); + } else { + api.log('FlashAPI.Camera.publish.success: ' + camId); + callback(null); + } + } + }) + })); + }; + // Run + _each(_cameraQueue, function (args) { + api.Camera.fallback.apply(api.Camera, args); + }); + _cameraQueue = []; + + + // FileAPI.Camera:proto + api.extend(api.Camera.prototype, { + _id: function () { + return this.video.id; + }, + + start: function (callback) { + var _this = this; + flash.cmd(this._id(), 'camera.on', { + callback: _wrap(function _(evt) { + _unwrap(_); + + if (evt.error) { + api.log('FlashAPI.camera.on.error: ' + evt.error); + callback(evt.error, _this); + } else { + api.log('FlashAPI.camera.on.success: ' + _this._id()); + _this._active = true; + callback(null, _this); + } + }) + }); + }, + + stop: function () { + this._active = false; + flash.cmd(this._id(), 'camera.off'); + }, + + shot: function () { + api.log('FlashAPI.Camera.shot:', this._id()); + + var shot = api.Flash.cmd(this._id(), 'shot', {}); + shot.type = 'image/png'; + shot.flashId = this._id(); + shot.isShot = true; + + return new api.Camera.Shot(shot); + } + }); + } + }); + + api.Camera.fallback = function () { + _cameraQueue.push(arguments); + }; + + }()); + } +}(window, window.jQuery, FileAPI)); +if( typeof define === "function" && define.amd ){ define("FileAPI", [], function (){ return FileAPI; }); } diff --git a/infoscreen/static/js/ng-file-upload-bower-12.2.11/FileAPI.min.js b/infoscreen/static/js/ng-file-upload-bower-12.2.11/FileAPI.min.js new file mode 100755 index 0000000..d82fa61 --- /dev/null +++ b/infoscreen/static/js/ng-file-upload-bower-12.2.11/FileAPI.min.js @@ -0,0 +1,6 @@ +/*! 12.2.11 */ +/*! FileAPI 2.0.7 - BSD | git://github.com/mailru/FileAPI.git + * FileAPI — a set of javascript tools for working with files. Multiupload, drag'n'drop and chunked file upload. Images: crop, resize and auto orientation by EXIF. + */ +!function(a){"use strict";var b=a.HTMLCanvasElement&&a.HTMLCanvasElement.prototype,c=a.Blob&&function(){try{return Boolean(new Blob)}catch(a){return!1}}(),d=c&&a.Uint8Array&&function(){try{return 100===new Blob([new Uint8Array(100)]).size}catch(a){return!1}}(),e=a.BlobBuilder||a.WebKitBlobBuilder||a.MozBlobBuilder||a.MSBlobBuilder,f=(c||e)&&a.atob&&a.ArrayBuffer&&a.Uint8Array&&function(a){var b,f,g,h,i,j;for(b=a.split(",")[0].indexOf("base64")>=0?atob(a.split(",")[1]):decodeURIComponent(a.split(",")[1]),f=new ArrayBuffer(b.length),g=new Uint8Array(f),h=0;hd;d++)d in a&&b.call(c,a[d],d,a);else for(var f in a)a.hasOwnProperty(f)&&b.call(c,a[f],f,a)},S=function(a){for(var b=arguments,c=1,d=function(b,c){a[c]=b};c=c&&!d&&f.end()},isFail:function(){return d},fail:function(){!d&&a(d=!0)},end:function(){e||(e=!0,a())}};return f},each:R,afor:function(a,b){var c=0,d=a.length;Q(a)&&d--?!function e(){b(d!=c&&e,a[c],c++)}():b(!1)},extend:S,isFile:function(a){return"[object File]"===H.call(a)},isBlob:function(a){return this.isFile(a)||"[object Blob]"===H.call(a)},isCanvas:function(a){return a&&D.test(a.nodeName)},getFilesFilter:function(a){return a="string"==typeof a?a:a.getAttribute&&a.getAttribute("accept")||"",a?new RegExp("("+a.replace(/\./g,"\\.").replace(/,/g,"|")+")$","i"):/./},readAsDataURL:function(a,b){Y.isCanvas(a)?c(a,b,"load",Y.toDataURL(a)):e(a,b,"DataURL")},readAsBinaryString:function(a,b){d("BinaryString")?e(a,b,"BinaryString"):e(a,function(a){if("load"==a.type)try{a.result=Y.toBinaryString(a.result)}catch(c){a.type="error",a.message=c.toString()}b(a)},"DataURL")},readAsArrayBuffer:function(a,b){e(a,b,"ArrayBuffer")},readAsText:function(a,b,c){c||(c=b,b="utf-8"),e(a,c,"Text",b)},toDataURL:function(a,b){return"string"==typeof a?a:a.toDataURL?a.toDataURL(b||"image/png"):void 0},toBinaryString:function(b){return a.atob(Y.toDataURL(b).replace(G,""))},readAsImage:function(a,d,e){if(Y.isFile(a))if(r){var f=r.createObjectURL(a);f===b?c(a,d,"error"):Y.readAsImage(f,d,e)}else Y.readAsDataURL(a,function(b){"load"==b.type?Y.readAsImage(b.result,d,e):(e||"error"==b.type)&&c(a,d,b,null,{loaded:b.loaded,total:b.total})});else if(Y.isCanvas(a))c(a,d,"load",a);else if(C.test(a.nodeName))if(a.complete)c(a,d,"load",a);else{var g="error abort load";V(a,g,function i(b){"load"==b.type&&r&&r.revokeObjectURL(a.src),U(a,g,i),c(a,d,b,a)})}else if(a.iframe)c(a,d,{type:"error"});else{var h=Y.newImage(a.dataURL||a);Y.readAsImage(h,d,e)}},checkFileObj:function(a){var b={},c=Y.accept;return"object"==typeof a?b=a:b.name=(a+"").split(/\\|\//g).pop(),null==b.type&&(b.type=b.name.split(".").pop()),R(c,function(a,c){a=new RegExp(a.replace(/\s/g,"|"),"i"),(a.test(b.type)||Y.ext2mime[b.type])&&(b.type=Y.ext2mime[b.type]||c.split("/")[0]+"/"+b.type)}),b},getDropFiles:function(a,b){var c=[],d=k(a),e=Q(d.items)&&d.items[0]&&g(d.items[0]),i=Y.queue(function(){b(c)});R((e?d.items:d.files)||[],function(a){i.inc();try{e?h(a,function(a,b){a?Y.log("[err] getDropFiles:",a):c.push.apply(c,b),i.next()}):f(a,function(b){b&&c.push(a),i.next()})}catch(b){i.next(),Y.log("[err] getDropFiles: ",b)}}),i.check()},getFiles:function(a,b,c){var d=[];return c?(Y.filterFiles(Y.getFiles(a),b,c),null):(a.jquery&&(a.each(function(){d=d.concat(Y.getFiles(this))}),a=d,d=[]),"string"==typeof b&&(b=Y.getFilesFilter(b)),a.originalEvent?a=W(a.originalEvent):a.srcElement&&(a=W(a)),a.dataTransfer?a=a.dataTransfer:a.target&&(a=a.target),a.files?(d=a.files,y||(d[0].blob=a,d[0].iframe=!0)):!y&&j(a)?Y.trim(a.value)&&(d=[Y.checkFileObj(a.value)],d[0].blob=a,d[0].iframe=!0):Q(a)&&(d=a),Y.filter(d,function(a){return!b||b.test(a.name)}))},getTotalSize:function(a){for(var b=0,c=a&&a.length;c--;)b+=a[c].size;return b},getInfo:function(a,b){var c={},d=L.concat();Y.isFile(a)?!function e(){var f=d.shift();f?f.test(a.type)?f(a,function(a,d){a?b(a):(S(c,d),e())}):e():b(!1,c)}():b("not_support_info",c)},addInfoReader:function(a,b){b.test=function(b){return a.test(b)},L.push(b)},filter:function(a,b){for(var c,d=[],e=0,f=a.length;f>e;e++)e in a&&(c=a[e],b.call(c,c,e,a)&&d.push(c));return d},filterFiles:function(a,b,c){if(a.length){var d,e=a.concat(),f=[],g=[];!function h(){e.length?(d=e.shift(),Y.getInfo(d,function(a,c){(b(d,a?!1:c)?f:g).push(d),h()})):c(f,g)}()}else c([],a)},upload:function(a){a=S({jsonp:"callback",prepare:Y.F,beforeupload:Y.F,upload:Y.F,fileupload:Y.F,fileprogress:Y.F,filecomplete:Y.F,progress:Y.F,complete:Y.F,pause:Y.F,imageOriginal:!0,chunkSize:Y.chunkSize,chunkUploadRetry:Y.chunkUploadRetry,uploadRetry:Y.uploadRetry},a),a.imageAutoOrientation&&!a.imageTransform&&(a.imageTransform={rotate:"auto"});var b,c=new Y.XHR(a),d=this._getFilesDataArray(a.files),e=this,f=0,g=0,h=!1;return R(d,function(a){f+=a.size}),c.files=[],R(d,function(a){c.files.push(a.file)}),c.total=f,c.loaded=0,c.filesLeft=d.length,a.beforeupload(c,a),b=function(){var j=d.shift(),k=j&&j.file,l=!1,m=i(a);if(c.filesLeft=d.length,k&&k.name===Y.expando&&(k=null,Y.log("[warn] FileAPI.upload() — called without files")),("abort"!=c.statusText||c.current)&&j){if(h=!1,c.currentFile=k,k&&a.prepare(k,m)===!1)return void b.call(e);m.file=k,e._getFormData(m,j,function(h){g||a.upload(c,a);var i=new Y.XHR(S({},m,{upload:k?function(){a.fileupload(k,i,m)}:n,progress:k?function(b){l||(l=b.loaded===b.total,a.fileprogress({type:"progress",total:j.total=b.total,loaded:j.loaded=b.loaded},k,i,m),a.progress({type:"progress",total:f,loaded:c.loaded=g+j.size*(b.loaded/b.total)|0},k,i,m))}:n,complete:function(d){R(N,function(a){c[a]=i[a]}),k&&(j.total=j.total||j.size,j.loaded=j.total,d||(this.progress(j),l=!0,g+=j.size,c.loaded=g),a.filecomplete(d,i,k,m)),setTimeout(function(){b.call(e)},0)}}));c.abort=function(a){a||(d.length=0),this.current=a,i.abort()},i.send(h)})}else{var o=200==c.status||201==c.status||204==c.status;a.complete(o?!1:c.statusText||"error",c,a),h=!0}},setTimeout(b,0),c.append=function(a,g){a=Y._getFilesDataArray([].concat(a)),R(a,function(a){f+=a.size,c.files.push(a.file),g?d.unshift(a):d.push(a)}),c.statusText="",h&&b.call(e)},c.remove=function(a){for(var b,c=d.length;c--;)d[c].file==a&&(b=d.splice(c,1),f-=b.size);return b},c},_getFilesDataArray:function(a){var b=[],c={};if(j(a)){var d=Y.getFiles(a);c[a.name||"file"]=null!==a.getAttribute("multiple")?d:d[0]}else Q(a)&&j(a[0])?R(a,function(a){c[a.name||"file"]=Y.getFiles(a)}):c=a;return R(c,function e(a,c){Q(a)?R(a,function(a){e(a,c)}):a&&(a.name||a.image)&&b.push({name:c,file:a,size:a.size,total:a.size,loaded:0})}),b.length||b.push({file:{name:Y.expando}}),b},_getFormData:function(a,b,c){var d=b.file,e=b.name,f=d.name,g=d.type,h=Y.support.transform&&a.imageTransform,i=new Y.Form,j=Y.queue(function(){c(i)}),k=h&&l(h),m=Y.postNameConcat;R(a.data,function n(a,b){"object"==typeof a?R(a,function(a,c){n(a,m(b,c))}):i.append(b,a)}),function o(b){b.image?(j.inc(),b.toData(function(a,b){f=f||(new Date).getTime()+".png",o(b),j.next()})):Y.Image&&h&&(/^image/.test(b.type)||E.test(b.nodeName))?(j.inc(),k&&(h=[h]),Y.Image.transform(b,h,a.imageAutoOrientation,function(c,d){if(k&&!c)B||Y.flashEngine||(i.multipart=!0),i.append(e,d[0],f,h[0].type||g);else{var l=0;c||R(d,function(a,b){B||Y.flashEngine||(i.multipart=!0),h[b].postName||(l=1),i.append(h[b].postName||m(e,b),a,f,h[b].type||g)}),(c||a.imageOriginal)&&i.append(m(e,l?"original":null),b,f,g)}j.next()})):f!==Y.expando&&i.append(e,b,f)}(d),j.check()},reset:function(a,b){var c,d;return x?(d=x(a).clone(!0).insertBefore(a).val("")[0],b||x(a).remove()):(c=a.parentNode,d=c.insertBefore(a.cloneNode(!0),a),d.value="",b||c.removeChild(a),R(K[Y.uid(a)],function(b,c){R(b,function(b){U(a,c,b),T(d,c,b)})})),d},load:function(a,b){var c=Y.getXHR();return c?(c.open("GET",a,!0),c.overrideMimeType&&c.overrideMimeType("text/plain; charset=x-user-defined"),T(c,"progress",function(a){a.lengthComputable&&b({type:a.type,loaded:a.loaded,total:a.total},c)}),c.onreadystatechange=function(){if(4==c.readyState)if(c.onreadystatechange=null,200==c.status){a=a.split("/");var d={name:a[a.length-1],size:c.getResponseHeader("Content-Length"),type:c.getResponseHeader("Content-Type")};d.dataURL="data:"+d.type+";base64,"+Y.encode64(c.responseBody||c.responseText),b({type:"load",result:d},c)}else b({type:"error"},c)},c.send(null)):b({type:"error"}),c},encode64:function(a){var b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",c="",d=0;for("string"!=typeof a&&(a=String(a));d>2,k=(3&g)<<4|h>>4;isNaN(h)?e=f=64:(e=(15&h)<<2|i>>6,f=isNaN(i)?64:63&i),c+=b.charAt(j)+b.charAt(k)+b.charAt(e)+b.charAt(f)}return c}};Y.addInfoReader(/^image/,function(a,b){if(!a.__dimensions){var c=a.__dimensions=Y.defer();Y.readAsImage(a,function(a){var b=a.target;c.resolve("load"==a.type?!1:"error",{width:b.width,height:b.height}),b.src=Y.EMPTY_PNG,b=null})}a.__dimensions.then(b)}),Y.event.dnd=function(a,b,c){var d,e;c||(c=b,b=Y.F),u?(T(a,"dragenter dragleave dragover",b.ff=b.ff||function(a){for(var c=k(a).types,f=c&&c.length,g=!1;f--;)if(~c[f].indexOf("File")){a[P](),e!==a.type&&(e=a.type,"dragleave"!=e&&b.call(a[O],!0,a),g=!0);break}g&&(clearTimeout(d),d=setTimeout(function(){b.call(a[O],"dragleave"!=e,a)},50))}),T(a,"drop",c.ff=c.ff||function(a){a[P](),e=0,b.call(a[O],!1,a),Y.getDropFiles(a,function(b){c.call(a[O],b,a)})})):Y.log("Drag'n'Drop -- not supported")},Y.event.dnd.off=function(a,b,c){U(a,"dragenter dragleave dragover",b.ff),U(a,"drop",c.ff)},x&&!x.fn.dnd&&(x.fn.dnd=function(a,b){return this.each(function(){Y.event.dnd(this,a,b)})},x.fn.offdnd=function(a,b){return this.each(function(){Y.event.dnd.off(this,a,b)})}),a.FileAPI=S(Y,a.FileAPI),Y.log("FileAPI: "+Y.version),Y.log("protocol: "+a.location.protocol),Y.log("doctype: ["+p.name+"] "+p.publicId+" "+p.systemId),R(o.getElementsByTagName("meta"),function(a){/x-ua-compatible/i.test(a.getAttribute("http-equiv"))&&Y.log("meta.http-equiv: "+a.getAttribute("content"))});try{Y._supportConsoleLog=!!console.log,Y._supportConsoleLogApply=!!console.log.apply}catch(Z){}Y.flashUrl||(Y.flashUrl=Y.staticPath+"FileAPI.flash.swf"),Y.flashImageUrl||(Y.flashImageUrl=Y.staticPath+"FileAPI.flash.image.swf"),Y.flashWebcamUrl||(Y.flashWebcamUrl=Y.staticPath+"FileAPI.flash.camera.swf")}(window,void 0),function(a,b,c){"use strict";function d(b){if(b instanceof d){var c=new d(b.file);return a.extend(c.matrix,b.matrix),c}return this instanceof d?(this.file=b,this.size=b.size||100,void(this.matrix={sx:0,sy:0,sw:0,sh:0,dx:0,dy:0,dw:0,dh:0,resize:0,deg:0,quality:1,filter:0})):new d(b)}var e=Math.min,f=Math.round,g=function(){return b.createElement("canvas")},h=!1,i={8:270,3:180,6:90,7:270,4:180,5:90};try{h=g().toDataURL("image/png").indexOf("data:image/png")>-1}catch(j){}d.prototype={image:!0,constructor:d,set:function(b){return a.extend(this.matrix,b),this},crop:function(a,b,d,e){return d===c&&(d=a,e=b,a=b=0),this.set({sx:a,sy:b,sw:d,sh:e||d})},resize:function(a,b,c){return/min|max/.test(b)&&(c=b,b=a),this.set({dw:a,dh:b||a,resize:c})},preview:function(a,b){return this.resize(a,b||a,"preview")},rotate:function(a){return this.set({deg:a})},filter:function(a){return this.set({filter:a})},overlay:function(a){return this.set({overlay:a})},clone:function(){return new d(this)},_load:function(b,c){var d=this;/img|video/i.test(b.nodeName)?c.call(d,null,b):a.readAsImage(b,function(a){c.call(d,"load"!=a.type,a.result)})},_apply:function(b,c){var f,h=g(),i=this.getMatrix(b),j=h.getContext("2d"),k=b.videoWidth||b.width,l=b.videoHeight||b.height,m=i.deg,n=i.dw,o=i.dh,p=k,q=l,r=i.filter,s=b,t=i.overlay,u=a.queue(function(){b.src=a.EMPTY_PNG,c(!1,h)}),v=a.renderImageToCanvas;for(m-=360*Math.floor(m/360),b._type=this.file.type;i.multipass&&e(p/n,q/o)>2;)p=p/2+.5|0,q=q/2+.5|0,f=g(),f.width=p,f.height=q,s!==b?(v(f,s,0,0,s.width,s.height,0,0,p,q),s=f):(s=f,v(s,b,i.sx,i.sy,i.sw,i.sh,0,0,p,q),i.sx=i.sy=i.sw=i.sh=0);h.width=m%180?o:n,h.height=m%180?n:o,h.type=i.type,h.quality=i.quality,j.rotate(m*Math.PI/180),v(j.canvas,s,i.sx,i.sy,i.sw||s.width,i.sh||s.height,180==m||270==m?-n:0,90==m||180==m?-o:0,n,o),n=h.width,o=h.height,t&&a.each([].concat(t),function(b){u.inc();var c=new window.Image,d=function(){var e=0|b.x,f=0|b.y,g=b.w||c.width,h=b.h||c.height,i=b.rel;e=1==i||4==i||7==i?(n-g+e)/2:2==i||5==i||8==i?n-(g+e):e,f=3==i||4==i||5==i?(o-h+f)/2:i>=6?o-(h+f):f,a.event.off(c,"error load abort",d);try{j.globalAlpha=b.opacity||1,j.drawImage(c,e,f,g,h)}catch(k){}u.next()};a.event.on(c,"error load abort",d),c.src=b.src,c.complete&&d()}),r&&(u.inc(),d.applyFilter(h,r,u.next)),u.check()},getMatrix:function(b){var c=a.extend({},this.matrix),d=c.sw=c.sw||b.videoWidth||b.naturalWidth||b.width,g=c.sh=c.sh||b.videoHeight||b.naturalHeight||b.height,h=c.dw=c.dw||d,i=c.dh=c.dh||g,j=d/g,k=h/i,l=c.resize;if("preview"==l){if(h!=d||i!=g){var m,n;k>=j?(m=d,n=m/k):(n=g,m=n*k),(m!=d||n!=g)&&(c.sx=~~((d-m)/2),c.sy=~~((g-n)/2),d=m,g=n)}}else l&&(d>h||g>i?"min"==l?(h=f(k>j?e(d,h):i*j),i=f(k>j?h/j:e(g,i))):(h=f(j>=k?e(d,h):i*j),i=f(j>=k?h/j:e(g,i))):(h=d,i=g));return c.sw=d,c.sh=g,c.dw=h,c.dh=i,c.multipass=a.multiPassResize,c},_trans:function(b){this._load(this.file,function(c,d){if(c)b(c);else try{this._apply(d,b)}catch(c){a.log("[err] FileAPI.Image.fn._apply:",c),b(c)}})},get:function(b){if(a.support.transform){var c=this,d=c.matrix;"auto"==d.deg?a.getInfo(c.file,function(a,e){d.deg=i[e&&e.exif&&e.exif.Orientation]||0,c._trans(b)}):c._trans(b)}else b("not_support_transform");return this},toData:function(a){return this.get(a)}},d.exifOrientation=i,d.transform=function(b,e,f,g){function h(h,i){var j={},k=a.queue(function(a){g(a,j)});h?k.fail():a.each(e,function(a,e){if(!k.isFail()){var g=new d(i.nodeType?i:b),h="function"==typeof a;if(h?a(i,g):a.width?g[a.preview?"preview":"resize"](a.width,a.height,a.strategy):a.maxWidth&&(i.width>a.maxWidth||i.height>a.maxHeight)&&g.resize(a.maxWidth,a.maxHeight,"max"),a.crop){var l=a.crop;g.crop(0|l.x,0|l.y,l.w||l.width,l.h||l.height)}a.rotate===c&&f&&(a.rotate="auto"),g.set({type:g.matrix.type||a.type||b.type||"image/png"}),h||g.set({deg:a.rotate,overlay:a.overlay,filter:a.filter,quality:a.quality||1}),k.inc(),g.toData(function(a,b){a?k.fail():(j[e]=b,k.next())})}})}b.width?h(!1,b):a.getInfo(b,h)},a.each(["TOP","CENTER","BOTTOM"],function(b,c){a.each(["LEFT","CENTER","RIGHT"],function(a,e){d[b+"_"+a]=3*c+e,d[a+"_"+b]=3*c+e})}),d.toCanvas=function(a){var c=b.createElement("canvas");return c.width=a.videoWidth||a.width,c.height=a.videoHeight||a.height,c.getContext("2d").drawImage(a,0,0),c},d.fromDataURL=function(b,c,d){var e=a.newImage(b);a.extend(e,c),d(e)},d.applyFilter=function(b,c,e){"function"==typeof c?c(b,e):window.Caman&&window.Caman("IMG"==b.tagName?d.toCanvas(b):b,function(){"string"==typeof c?this[c]():a.each(c,function(a,b){this[b](a)},this),this.render(e)})},a.renderImageToCanvas=function(b,c,d,e,f,g,h,i,j,k){try{return b.getContext("2d").drawImage(c,d,e,f,g,h,i,j,k)}catch(l){throw a.log("renderImageToCanvas failed"),l}},a.support.canvas=a.support.transform=h,a.Image=d}(FileAPI,document),function(a){"use strict";a(FileAPI)}(function(a){"use strict";if(window.navigator&&window.navigator.platform&&/iP(hone|od|ad)/.test(window.navigator.platform)){var b=a.renderImageToCanvas;a.detectSubsampling=function(a){var b,c;return a.width*a.height>1048576?(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,-a.width+1,0),0===c.getImageData(0,0,1,1).data[3]):!1},a.detectVerticalSquash=function(a,b){var c,d,e,f,g,h=a.naturalHeight||a.height,i=document.createElement("canvas"),j=i.getContext("2d");for(b&&(h/=2),i.width=1,i.height=h,j.drawImage(a,0,0),c=j.getImageData(0,0,1,h).data,d=0,e=h,f=h;f>d;)g=c[4*(f-1)+3],0===g?e=f:d=f,f=e+d>>1;return f/h||1},a.renderImageToCanvas=function(c,d,e,f,g,h,i,j,k,l){if("image/jpeg"===d._type){var m,n,o,p,q=c.getContext("2d"),r=document.createElement("canvas"),s=1024,t=r.getContext("2d");if(r.width=s,r.height=s,q.save(),m=a.detectSubsampling(d),m&&(e/=2,f/=2,g/=2,h/=2),n=a.detectVerticalSquash(d,m),m||1!==n){for(f*=n,k=Math.ceil(s*k/g),l=Math.ceil(s*l/h/n),j=0,p=0;h>p;){for(i=0,o=0;g>o;)t.clearRect(0,0,s,s),t.drawImage(d,e,f,g,h,-o,-p,g,h),q.drawImage(r,0,0,s,s,i,j,k,l),o+=s,i+=k;p+=s,j+=l}return q.restore(),c}}return b(c,d,e,f,g,h,i,j,k,l)}}}),function(a,b){"use strict";function c(b,c,d){var e=b.blob,f=b.file;if(f){if(!e.toDataURL)return void a.readAsBinaryString(e,function(a){"load"==a.type&&c(b,a.result)});var g={"image/jpeg":".jpe?g","image/png":".png"},h=g[b.type]?b.type:"image/png",i=g[h]||".png",j=e.quality||1;f.match(new RegExp(i+"$","i"))||(f+=i.replace("?","")),b.file=f,b.type=h,!d&&e.toBlob?e.toBlob(function(a){c(b,a)},h,j):c(b,a.toBinaryString(e.toDataURL(h,j)))}else c(b,e)}var d=b.document,e=b.FormData,f=function(){this.items=[]},g=b.encodeURIComponent;f.prototype={append:function(a,b,c,d){this.items.push({name:a,blob:b&&b.blob||(void 0==b?"":b),file:b&&(c||b.name),type:b&&(d||b.type)})},each:function(a){for(var b=0,c=this.items.length;c>b;b++)a.call(this,this.items[b])},toData:function(b,c){c._chunked=a.support.chunked&&c.chunkSize>0&&1==a.filter(this.items,function(a){return a.file}).length,a.support.html5?a.formData&&!this.multipart&&e?c._chunked?(a.log("FileAPI.Form.toPlainData"),this.toPlainData(b)):(a.log("FileAPI.Form.toFormData"),this.toFormData(b)):(a.log("FileAPI.Form.toMultipartData"),this.toMultipartData(b)):(a.log("FileAPI.Form.toHtmlData"),this.toHtmlData(b))},_to:function(b,c,d,e){var f=a.queue(function(){c(b)});this.each(function(a){d(a,b,f,e)}),f.check()},toHtmlData:function(b){this._to(d.createDocumentFragment(),b,function(b,c){var e,f=b.blob;b.file?(a.reset(f,!0),f.name=b.name,f.disabled=!1,c.appendChild(f)):(e=d.createElement("input"),e.name=b.name,e.type="hidden",e.value=f,c.appendChild(e))})},toPlainData:function(a){this._to({},a,function(a,b,d){a.file&&(b.type=a.file),a.blob.toBlob?(d.inc(),c(a,function(a,c){b.name=a.name,b.file=c,b.size=c.length,b.type=a.type,d.next()})):a.file?(b.name=a.blob.name,b.file=a.blob,b.size=a.blob.size,b.type=a.type):(b.params||(b.params=[]),b.params.push(g(a.name)+"="+g(a.blob))),b.start=-1,b.end=b.file&&b.file.FileAPIReadPosition||-1,b.retry=0})},toFormData:function(a){this._to(new e,a,function(a,b,d){a.blob&&a.blob.toBlob?(d.inc(),c(a,function(a,c){b.append(a.name,c,a.file),d.next()})):a.file?b.append(a.name,a.blob,a.file):b.append(a.name,a.blob),a.file&&b.append("_"+a.name,a.file)})},toMultipartData:function(b){this._to([],b,function(a,b,d,e){d.inc(),c(a,function(a,c){b.push("--_"+e+('\r\nContent-Disposition: form-data; name="'+a.name+'"'+(a.file?'; filename="'+g(a.file)+'"':"")+(a.file?"\r\nContent-Type: "+(a.type||"application/octet-stream"):"")+"\r\n\r\n"+(a.file?c:g(c))+"\r\n")),d.next()},!0)},a.expando)}},a.Form=f}(FileAPI,window),function(a,b){"use strict";var c=function(){},d=a.document,e=function(a){this.uid=b.uid(),this.xhr={abort:c,getResponseHeader:c,getAllResponseHeaders:c},this.options=a},f={"":1,XML:1,Text:1,Body:1};e.prototype={status:0,statusText:"",constructor:e,getResponseHeader:function(a){return this.xhr.getResponseHeader(a)},getAllResponseHeaders:function(){return this.xhr.getAllResponseHeaders()||{}},end:function(d,e){var f=this,g=f.options;f.end=f.abort=c,f.status=d,e&&(f.statusText=e),b.log("xhr.end:",d,e),g.complete(200==d||201==d?!1:f.statusText||"unknown",f),f.xhr&&f.xhr.node&&setTimeout(function(){var b=f.xhr.node;try{b.parentNode.removeChild(b)}catch(c){}try{delete a[f.uid]}catch(c){}a[f.uid]=f.xhr.node=null},9)},abort:function(){this.end(0,"abort"),this.xhr&&(this.xhr.aborted=!0,this.xhr.abort())},send:function(a){var b=this,c=this.options;a.toData(function(a){c.upload(c,b),b._send.call(b,c,a)},c)},_send:function(c,e){var g,h=this,i=h.uid,j=h.uid+"Load",k=c.url;if(b.log("XHR._send:",e),c.cache||(k+=(~k.indexOf("?")?"&":"?")+b.uid()),e.nodeName){var l=c.jsonp;k=k.replace(/([a-z]+)=(\?)/i,"$1="+i),c.upload(c,h);var m=function(a){if(~k.indexOf(a.origin))try{var c=b.parseJSON(a.data);c.id==i&&n(c.status,c.statusText,c.response)}catch(d){n(0,d.message)}},n=a[i]=function(c,d,e){h.readyState=4,h.responseText=e,h.end(c,d),b.event.off(a,"message",m),a[i]=g=p=a[j]=null};h.xhr.abort=function(){try{p.stop?p.stop():p.contentWindow.stop?p.contentWindow.stop():p.contentWindow.document.execCommand("Stop")}catch(a){}n(0,"abort")},b.event.on(a,"message",m),a[j]=function(){try{var a=p.contentWindow,c=a.document,d=a.result||b.parseJSON(c.body.innerHTML);n(d.status,d.statusText,d.response)}catch(e){b.log("[transport.onload]",e)}},g=d.createElement("div"),g.innerHTML='
'+(l&&c.url.indexOf("=?")<0?'':"")+"
";var o=g.getElementsByTagName("form")[0],p=g.getElementsByTagName("iframe")[0];o.appendChild(e),b.log(o.parentNode.innerHTML),d.body.appendChild(g),h.xhr.node=g,h.readyState=2,o.submit(),o=null}else{if(k=k.replace(/([a-z]+)=(\?)&?/i,""),this.xhr&&this.xhr.aborted)return void b.log("Error: already aborted");if(g=h.xhr=b.getXHR(),e.params&&(k+=(k.indexOf("?")<0?"?":"&")+e.params.join("&")),g.open("POST",k,!0),b.withCredentials&&(g.withCredentials="true"),c.headers&&c.headers["X-Requested-With"]||g.setRequestHeader("X-Requested-With","XMLHttpRequest"),b.each(c.headers,function(a,b){g.setRequestHeader(b,a)}),c._chunked){g.upload&&g.upload.addEventListener("progress",b.throttle(function(a){e.retry||c.progress({type:a.type,total:e.size,loaded:e.start+a.loaded,totalSize:e.size},h,c)},100),!1),g.onreadystatechange=function(){var a=parseInt(g.getResponseHeader("X-Last-Known-Byte"),10);if(h.status=g.status,h.statusText=g.statusText,h.readyState=g.readyState,4==g.readyState){try{for(var d in f)h["response"+d]=g["response"+d]}catch(i){}if(g.onreadystatechange=null,!g.status||g.status-201>0)if(b.log("Error: "+g.status),(!g.status&&!g.aborted||500==g.status||416==g.status)&&++e.retry<=c.chunkUploadRetry){var j=g.status?0:b.chunkNetworkDownRetryTimeout;c.pause(e.file,c),b.log("X-Last-Known-Byte: "+a),a?e.end=a:(e.end=e.start-1,416==g.status&&(e.end=e.end-c.chunkSize)),setTimeout(function(){h._send(c,e)},j)}else h.end(g.status);else e.retry=0,e.end==e.size-1?h.end(g.status):(b.log("X-Last-Known-Byte: "+a),a&&(e.end=a),e.file.FileAPIReadPosition=e.end,setTimeout(function(){h._send(c,e)},0));g=null}},e.start=e.end+1,e.end=Math.max(Math.min(e.start+c.chunkSize,e.size)-1,e.start);var q=e.file,r=(q.slice||q.mozSlice||q.webkitSlice).call(q,e.start,e.end+1);e.size&&!r.size?setTimeout(function(){h.end(-1)}):(g.setRequestHeader("Content-Range","bytes "+e.start+"-"+e.end+"/"+e.size),g.setRequestHeader("Content-Disposition","attachment; filename="+encodeURIComponent(e.name)),g.setRequestHeader("Content-Type",e.type||"application/octet-stream"),g.send(r)),q=r=null}else if(g.upload&&g.upload.addEventListener("progress",b.throttle(function(a){c.progress(a,h,c)},100),!1),g.onreadystatechange=function(){if(h.status=g.status,h.statusText=g.statusText,h.readyState=g.readyState,4==g.readyState){for(var a in f)h["response"+a]=g["response"+a];if(g.onreadystatechange=null,!g.status||g.status>201)if(b.log("Error: "+g.status),(!g.status&&!g.aborted||500==g.status)&&(c.retry||0)=0?a+"px":a}function d(a){var b,c=f.createElement("canvas"),d=!1;try{b=c.getContext("2d"),b.drawImage(a,0,0,1,1),d=255!=b.getImageData(0,0,1,1).data[4]}catch(e){}return d}var e=a.URL||a.webkitURL,f=a.document,g=a.navigator,h=g.getUserMedia||g.webkitGetUserMedia||g.mozGetUserMedia||g.msGetUserMedia,i=!!h;b.support.media=i;var j=function(a){this.video=a};j.prototype={isActive:function(){return!!this._active},start:function(a){var b,c,f=this,i=f.video,j=function(d){f._active=!d,clearTimeout(c),clearTimeout(b),a&&a(d,f)};h.call(g,{video:!0},function(a){f.stream=a,i.src=e.createObjectURL(a),b=setInterval(function(){d(i)&&j(null)},1e3),c=setTimeout(function(){j("timeout")},5e3),i.play()},j)},stop:function(){try{this._active=!1,this.video.pause(),this.stream.stop()}catch(a){}},shot:function(){return new k(this.video)}},j.get=function(a){return new j(a.firstChild)},j.publish=function(d,e,g){"function"==typeof e&&(g=e,e={}),e=b.extend({},{width:"100%",height:"100%",start:!0},e),d.jquery&&(d=d[0]);var h=function(a){if(a)g(a);else{var b=j.get(d);e.start?b.start(g):g(null,b)}};if(d.style.width=c(e.width),d.style.height=c(e.height),b.html5&&i){var k=f.createElement("video");k.style.width=c(e.width),k.style.height=c(e.height),a.jQuery?jQuery(d).empty():d.innerHTML="",d.appendChild(k),h()}else j.fallback(d,e,h)},j.fallback=function(a,b,c){c("not_support_camera")};var k=function(a){var c=a.nodeName?b.Image.toCanvas(a):a,d=b.Image(c);return d.type="image/png",d.width=c.width,d.height=c.height,d.size=c.width*c.height*4,d};j.Shot=k,b.Camera=j}(window,FileAPI),function(a,b,c){"use strict";var d=a.document,e=a.location,f=a.navigator,g=c.each;c.support.flash=function(){var b=f.mimeTypes,d=!1;if(f.plugins&&"object"==typeof f.plugins["Shockwave Flash"])d=f.plugins["Shockwave Flash"].description&&!(b&&b["application/x-shockwave-flash"]&&!b["application/x-shockwave-flash"].enabledPlugin); +else try{d=!(!a.ActiveXObject||!new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))}catch(g){c.log("Flash -- does not supported.")}return d&&/^file:/i.test(e)&&c.log("[warn] Flash does not work on `file:` protocol."),d}(),c.support.flash&&(0||!c.html5||!c.support.html5||c.cors&&!c.support.cors||c.media&&!c.support.media)&&function(){function h(a){return('').replace(/#(\w+)#/gi,function(b,c){return a[c]})}function i(a,b){if(a&&a.style){var c,d;for(c in b){d=b[c],"number"==typeof d&&(d+="px");try{a.style[c]=d}catch(e){}}}}function j(a,b){g(b,function(b,c){var d=a[c];a[c]=function(){return this.parent=d,b.apply(this,arguments)}})}function k(a){return a&&!a.flashId}function l(a){var b=a.wid=c.uid();return v._fn[b]=a,"FileAPI.Flash._fn."+b}function m(a){try{v._fn[a.wid]=null,delete v._fn[a.wid]}catch(b){}}function n(a,b){if(!u.test(a)){if(/^\.\//.test(a)||"/"!=a.charAt(0)){var c=e.pathname;c=c.substr(0,c.lastIndexOf("/")),a=(c+"/"+a).replace("/./","/")}"//"!=a.substr(0,2)&&(a="//"+e.host+a),u.test(a)||(a=e.protocol+a)}return b&&(a+=(/\?/.test(a)?"&":"?")+b),a}function o(a,b,e){function f(){try{var a=v.get(j);a.setImage(b)}catch(d){c.log('[err] FlashAPI.Preview.setImage -- can not set "base64":',d)}}var g,j=c.uid(),k=d.createElement("div"),o=10;for(g in a)k.setAttribute(g,a[g]),k[g]=a[g];i(k,a),a.width="100%",a.height="100%",k.innerHTML=h(c.extend({id:j,src:n(c.flashImageUrl,"r="+c.uid()),wmode:"opaque",flashvars:"scale="+a.scale+"&callback="+l(function p(){return m(p),--o>0&&f(),!0})},a)),e(!1,k),k=null}function p(a){return{id:a.id,name:a.name,matrix:a.matrix,flashId:a.flashId}}function q(a){function b(a){var b,c;if(b=c=0,a.offsetParent)do b+=a.offsetLeft,c+=a.offsetTop;while(a=a.offsetParent);return{left:b,top:c}}a.getBoundingClientRect(),d.body,(a&&a.ownerDocument).documentElement;return{top:b(a).top,left:b(a).left,width:a.offsetWidth,height:a.offsetHeight}}var r=c.uid(),s=0,t={},u=/^https?:/i,v={_fn:{},publish:function(a,b,d){d=d||{},a.innerHTML=h({id:b,src:n(c.flashUrl,"r="+c.version),wmode:d.camera?"":"transparent",flashvars:"callback="+(d.onEvent||"FileAPI.Flash.onEvent")+"&flashId="+b+"&storeKey="+f.userAgent.match(/\d/gi).join("")+"_"+c.version+(v.isReady||(c.pingUrl?"&ping="+c.pingUrl:""))+"&timeout="+c.flashAbortTimeout+(d.camera?"&useCamera="+n(c.flashWebcamUrl):"")+"&debug="+(c.debug?"1":"")},d)},init:function(){var a=d.body&&d.body.firstChild;if(a)do if(1==a.nodeType){c.log("FlashAPI.state: awaiting");var b=d.createElement("div");return b.id="_"+r,i(b,{top:1,right:1,width:5,height:5,position:"absolute",zIndex:1e6+""}),a.parentNode.insertBefore(b,a),void v.publish(b,r)}while(a=a.nextSibling);10>s&&setTimeout(v.init,50*++s)},ready:function(){c.log("FlashAPI.state: ready"),v.ready=c.F,v.isReady=!0,v.patch(),v.patchCamera&&v.patchCamera(),c.event.on(d,"mouseover",v.mouseover),c.event.on(d,"click",function(a){v.mouseover(a)&&(a.preventDefault?a.preventDefault():a.returnValue=!0)})},getEl:function(){return d.getElementById("_"+r)},getWrapper:function(a){do if(/js-fileapi-wrapper/.test(a.className))return a;while((a=a.parentNode)&&a!==d.body)},disableMouseover:!1,mouseover:function(a){if(!v.disableMouseover){var b=c.event.fix(a).target;if(/input/i.test(b.nodeName)&&"file"==b.type&&!b.disabled){var e=b.getAttribute(r),f=v.getWrapper(b);if(c.multiFlash){if("i"==e||"r"==e)return!1;if("p"!=e){b.setAttribute(r,"i");var g=d.createElement("div");if(!f)return void c.log("[err] FlashAPI.mouseover: js-fileapi-wrapper not found");i(g,{top:0,left:0,width:b.offsetWidth,height:b.offsetHeight,zIndex:1e6+"",position:"absolute"}),f.appendChild(g),v.publish(g,c.uid()),b.setAttribute(r,"p")}return!0}if(f){var h=q(f);i(v.getEl(),h),v.curInp=b}}else/object|embed/i.test(b.nodeName)||i(v.getEl(),{top:1,left:1,width:5,height:5})}},onEvent:function(a){var b=a.type;if("ready"==b){try{v.getInput(a.flashId).setAttribute(r,"r")}catch(d){}return v.ready(),setTimeout(function(){v.mouseenter(a)},50),!0}"ping"===b?c.log("(flash -> js).ping:",[a.status,a.savedStatus],a.error):"log"===b?c.log("(flash -> js).log:",a.target):b in v&&setTimeout(function(){c.log("FlashAPI.event."+a.type+":",a),v[b](a)},1)},mouseDown:function(){v.disableMouseover=!0},cancel:function(){v.disableMouseover=!1},mouseenter:function(a){var b=v.getInput(a.flashId);if(b){v.cmd(a,"multiple",null!=b.getAttribute("multiple"));var d=[],e={};g((b.getAttribute("accept")||"").split(/,\s*/),function(a){c.accept[a]&&g(c.accept[a].split(" "),function(a){e[a]=1})}),g(e,function(a,b){d.push(b)}),v.cmd(a,"accept",d.length?d.join(",")+","+d.join(",").toUpperCase():"*")}},get:function(b){return d[b]||a[b]||d.embeds[b]},getInput:function(a){if(!c.multiFlash)return v.curInp;try{var b=v.getWrapper(v.get(a));if(b)return b.getElementsByTagName("input")[0]}catch(d){c.log('[err] Can not find "input" by flashId:',a,d)}},select:function(a){try{var e,f=v.getInput(a.flashId),h=c.uid(f),i=a.target.files;g(i,function(a){c.checkFileObj(a)}),t[h]=i,d.createEvent?(e=d.createEvent("Event"),e.files=i,e.initEvent("change",!0,!0),f.dispatchEvent(e)):b?b(f).trigger({type:"change",files:i}):(e=d.createEventObject(),e.files=i,f.fireEvent("onchange",e))}finally{v.disableMouseover=!1}},interval:null,cmd:function(a,b,c,d){v.uploadInProgress&&v.readInProgress?setTimeout(function(){v.cmd(a,b,c,d)},100):this.cmdFn(a,b,c,d)},cmdFn:function(a,b,d,e){try{return c.log("(js -> flash)."+b+":",d),v.get(a.flashId||a).cmd(b,d)}catch(f){c.log("(js -> flash).onError:",f),e||setTimeout(function(){v.cmd(a,b,d,!0)},50)}},patch:function(){c.flashEngine=!0,j(c,{readAsDataURL:function(a,b){k(a)?this.parent.apply(this,arguments):(c.log("FlashAPI.readAsBase64"),v.readInProgress=!0,v.cmd(a,"readAsBase64",{id:a.id,callback:l(function d(e,f){v.readInProgress=!1,m(d),c.log("FlashAPI.readAsBase64:",e),b({type:e?"error":"load",error:e,result:"data:"+a.type+";base64,"+f})})}))},readAsText:function(b,d,e){e?c.log("[warn] FlashAPI.readAsText not supported `encoding` param"):e=d,c.readAsDataURL(b,function(b){if("load"==b.type)try{b.result=a.atob(b.result.split(";base64,")[1])}catch(c){b.type="error",b.error=c.toString()}e(b)})},getFiles:function(a,b,d){if(d)return c.filterFiles(c.getFiles(a),b,d),null;var e=c.isArray(a)?a:t[c.uid(a.target||a.srcElement||a)];return e?(b&&(b=c.getFilesFilter(b),e=c.filter(e,function(a){return b.test(a.name)})),e):this.parent.apply(this,arguments)},getInfo:function(a,b){if(k(a))this.parent.apply(this,arguments);else if(a.isShot)b(null,a.info={width:a.width,height:a.height});else{if(!a.__info){var d=a.__info=c.defer();d.resolve(null,a.info=null)}a.__info.then(b)}}}),c.support.transform=!0,c.Image&&j(c.Image.prototype,{get:function(a,b){return this.set({scaleMode:b||"noScale"}),this.parent(a)},_load:function(a,b){if(c.log("FlashAPI.Image._load:",a),k(a))this.parent.apply(this,arguments);else{var d=this;c.getInfo(a,function(c){b.call(d,c,a)})}},_apply:function(a,b){if(c.log("FlashAPI.Image._apply:",a),k(a))this.parent.apply(this,arguments);else{var d=this.getMatrix(a.info),e=b;v.cmd(a,"imageTransform",{id:a.id,matrix:d,callback:l(function f(g,h){c.log("FlashAPI.Image._apply.callback:",g),m(f),g?e(g):c.support.html5||c.support.dataURI&&!(h.length>3e4)?(d.filter&&(e=function(a,e){a?b(a):c.Image.applyFilter(e,d.filter,function(){b(a,this.canvas)})}),c.newImage("data:"+a.type+";base64,"+h,e)):o({width:d.deg%180?d.dh:d.dw,height:d.deg%180?d.dw:d.dh,scale:d.scaleMode},h,e)})})}},toData:function(a){var b=this.file,d=b.info,e=this.getMatrix(d);c.log("FlashAPI.Image.toData"),k(b)?this.parent.apply(this,arguments):("auto"==e.deg&&(e.deg=c.Image.exifOrientation[d&&d.exif&&d.exif.Orientation]||0),a.call(this,!b.info,{id:b.id,flashId:b.flashId,name:b.name,type:b.type,matrix:e}))}}),c.Image&&j(c.Image,{fromDataURL:function(a,b,d){!c.support.dataURI||a.length>3e4?o(c.extend({scale:"exactFit"},b),a.replace(/^data:[^,]+,/,""),function(a,b){d(b)}):this.parent(a,b,d)}}),j(c.Form.prototype,{toData:function(a){for(var b=this.items,d=b.length;d--;)if(b[d].file&&k(b[d].blob))return this.parent.apply(this,arguments);c.log("FlashAPI.Form.toData"),a(b)}}),j(c.XHR.prototype,{_send:function(a,b){if(b.nodeName||b.append&&c.support.html5||c.isArray(b)&&"string"==typeof b[0])return this.parent.apply(this,arguments);var d,e,f={},h={},i=this;if(g(b,function(a){a.file?(h[a.name]=a=p(a.blob),e=a.id,d=a.flashId):f[a.name]=a.blob}),e||(d=r),!d)return c.log("[err] FlashAPI._send: flashId -- undefined"),this.parent.apply(this,arguments);c.log("FlashAPI.XHR._send: "+d+" -> "+e),i.xhr={headers:{},abort:function(){v.uploadInProgress=!1,v.cmd(d,"abort",{id:e})},getResponseHeader:function(a){return this.headers[a]},getAllResponseHeaders:function(){return this.headers}};var j=c.queue(function(){v.uploadInProgress=!0,v.cmd(d,"upload",{url:n(a.url.replace(/([a-z]+)=(\?)&?/i,"")),data:f,files:e?h:null,headers:a.headers||{},callback:l(function b(d){var e=d.type,f=d.result;c.log("FlashAPI.upload."+e),"progress"==e?(d.loaded=Math.min(d.loaded,d.total),d.lengthComputable=!0,a.progress(d)):"complete"==e?(v.uploadInProgress=!1,m(b),"string"==typeof f&&(i.responseText=f.replace(/%22/g,'"').replace(/%5c/g,"\\").replace(/%26/g,"&").replace(/%25/g,"%")),i.end(d.status||200)):("abort"==e||"error"==e)&&(v.uploadInProgress=!1,i.end(d.status||0,d.message),m(b))})})});g(h,function(a){j.inc(),c.getInfo(a,j.next)}),j.check()}})}};c.Flash=v,c.newImage("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==",function(a,b){c.support.dataURI=!(1!=b.width||1!=b.height),v.init()})}()}(window,window.jQuery,FileAPI),function(a,b,c){"use strict";var d=c.each,e=[];c.support.flash&&c.media&&!c.support.media&&!function(){function a(a){var b=a.wid=c.uid();return c.Flash._fn[b]=a,"FileAPI.Flash._fn."+b}function b(a){try{c.Flash._fn[a.wid]=null,delete c.Flash._fn[a.wid]}catch(b){}}var f=c.Flash;c.extend(c.Flash,{patchCamera:function(){c.Camera.fallback=function(d,e,g){var h=c.uid();c.log("FlashAPI.Camera.publish: "+h),f.publish(d,h,c.extend(e,{camera:!0,onEvent:a(function i(a){"camera"===a.type&&(b(i),a.error?(c.log("FlashAPI.Camera.publish.error: "+a.error),g(a.error)):(c.log("FlashAPI.Camera.publish.success: "+h),g(null)))})}))},d(e,function(a){c.Camera.fallback.apply(c.Camera,a)}),e=[],c.extend(c.Camera.prototype,{_id:function(){return this.video.id},start:function(d){var e=this;f.cmd(this._id(),"camera.on",{callback:a(function g(a){b(g),a.error?(c.log("FlashAPI.camera.on.error: "+a.error),d(a.error,e)):(c.log("FlashAPI.camera.on.success: "+e._id()),e._active=!0,d(null,e))})})},stop:function(){this._active=!1,f.cmd(this._id(),"camera.off")},shot:function(){c.log("FlashAPI.Camera.shot:",this._id());var a=c.Flash.cmd(this._id(),"shot",{});return a.type="image/png",a.flashId=this._id(),a.isShot=!0,new c.Camera.Shot(a)}})}}),c.Camera.fallback=function(){e.push(arguments)}}()}(window,window.jQuery,FileAPI),"function"==typeof define&&define.amd&&define("FileAPI",[],function(){return FileAPI}); \ No newline at end of file diff --git a/infoscreen/static/js/ng-file-upload-bower-12.2.11/LICENSE b/infoscreen/static/js/ng-file-upload-bower-12.2.11/LICENSE new file mode 100644 index 0000000..7ebd53c --- /dev/null +++ b/infoscreen/static/js/ng-file-upload-bower-12.2.11/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 danialfarid + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/infoscreen/static/js/ng-file-upload-bower-12.2.11/README.md b/infoscreen/static/js/ng-file-upload-bower-12.2.11/README.md new file mode 100755 index 0000000..ae7bc79 --- /dev/null +++ b/infoscreen/static/js/ng-file-upload-bower-12.2.11/README.md @@ -0,0 +1,5 @@ +# angular-file-upload-bower + +bower distribution of [angular-file-upload](https://github.com/danialfarid/angular-file-upload). +All issues and pull request must be sumbitted to [angular-file-upload](https://github.com/danialfarid/angular-file-upload) + diff --git a/infoscreen/static/js/ng-file-upload-bower-12.2.11/bower.json b/infoscreen/static/js/ng-file-upload-bower-12.2.11/bower.json new file mode 100755 index 0000000..306e760 --- /dev/null +++ b/infoscreen/static/js/ng-file-upload-bower-12.2.11/bower.json @@ -0,0 +1,14 @@ +{ + "name": "ng-file-upload", + "main": "ng-file-upload.js", + "homepage": "https://github.com/danialfarid/ng-file-upload", + "dependencies": { + "angular": ">1.2.0" + }, + "authors": [ + "danialf " + ], + "description": "Lightweight Angular JS directive to upload files. Support drag&drop, paste image, progress and abort", + "ignore": [], + "license": "MIT" + } diff --git a/infoscreen/static/js/ng-file-upload-bower-12.2.11/ng-file-upload-all.js b/infoscreen/static/js/ng-file-upload-bower-12.2.11/ng-file-upload-all.js new file mode 100644 index 0000000..792d4b5 --- /dev/null +++ b/infoscreen/static/js/ng-file-upload-bower-12.2.11/ng-file-upload-all.js @@ -0,0 +1,2888 @@ +/**! + * AngularJS file upload directives and services. Supports: file upload/drop/paste, resume, cancel/abort, + * progress, resize, thumbnail, preview, validation and CORS + * FileAPI Flash shim for old browsers not supporting FormData + * @author Danial + * @version 12.2.11 + */ + +(function () { + /** @namespace FileAPI.noContentTimeout */ + + function patchXHR(fnName, newFn) { + window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]); + } + + function redefineProp(xhr, prop, fn) { + try { + Object.defineProperty(xhr, prop, {get: fn}); + } catch (e) {/*ignore*/ + } + } + + if (!window.FileAPI) { + window.FileAPI = {}; + } + + if (!window.XMLHttpRequest) { + throw 'AJAX is not supported. XMLHttpRequest is not defined.'; + } + + FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad; + if (FileAPI.shouldLoad) { + var initializeUploadListener = function (xhr) { + if (!xhr.__listeners) { + if (!xhr.upload) xhr.upload = {}; + xhr.__listeners = []; + var origAddEventListener = xhr.upload.addEventListener; + xhr.upload.addEventListener = function (t, fn) { + xhr.__listeners[t] = fn; + if (origAddEventListener) origAddEventListener.apply(this, arguments); + }; + } + }; + + patchXHR('open', function (orig) { + return function (m, url, b) { + initializeUploadListener(this); + this.__url = url; + try { + orig.apply(this, [m, url, b]); + } catch (e) { + if (e.message.indexOf('Access is denied') > -1) { + this.__origError = e; + orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]); + } + } + }; + }); + + patchXHR('getResponseHeader', function (orig) { + return function (h) { + return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h])); + }; + }); + + patchXHR('getAllResponseHeaders', function (orig) { + return function () { + return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this)); + }; + }); + + patchXHR('abort', function (orig) { + return function () { + return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this)); + }; + }); + + patchXHR('setRequestHeader', function (orig) { + return function (header, value) { + if (header === '__setXHR_') { + initializeUploadListener(this); + var val = value(this); + // fix for angular < 1.2.0 + if (val instanceof Function) { + val(this); + } + } else { + this.__requestHeaders = this.__requestHeaders || {}; + this.__requestHeaders[header] = value; + orig.apply(this, arguments); + } + }; + }); + + patchXHR('send', function (orig) { + return function () { + var xhr = this; + if (arguments[0] && arguments[0].__isFileAPIShim) { + var formData = arguments[0]; + var config = { + url: xhr.__url, + jsonp: false, //removes the callback form param + cache: true, //removes the ?fileapiXXX in the url + complete: function (err, fileApiXHR) { + if (err && angular.isString(err) && err.indexOf('#2174') !== -1) { + // this error seems to be fine the file is being uploaded properly. + err = null; + } + xhr.__completed = true; + if (!err && xhr.__listeners.load) + xhr.__listeners.load({ + type: 'load', + loaded: xhr.__loaded, + total: xhr.__total, + target: xhr, + lengthComputable: true + }); + if (!err && xhr.__listeners.loadend) + xhr.__listeners.loadend({ + type: 'loadend', + loaded: xhr.__loaded, + total: xhr.__total, + target: xhr, + lengthComputable: true + }); + if (err === 'abort' && xhr.__listeners.abort) + xhr.__listeners.abort({ + type: 'abort', + loaded: xhr.__loaded, + total: xhr.__total, + target: xhr, + lengthComputable: true + }); + if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () { + return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status; + }); + if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () { + return fileApiXHR.statusText; + }); + redefineProp(xhr, 'readyState', function () { + return 4; + }); + if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () { + return fileApiXHR.response; + }); + var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined); + redefineProp(xhr, 'responseText', function () { + return resp; + }); + redefineProp(xhr, 'response', function () { + return resp; + }); + if (err) redefineProp(xhr, 'err', function () { + return err; + }); + xhr.__fileApiXHR = fileApiXHR; + if (xhr.onreadystatechange) xhr.onreadystatechange(); + if (xhr.onload) xhr.onload(); + }, + progress: function (e) { + e.target = xhr; + if (xhr.__listeners.progress) xhr.__listeners.progress(e); + xhr.__total = e.total; + xhr.__loaded = e.loaded; + if (e.total === e.loaded) { + // fix flash issue that doesn't call complete if there is no response text from the server + var _this = this; + setTimeout(function () { + if (!xhr.__completed) { + xhr.getAllResponseHeaders = function () { + }; + _this.complete(null, {status: 204, statusText: 'No Content'}); + } + }, FileAPI.noContentTimeout || 10000); + } + }, + headers: xhr.__requestHeaders + }; + config.data = {}; + config.files = {}; + for (var i = 0; i < formData.data.length; i++) { + var item = formData.data[i]; + if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) { + config.files[item.key] = item.val; + } else { + config.data[item.key] = item.val; + } + } + + setTimeout(function () { + if (!FileAPI.hasFlash) { + throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"'; + } + xhr.__fileApiXHR = FileAPI.upload(config); + }, 1); + } else { + if (this.__origError) { + throw this.__origError; + } + orig.apply(xhr, arguments); + } + }; + }); + window.XMLHttpRequest.__isFileAPIShim = true; + window.FormData = FormData = function () { + return { + append: function (key, val, name) { + if (val.__isFileAPIBlobShim) { + val = val.data[0]; + } + this.data.push({ + key: key, + val: val, + name: name + }); + }, + data: [], + __isFileAPIShim: true + }; + }; + + window.Blob = Blob = function (b) { + return { + data: b, + __isFileAPIBlobShim: true + }; + }; + } + +})(); + +(function () { + /** @namespace FileAPI.forceLoad */ + /** @namespace window.FileAPI.jsUrl */ + /** @namespace window.FileAPI.jsPath */ + + function isInputTypeFile(elem) { + return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file'; + } + + function hasFlash() { + try { + var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash'); + if (fo) return true; + } catch (e) { + if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true; + } + return false; + } + + function getOffset(obj) { + var left = 0, top = 0; + + if (window.jQuery) { + return jQuery(obj).offset(); + } + + if (obj.offsetParent) { + do { + left += (obj.offsetLeft - obj.scrollLeft); + top += (obj.offsetTop - obj.scrollTop); + obj = obj.offsetParent; + } while (obj); + } + return { + left: left, + top: top + }; + } + + if (FileAPI.shouldLoad) { + FileAPI.hasFlash = hasFlash(); + + //load FileAPI + if (FileAPI.forceLoad) { + FileAPI.html5 = false; + } + + if (!FileAPI.upload) { + var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src; + if (window.FileAPI.jsUrl) { + jsUrl = window.FileAPI.jsUrl; + } else if (window.FileAPI.jsPath) { + basePath = window.FileAPI.jsPath; + } else { + for (i = 0; i < allScripts.length; i++) { + src = allScripts[i].src; + index = src.search(/\/ng\-file\-upload[\-a-zA-z0-9\.]*\.js/); + if (index > -1) { + basePath = src.substring(0, index + 1); + break; + } + } + } + + if (FileAPI.staticPath == null) FileAPI.staticPath = basePath; + script.setAttribute('src', jsUrl || basePath + 'FileAPI.min.js'); + document.getElementsByTagName('head')[0].appendChild(script); + } + + FileAPI.ngfFixIE = function (elem, fileElem, changeFn) { + if (!hasFlash()) { + throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"'; + } + var fixInputStyle = function () { + var label = fileElem.parent(); + if (elem.attr('disabled')) { + if (label) label.removeClass('js-fileapi-wrapper'); + } else { + if (!fileElem.attr('__ngf_flash_')) { + fileElem.unbind('change'); + fileElem.unbind('click'); + fileElem.bind('change', function (evt) { + fileApiChangeFn.apply(this, [evt]); + changeFn.apply(this, [evt]); + }); + fileElem.attr('__ngf_flash_', 'true'); + } + label.addClass('js-fileapi-wrapper'); + if (!isInputTypeFile(elem)) { + label.css('position', 'absolute') + .css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px') + .css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px') + .css('filter', 'alpha(opacity=0)').css('display', elem.css('display')) + .css('overflow', 'hidden').css('z-index', '900000') + .css('visibility', 'visible'); + fileElem.css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px') + .css('position', 'absolute').css('top', '0px').css('left', '0px'); + } + } + }; + + elem.bind('mouseenter', fixInputStyle); + + var fileApiChangeFn = function (evt) { + var files = FileAPI.getFiles(evt); + //just a double check for #233 + for (var i = 0; i < files.length; i++) { + if (files[i].size === undefined) files[i].size = 0; + if (files[i].name === undefined) files[i].name = 'file'; + if (files[i].type === undefined) files[i].type = 'undefined'; + } + if (!evt.target) { + evt.target = {}; + } + evt.target.files = files; + // if evt.target.files is not writable use helper field + if (evt.target.files !== files) { + evt.__files_ = files; + } + (evt.__files_ || evt.target.files).item = function (i) { + return (evt.__files_ || evt.target.files)[i] || null; + }; + }; + }; + + FileAPI.disableFileInput = function (elem, disable) { + if (disable) { + elem.removeClass('js-fileapi-wrapper'); + } else { + elem.addClass('js-fileapi-wrapper'); + } + }; + } +})(); + +if (!window.FileReader) { + window.FileReader = function () { + var _this = this, loadStarted = false; + this.listeners = {}; + this.addEventListener = function (type, fn) { + _this.listeners[type] = _this.listeners[type] || []; + _this.listeners[type].push(fn); + }; + this.removeEventListener = function (type, fn) { + if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1); + }; + this.dispatchEvent = function (evt) { + var list = _this.listeners[evt.type]; + if (list) { + for (var i = 0; i < list.length; i++) { + list[i].call(_this, evt); + } + } + }; + this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null; + + var constructEvent = function (type, evt) { + var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error}; + if (evt.result != null) e.target.result = evt.result; + return e; + }; + var listener = function (evt) { + if (!loadStarted) { + loadStarted = true; + if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt)); + } + var e; + if (evt.type === 'load') { + if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt)); + e = constructEvent('load', evt); + if (_this.onload) _this.onload(e); + _this.dispatchEvent(e); + } else if (evt.type === 'progress') { + e = constructEvent('progress', evt); + if (_this.onprogress) _this.onprogress(e); + _this.dispatchEvent(e); + } else { + e = constructEvent('error', evt); + if (_this.onerror) _this.onerror(e); + _this.dispatchEvent(e); + } + }; + this.readAsDataURL = function (file) { + FileAPI.readAsDataURL(file, listener); + }; + this.readAsText = function (file) { + FileAPI.readAsText(file, listener); + }; + }; +} + +/**! + * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort, + * progress, resize, thumbnail, preview, validation and CORS + * @author Danial + * @version 12.2.11 + */ + +if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) { + window.XMLHttpRequest.prototype.setRequestHeader = (function (orig) { + return function (header, value) { + if (header === '__setXHR_') { + var val = value(this); + // fix for angular < 1.2.0 + if (val instanceof Function) { + val(this); + } + } else { + orig.apply(this, arguments); + } + }; + })(window.XMLHttpRequest.prototype.setRequestHeader); +} + +var ngFileUpload = angular.module('ngFileUpload', []); + +ngFileUpload.version = '12.2.11'; + +ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) { + var upload = this; + upload.promisesCount = 0; + + this.isResumeSupported = function () { + return window.Blob && window.Blob.prototype.slice; + }; + + var resumeSupported = this.isResumeSupported(); + + function sendHttp(config) { + config.method = config.method || 'POST'; + config.headers = config.headers || {}; + + var deferred = config._deferred = config._deferred || $q.defer(); + var promise = deferred.promise; + + function notifyProgress(e) { + if (deferred.notify) { + deferred.notify(e); + } + if (promise.progressFunc) { + $timeout(function () { + promise.progressFunc(e); + }); + } + } + + function getNotifyEvent(n) { + if (config._start != null && resumeSupported) { + return { + loaded: n.loaded + config._start, + total: (config._file && config._file.size) || n.total, + type: n.type, config: config, + lengthComputable: true, target: n.target + }; + } else { + return n; + } + } + + if (!config.disableProgress) { + config.headers.__setXHR_ = function () { + return function (xhr) { + if (!xhr || !xhr.upload || !xhr.upload.addEventListener) return; + config.__XHR = xhr; + if (config.xhrFn) config.xhrFn(xhr); + xhr.upload.addEventListener('progress', function (e) { + e.config = config; + notifyProgress(getNotifyEvent(e)); + }, false); + //fix for firefox not firing upload progress end, also IE8-9 + xhr.upload.addEventListener('load', function (e) { + if (e.lengthComputable) { + e.config = config; + notifyProgress(getNotifyEvent(e)); + } + }, false); + }; + }; + } + + function uploadWithAngular() { + $http(config).then(function (r) { + if (resumeSupported && config._chunkSize && !config._finished && config._file) { + var fileSize = config._file && config._file.size || 0; + notifyProgress({ + loaded: Math.min(config._end, fileSize), + total: fileSize, + config: config, + type: 'progress' + } + ); + upload.upload(config, true); + } else { + if (config._finished) delete config._finished; + deferred.resolve(r); + } + }, function (e) { + deferred.reject(e); + }, function (n) { + deferred.notify(n); + } + ); + } + + if (!resumeSupported) { + uploadWithAngular(); + } else if (config._chunkSize && config._end && !config._finished) { + config._start = config._end; + config._end += config._chunkSize; + uploadWithAngular(); + } else if (config.resumeSizeUrl) { + $http.get(config.resumeSizeUrl).then(function (resp) { + if (config.resumeSizeResponseReader) { + config._start = config.resumeSizeResponseReader(resp.data); + } else { + config._start = parseInt((resp.data.size == null ? resp.data : resp.data.size).toString()); + } + if (config._chunkSize) { + config._end = config._start + config._chunkSize; + } + uploadWithAngular(); + }, function (e) { + throw e; + }); + } else if (config.resumeSize) { + config.resumeSize().then(function (size) { + config._start = size; + if (config._chunkSize) { + config._end = config._start + config._chunkSize; + } + uploadWithAngular(); + }, function (e) { + throw e; + }); + } else { + if (config._chunkSize) { + config._start = 0; + config._end = config._start + config._chunkSize; + } + uploadWithAngular(); + } + + + promise.success = function (fn) { + promise.then(function (response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + promise.error = function (fn) { + promise.then(null, function (response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + promise.progress = function (fn) { + promise.progressFunc = fn; + promise.then(null, null, function (n) { + fn(n); + }); + return promise; + }; + promise.abort = promise.pause = function () { + if (config.__XHR) { + $timeout(function () { + config.__XHR.abort(); + }); + } + return promise; + }; + promise.xhr = function (fn) { + config.xhrFn = (function (origXhrFn) { + return function () { + if (origXhrFn) origXhrFn.apply(promise, arguments); + fn.apply(promise, arguments); + }; + })(config.xhrFn); + return promise; + }; + + upload.promisesCount++; + if (promise['finally'] && promise['finally'] instanceof Function) { + promise['finally'](function () { + upload.promisesCount--; + }); + } + return promise; + } + + this.isUploadInProgress = function () { + return upload.promisesCount > 0; + }; + + this.rename = function (file, name) { + file.ngfName = name; + return file; + }; + + this.jsonBlob = function (val) { + if (val != null && !angular.isString(val)) { + val = JSON.stringify(val); + } + var blob = new window.Blob([val], {type: 'application/json'}); + blob._ngfBlob = true; + return blob; + }; + + this.json = function (val) { + return angular.toJson(val); + }; + + function copy(obj) { + var clone = {}; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + clone[key] = obj[key]; + } + } + return clone; + } + + this.isFile = function (file) { + return file != null && (file instanceof window.Blob || (file.flashId && file.name && file.size)); + }; + + this.upload = function (config, internal) { + function toResumeFile(file, formData) { + if (file._ngfBlob) return file; + config._file = config._file || file; + if (config._start != null && resumeSupported) { + if (config._end && config._end >= file.size) { + config._finished = true; + config._end = file.size; + } + var slice = file.slice(config._start, config._end || file.size); + slice.name = file.name; + slice.ngfName = file.ngfName; + if (config._chunkSize) { + formData.append('_chunkSize', config._chunkSize); + formData.append('_currentChunkSize', config._end - config._start); + formData.append('_chunkNumber', Math.floor(config._start / config._chunkSize)); + formData.append('_totalSize', config._file.size); + } + return slice; + } + return file; + } + + function addFieldToFormData(formData, val, key) { + if (val !== undefined) { + if (angular.isDate(val)) { + val = val.toISOString(); + } + if (angular.isString(val)) { + formData.append(key, val); + } else if (upload.isFile(val)) { + var file = toResumeFile(val, formData); + var split = key.split(','); + if (split[1]) { + file.ngfName = split[1].replace(/^\s+|\s+$/g, ''); + key = split[0]; + } + config._fileKey = config._fileKey || key; + formData.append(key, file, file.ngfName || file.name); + } else { + if (angular.isObject(val)) { + if (val.$$ngfCircularDetection) throw 'ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: ' + key; + + val.$$ngfCircularDetection = true; + try { + for (var k in val) { + if (val.hasOwnProperty(k) && k !== '$$ngfCircularDetection') { + var objectKey = config.objectKey == null ? '[i]' : config.objectKey; + if (val.length && parseInt(k) > -1) { + objectKey = config.arrayKey == null ? objectKey : config.arrayKey; + } + addFieldToFormData(formData, val[k], key + objectKey.replace(/[ik]/g, k)); + } + } + } finally { + delete val.$$ngfCircularDetection; + } + } else { + formData.append(key, val); + } + } + } + } + + function digestConfig() { + config._chunkSize = upload.translateScalars(config.resumeChunkSize); + config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null; + + config.headers = config.headers || {}; + config.headers['Content-Type'] = undefined; + config.transformRequest = config.transformRequest ? + (angular.isArray(config.transformRequest) ? + config.transformRequest : [config.transformRequest]) : []; + config.transformRequest.push(function (data) { + var formData = new window.FormData(), key; + data = data || config.fields || {}; + if (config.file) { + data.file = config.file; + } + for (key in data) { + if (data.hasOwnProperty(key)) { + var val = data[key]; + if (config.formDataAppender) { + config.formDataAppender(formData, key, val); + } else { + addFieldToFormData(formData, val, key); + } + } + } + + return formData; + }); + } + + if (!internal) config = copy(config); + if (!config._isDigested) { + config._isDigested = true; + digestConfig(); + } + + return sendHttp(config); + }; + + this.http = function (config) { + config = copy(config); + config.transformRequest = config.transformRequest || function (data) { + if ((window.ArrayBuffer && data instanceof window.ArrayBuffer) || data instanceof window.Blob) { + return data; + } + return $http.defaults.transformRequest[0].apply(this, arguments); + }; + config._chunkSize = upload.translateScalars(config.resumeChunkSize); + config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null; + + return sendHttp(config); + }; + + this.translateScalars = function (str) { + if (angular.isString(str)) { + if (str.search(/kb/i) === str.length - 2) { + return parseFloat(str.substring(0, str.length - 2) * 1024); + } else if (str.search(/mb/i) === str.length - 2) { + return parseFloat(str.substring(0, str.length - 2) * 1048576); + } else if (str.search(/gb/i) === str.length - 2) { + return parseFloat(str.substring(0, str.length - 2) * 1073741824); + } else if (str.search(/b/i) === str.length - 1) { + return parseFloat(str.substring(0, str.length - 1)); + } else if (str.search(/s/i) === str.length - 1) { + return parseFloat(str.substring(0, str.length - 1)); + } else if (str.search(/m/i) === str.length - 1) { + return parseFloat(str.substring(0, str.length - 1) * 60); + } else if (str.search(/h/i) === str.length - 1) { + return parseFloat(str.substring(0, str.length - 1) * 3600); + } + } + return str; + }; + + this.urlToBlob = function(url) { + var defer = $q.defer(); + $http({url: url, method: 'get', responseType: 'arraybuffer'}).then(function (resp) { + var arrayBufferView = new Uint8Array(resp.data); + var type = resp.headers('content-type') || 'image/WebP'; + var blob = new window.Blob([arrayBufferView], {type: type}); + var matches = url.match(/.*\/(.+?)(\?.*)?$/); + if (matches.length > 1) { + blob.name = matches[1]; + } + defer.resolve(blob); + }, function (e) { + defer.reject(e); + }); + return defer.promise; + }; + + this.setDefaults = function (defaults) { + this.defaults = defaults || {}; + }; + + this.defaults = {}; + this.version = ngFileUpload.version; +} + +]); + +ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadExif', function ($parse, $timeout, $compile, $q, UploadExif) { + var upload = UploadExif; + upload.getAttrWithDefaults = function (attr, name) { + if (attr[name] != null) return attr[name]; + var def = upload.defaults[name]; + return (def == null ? def : (angular.isString(def) ? def : JSON.stringify(def))); + }; + + upload.attrGetter = function (name, attr, scope, params) { + var attrVal = this.getAttrWithDefaults(attr, name); + if (scope) { + try { + if (params) { + return $parse(attrVal)(scope, params); + } else { + return $parse(attrVal)(scope); + } + } catch (e) { + // hangle string value without single qoute + if (name.search(/min|max|pattern/i)) { + return attrVal; + } else { + throw e; + } + } + } else { + return attrVal; + } + }; + + upload.shouldUpdateOn = function (type, attr, scope) { + var modelOptions = upload.attrGetter('ngfModelOptions', attr, scope); + if (modelOptions && modelOptions.updateOn) { + return modelOptions.updateOn.split(' ').indexOf(type) > -1; + } + return true; + }; + + upload.emptyPromise = function () { + var d = $q.defer(); + var args = arguments; + $timeout(function () { + d.resolve.apply(d, args); + }); + return d.promise; + }; + + upload.rejectPromise = function () { + var d = $q.defer(); + var args = arguments; + $timeout(function () { + d.reject.apply(d, args); + }); + return d.promise; + }; + + upload.happyPromise = function (promise, data) { + var d = $q.defer(); + promise.then(function (result) { + d.resolve(result); + }, function (error) { + $timeout(function () { + throw error; + }); + d.resolve(data); + }); + return d.promise; + }; + + function applyExifRotations(files, attr, scope) { + var promises = [upload.emptyPromise()]; + angular.forEach(files, function (f, i) { + if (f.type.indexOf('image/jpeg') === 0 && upload.attrGetter('ngfFixOrientation', attr, scope, {$file: f})) { + promises.push(upload.happyPromise(upload.applyExifRotation(f), f).then(function (fixedFile) { + files.splice(i, 1, fixedFile); + })); + } + }); + return $q.all(promises); + } + + function resize(files, attr, scope) { + var resizeVal = upload.attrGetter('ngfResize', attr, scope); + if (!resizeVal || !upload.isResizeSupported() || !files.length) return upload.emptyPromise(); + if (resizeVal instanceof Function) { + var defer = $q.defer(); + return resizeVal(files).then(function (p) { + resizeWithParams(p, files, attr, scope).then(function (r) { + defer.resolve(r); + }, function (e) { + defer.reject(e); + }); + }, function (e) { + defer.reject(e); + }); + } else { + return resizeWithParams(resizeVal, files, attr, scope); + } + } + + function resizeWithParams(params, files, attr, scope) { + var promises = [upload.emptyPromise()]; + + function handleFile(f, i) { + if (f.type.indexOf('image') === 0) { + if (params.pattern && !upload.validatePattern(f, params.pattern)) return; + params.resizeIf = function (width, height) { + return upload.attrGetter('ngfResizeIf', attr, scope, + {$width: width, $height: height, $file: f}); + }; + var promise = upload.resize(f, params); + promises.push(promise); + promise.then(function (resizedFile) { + files.splice(i, 1, resizedFile); + }, function (e) { + f.$error = 'resize'; + f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name); + }); + } + } + + for (var i = 0; i < files.length; i++) { + handleFile(files[i], i); + } + return $q.all(promises); + } + + upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) { + function update(files, invalidFiles, newFiles, dupFiles, isSingleModel) { + attr.$$ngfPrevValidFiles = files; + attr.$$ngfPrevInvalidFiles = invalidFiles; + var file = files && files.length ? files[0] : null; + var invalidFile = invalidFiles && invalidFiles.length ? invalidFiles[0] : null; + + if (ngModel) { + upload.applyModelValidation(ngModel, files); + ngModel.$setViewValue(isSingleModel ? file : files); + } + + if (fileChange) { + $parse(fileChange)(scope, { + $files: files, + $file: file, + $newFiles: newFiles, + $duplicateFiles: dupFiles, + $invalidFiles: invalidFiles, + $invalidFile: invalidFile, + $event: evt + }); + } + + var invalidModel = upload.attrGetter('ngfModelInvalid', attr); + if (invalidModel) { + $timeout(function () { + $parse(invalidModel).assign(scope, isSingleModel ? invalidFile : invalidFiles); + }); + } + $timeout(function () { + // scope apply changes + }); + } + + var allNewFiles, dupFiles = [], prevValidFiles, prevInvalidFiles, + invalids = [], valids = []; + + function removeDuplicates() { + function equals(f1, f2) { + return f1.name === f2.name && (f1.$ngfOrigSize || f1.size) === (f2.$ngfOrigSize || f2.size) && + f1.type === f2.type; + } + + function isInPrevFiles(f) { + var j; + for (j = 0; j < prevValidFiles.length; j++) { + if (equals(f, prevValidFiles[j])) { + return true; + } + } + for (j = 0; j < prevInvalidFiles.length; j++) { + if (equals(f, prevInvalidFiles[j])) { + return true; + } + } + return false; + } + + if (files) { + allNewFiles = []; + dupFiles = []; + for (var i = 0; i < files.length; i++) { + if (isInPrevFiles(files[i])) { + dupFiles.push(files[i]); + } else { + allNewFiles.push(files[i]); + } + } + } + } + + function toArray(v) { + return angular.isArray(v) ? v : [v]; + } + + function resizeAndUpdate() { + function updateModel() { + $timeout(function () { + update(keep ? prevValidFiles.concat(valids) : valids, + keep ? prevInvalidFiles.concat(invalids) : invalids, + files, dupFiles, isSingleModel); + }, options && options.debounce ? options.debounce.change || options.debounce : 0); + } + + resize(validateAfterResize ? allNewFiles : valids, attr, scope).then(function () { + if (validateAfterResize) { + upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope) + .then(function (validationResult) { + valids = validationResult.validsFiles; + invalids = validationResult.invalidsFiles; + updateModel(); + }); + } else { + updateModel(); + } + }, function (e) { + throw 'Could not resize files ' + e; + }); + } + + prevValidFiles = attr.$$ngfPrevValidFiles || []; + prevInvalidFiles = attr.$$ngfPrevInvalidFiles || []; + if (ngModel && ngModel.$modelValue) { + prevValidFiles = toArray(ngModel.$modelValue); + } + + var keep = upload.attrGetter('ngfKeep', attr, scope); + allNewFiles = (files || []).slice(0); + if (keep === 'distinct' || upload.attrGetter('ngfKeepDistinct', attr, scope) === true) { + removeDuplicates(attr, scope); + } + + var isSingleModel = !keep && !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr); + + if (keep && !allNewFiles.length) return; + + upload.attrGetter('ngfBeforeModelChange', attr, scope, { + $files: files, + $file: files && files.length ? files[0] : null, + $newFiles: allNewFiles, + $duplicateFiles: dupFiles, + $event: evt + }); + + var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope); + + var options = upload.attrGetter('ngfModelOptions', attr, scope); + upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope) + .then(function (validationResult) { + if (noDelay) { + update(allNewFiles, [], files, dupFiles, isSingleModel); + } else { + if ((!options || !options.allowInvalid) && !validateAfterResize) { + valids = validationResult.validFiles; + invalids = validationResult.invalidFiles; + } else { + valids = allNewFiles; + } + if (upload.attrGetter('ngfFixOrientation', attr, scope) && upload.isExifSupported()) { + applyExifRotations(valids, attr, scope).then(function () { + resizeAndUpdate(); + }); + } else { + resizeAndUpdate(); + } + } + }); + }; + + return upload; +}]); + +ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) { + var generatedElems = []; + + function isDelayedClickSupported(ua) { + // fix for android native browser < 4.4 and safari windows + var m = ua.match(/Android[^\d]*(\d+)\.(\d+)/); + if (m && m.length > 2) { + var v = Upload.defaults.androidFixMinorVersion || 4; + return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v); + } + + // safari on windows + return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua); + } + + function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) { + /** @namespace attr.ngfSelect */ + /** @namespace attr.ngfChange */ + /** @namespace attr.ngModel */ + /** @namespace attr.ngfModelOptions */ + /** @namespace attr.ngfMultiple */ + /** @namespace attr.ngfCapture */ + /** @namespace attr.ngfValidate */ + /** @namespace attr.ngfKeep */ + var attrGetter = function (name, scope) { + return upload.attrGetter(name, attr, scope); + }; + + function isInputTypeFile() { + return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file'; + } + + function fileChangeAttr() { + return attrGetter('ngfChange') || attrGetter('ngfSelect'); + } + + function changeFn(evt) { + if (upload.shouldUpdateOn('change', attr, scope)) { + var fileList = evt.__files_ || (evt.target && evt.target.files), files = []; + /* Handle duplicate call in IE11 */ + if (!fileList) return; + for (var i = 0; i < fileList.length; i++) { + files.push(fileList[i]); + } + upload.updateModel(ngModel, attr, scope, fileChangeAttr(), + files.length ? files : null, evt); + } + } + + upload.registerModelChangeValidator(ngModel, attr, scope); + + var unwatches = []; + if (attrGetter('ngfMultiple')) { + unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () { + fileElem.attr('multiple', attrGetter('ngfMultiple', scope)); + })); + } + if (attrGetter('ngfCapture')) { + unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () { + fileElem.attr('capture', attrGetter('ngfCapture', scope)); + })); + } + if (attrGetter('ngfAccept')) { + unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () { + fileElem.attr('accept', attrGetter('ngfAccept', scope)); + })); + } + attr.$observe('accept', function () { + fileElem.attr('accept', attrGetter('accept')); + }); + unwatches.push(function () { + if (attr.$$observers) delete attr.$$observers.accept; + }); + function bindAttrToFileInput(fileElem) { + for (var i = 0; i < elem[0].attributes.length; i++) { + var attribute = elem[0].attributes[i]; + if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') { + if (attribute.value == null || attribute.value === '') { + if (attribute.name === 'required') attribute.value = 'required'; + if (attribute.name === 'multiple') attribute.value = 'multiple'; + } + fileElem.attr(attribute.name, attribute.name === 'id' ? 'ngf-' + attribute.value : attribute.value); + } + } + } + + function createFileInput() { + if (isInputTypeFile()) { + return elem; + } + + var fileElem = angular.element(''); + + bindAttrToFileInput(fileElem); + + var label = angular.element(''); + label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden') + .css('width', '0px').css('height', '0px').css('border', 'none') + .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1'); + if (elem.attr('id')) { + label.attr('id', 'ngf-label-' + elem.attr('id')); + } + generatedElems.push({el: elem, ref: label}); + + document.body.appendChild(label.append(fileElem)[0]); + + return fileElem; + } + + function clickHandler(evt) { + if (elem.attr('disabled')) return false; + if (attrGetter('ngfSelectDisabled', scope)) return; + + var r = detectSwipe(evt); + // prevent the click if it is a swipe + if (r != null) return r; + + resetModel(evt); + + // fix for md when the element is removed from the DOM and added back #460 + try { + if (!isInputTypeFile() && !document.body.contains(fileElem[0])) { + generatedElems.push({el: elem, ref: fileElem.parent()}); + document.body.appendChild(fileElem.parent()[0]); + fileElem.bind('change', changeFn); + } + } catch(e){/*ignore*/} + + if (isDelayedClickSupported(navigator.userAgent)) { + setTimeout(function () { + fileElem[0].click(); + }, 0); + } else { + fileElem[0].click(); + } + + return false; + } + + + var initialTouchStartY = 0; + var initialTouchStartX = 0; + + function detectSwipe(evt) { + var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches); + if (touches) { + if (evt.type === 'touchstart') { + initialTouchStartX = touches[0].clientX; + initialTouchStartY = touches[0].clientY; + return true; // don't block event default + } else { + // prevent scroll from triggering event + if (evt.type === 'touchend') { + var currentX = touches[0].clientX; + var currentY = touches[0].clientY; + if ((Math.abs(currentX - initialTouchStartX) > 20) || + (Math.abs(currentY - initialTouchStartY) > 20)) { + evt.stopPropagation(); + evt.preventDefault(); + return false; + } + } + return true; + } + } + } + + var fileElem = elem; + + function resetModel(evt) { + if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) { + fileElem.val(null); + upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true); + } + } + + if (!isInputTypeFile()) { + fileElem = createFileInput(); + } + fileElem.bind('change', changeFn); + + if (!isInputTypeFile()) { + elem.bind('click touchstart touchend', clickHandler); + } else { + elem.bind('click', resetModel); + } + + function ie10SameFileSelectFix(evt) { + if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) { + if (!fileElem[0].parentNode) { + fileElem = null; + return; + } + evt.preventDefault(); + evt.stopPropagation(); + fileElem.unbind('click'); + var clone = fileElem.clone(); + fileElem.replaceWith(clone); + fileElem = clone; + fileElem.attr('__ngf_ie10_Fix_', 'true'); + fileElem.bind('change', changeFn); + fileElem.bind('click', ie10SameFileSelectFix); + fileElem[0].click(); + return false; + } else { + fileElem.removeAttr('__ngf_ie10_Fix_'); + } + } + + if (navigator.appVersion.indexOf('MSIE 10') !== -1) { + fileElem.bind('click', ie10SameFileSelectFix); + } + + if (ngModel) ngModel.$formatters.push(function (val) { + if (val == null || val.length === 0) { + if (fileElem.val()) { + fileElem.val(null); + } + } + return val; + }); + + scope.$on('$destroy', function () { + if (!isInputTypeFile()) fileElem.parent().remove(); + angular.forEach(unwatches, function (unwatch) { + unwatch(); + }); + }); + + $timeout(function () { + for (var i = 0; i < generatedElems.length; i++) { + var g = generatedElems[i]; + if (!document.body.contains(g.el[0])) { + generatedElems.splice(i, 1); + g.ref.remove(); + } + } + }); + + if (window.FileAPI && window.FileAPI.ngfFixIE) { + window.FileAPI.ngfFixIE(elem, fileElem, changeFn); + } + } + + return { + restrict: 'AEC', + require: '?ngModel', + link: function (scope, elem, attr, ngModel) { + linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload); + } + }; +}]); + +(function () { + + ngFileUpload.service('UploadDataUrl', ['UploadBase', '$timeout', '$q', function (UploadBase, $timeout, $q) { + var upload = UploadBase; + upload.base64DataUrl = function (file) { + if (angular.isArray(file)) { + var d = $q.defer(), count = 0; + angular.forEach(file, function (f) { + upload.dataUrl(f, true)['finally'](function () { + count++; + if (count === file.length) { + var urls = []; + angular.forEach(file, function (ff) { + urls.push(ff.$ngfDataUrl); + }); + d.resolve(urls, file); + } + }); + }); + return d.promise; + } else { + return upload.dataUrl(file, true); + } + }; + upload.dataUrl = function (file, disallowObjectUrl) { + if (!file) return upload.emptyPromise(file, file); + if ((disallowObjectUrl && file.$ngfDataUrl != null) || (!disallowObjectUrl && file.$ngfBlobUrl != null)) { + return upload.emptyPromise(disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl, file); + } + var p = disallowObjectUrl ? file.$$ngfDataUrlPromise : file.$$ngfBlobUrlPromise; + if (p) return p; + + var deferred = $q.defer(); + $timeout(function () { + if (window.FileReader && file && + (!window.FileAPI || navigator.userAgent.indexOf('MSIE 8') === -1 || file.size < 20000) && + (!window.FileAPI || navigator.userAgent.indexOf('MSIE 9') === -1 || file.size < 4000000)) { + //prefer URL.createObjectURL for handling refrences to files of all sizes + //since it doesn´t build a large string in memory + var URL = window.URL || window.webkitURL; + if (URL && URL.createObjectURL && !disallowObjectUrl) { + var url; + try { + url = URL.createObjectURL(file); + } catch (e) { + $timeout(function () { + file.$ngfBlobUrl = ''; + deferred.reject(); + }); + return; + } + $timeout(function () { + file.$ngfBlobUrl = url; + if (url) { + deferred.resolve(url, file); + upload.blobUrls = upload.blobUrls || []; + upload.blobUrlsTotalSize = upload.blobUrlsTotalSize || 0; + upload.blobUrls.push({url: url, size: file.size}); + upload.blobUrlsTotalSize += file.size || 0; + var maxMemory = upload.defaults.blobUrlsMaxMemory || 268435456; + var maxLength = upload.defaults.blobUrlsMaxQueueSize || 200; + while ((upload.blobUrlsTotalSize > maxMemory || upload.blobUrls.length > maxLength) && upload.blobUrls.length > 1) { + var obj = upload.blobUrls.splice(0, 1)[0]; + URL.revokeObjectURL(obj.url); + upload.blobUrlsTotalSize -= obj.size; + } + } + }); + } else { + var fileReader = new FileReader(); + fileReader.onload = function (e) { + $timeout(function () { + file.$ngfDataUrl = e.target.result; + deferred.resolve(e.target.result, file); + $timeout(function () { + delete file.$ngfDataUrl; + }, 1000); + }); + }; + fileReader.onerror = function () { + $timeout(function () { + file.$ngfDataUrl = ''; + deferred.reject(); + }); + }; + fileReader.readAsDataURL(file); + } + } else { + $timeout(function () { + file[disallowObjectUrl ? '$ngfDataUrl' : '$ngfBlobUrl'] = ''; + deferred.reject(); + }); + } + }); + + if (disallowObjectUrl) { + p = file.$$ngfDataUrlPromise = deferred.promise; + } else { + p = file.$$ngfBlobUrlPromise = deferred.promise; + } + p['finally'](function () { + delete file[disallowObjectUrl ? '$$ngfDataUrlPromise' : '$$ngfBlobUrlPromise']; + }); + return p; + }; + return upload; + }]); + + function getTagType(el) { + if (el.tagName.toLowerCase() === 'img') return 'image'; + if (el.tagName.toLowerCase() === 'audio') return 'audio'; + if (el.tagName.toLowerCase() === 'video') return 'video'; + return /./; + } + + function linkFileDirective(Upload, $timeout, scope, elem, attr, directiveName, resizeParams, isBackground) { + function constructDataUrl(file) { + var disallowObjectUrl = Upload.attrGetter('ngfNoObjectUrl', attr, scope); + Upload.dataUrl(file, disallowObjectUrl)['finally'](function () { + $timeout(function () { + var src = (disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl; + if (isBackground) { + elem.css('background-image', 'url(\'' + (src || '') + '\')'); + } else { + elem.attr('src', src); + } + if (src) { + elem.removeClass('ng-hide'); + } else { + elem.addClass('ng-hide'); + } + }); + }); + } + + $timeout(function () { + var unwatch = scope.$watch(attr[directiveName], function (file) { + var size = resizeParams; + if (directiveName === 'ngfThumbnail') { + if (!size) { + size = {width: elem[0].naturalWidth || elem[0].clientWidth, + height: elem[0].naturalHeight || elem[0].clientHeight}; + } + if (size.width === 0 && window.getComputedStyle) { + var style = getComputedStyle(elem[0]); + size = { + width: parseInt(style.width.slice(0, -2)), + height: parseInt(style.height.slice(0, -2)) + }; + } + } + + if (angular.isString(file)) { + elem.removeClass('ng-hide'); + if (isBackground) { + return elem.css('background-image', 'url(\'' + file + '\')'); + } else { + return elem.attr('src', file); + } + } + if (file && file.type && file.type.search(getTagType(elem[0])) === 0 && + (!isBackground || file.type.indexOf('image') === 0)) { + if (size && Upload.isResizeSupported()) { + size.resizeIf = function (width, height) { + return Upload.attrGetter('ngfResizeIf', attr, scope, + {$width: width, $height: height, $file: file}); + }; + Upload.resize(file, size).then( + function (f) { + constructDataUrl(f); + }, function (e) { + throw e; + } + ); + } else { + constructDataUrl(file); + } + } else { + elem.addClass('ng-hide'); + } + }); + + scope.$on('$destroy', function () { + unwatch(); + }); + }); + } + + + /** @namespace attr.ngfSrc */ + /** @namespace attr.ngfNoObjectUrl */ + ngFileUpload.directive('ngfSrc', ['Upload', '$timeout', function (Upload, $timeout) { + return { + restrict: 'AE', + link: function (scope, elem, attr) { + linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfSrc', + Upload.attrGetter('ngfResize', attr, scope), false); + } + }; + }]); + + /** @namespace attr.ngfBackground */ + /** @namespace attr.ngfNoObjectUrl */ + ngFileUpload.directive('ngfBackground', ['Upload', '$timeout', function (Upload, $timeout) { + return { + restrict: 'AE', + link: function (scope, elem, attr) { + linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfBackground', + Upload.attrGetter('ngfResize', attr, scope), true); + } + }; + }]); + + /** @namespace attr.ngfThumbnail */ + /** @namespace attr.ngfAsBackground */ + /** @namespace attr.ngfSize */ + /** @namespace attr.ngfNoObjectUrl */ + ngFileUpload.directive('ngfThumbnail', ['Upload', '$timeout', function (Upload, $timeout) { + return { + restrict: 'AE', + link: function (scope, elem, attr) { + var size = Upload.attrGetter('ngfSize', attr, scope); + linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfThumbnail', size, + Upload.attrGetter('ngfAsBackground', attr, scope)); + } + }; + }]); + + ngFileUpload.config(['$compileProvider', function ($compileProvider) { + if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/); + if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/); + }]); + + ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) { + return function (file, disallowObjectUrl, trustedUrl) { + if (angular.isString(file)) { + return $sce.trustAsResourceUrl(file); + } + var src = file && ((disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl); + if (file && !src) { + if (!file.$ngfDataUrlFilterInProgress && angular.isObject(file)) { + file.$ngfDataUrlFilterInProgress = true; + UploadDataUrl.dataUrl(file, disallowObjectUrl); + } + return ''; + } + if (file) delete file.$ngfDataUrlFilterInProgress; + return (file && src ? (trustedUrl ? $sce.trustAsResourceUrl(src) : src) : file) || ''; + }; + }]); + +})(); + +ngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', function (UploadDataUrl, $q, $timeout) { + var upload = UploadDataUrl; + + function globStringToRegex(str) { + var regexp = '', excludes = []; + if (str.length > 2 && str[0] === '/' && str[str.length - 1] === '/') { + regexp = str.substring(1, str.length - 1); + } else { + var split = str.split(','); + if (split.length > 1) { + for (var i = 0; i < split.length; i++) { + var r = globStringToRegex(split[i]); + if (r.regexp) { + regexp += '(' + r.regexp + ')'; + if (i < split.length - 1) { + regexp += '|'; + } + } else { + excludes = excludes.concat(r.excludes); + } + } + } else { + if (str.indexOf('!') === 0) { + excludes.push('^((?!' + globStringToRegex(str.substring(1)).regexp + ').)*$'); + } else { + if (str.indexOf('.') === 0) { + str = '*' + str; + } + regexp = '^' + str.replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&') + '$'; + regexp = regexp.replace(/\\\*/g, '.*').replace(/\\\?/g, '.'); + } + } + } + return {regexp: regexp, excludes: excludes}; + } + + upload.validatePattern = function (file, val) { + if (!val) { + return true; + } + var pattern = globStringToRegex(val), valid = true; + if (pattern.regexp && pattern.regexp.length) { + var regexp = new RegExp(pattern.regexp, 'i'); + valid = (file.type != null && regexp.test(file.type)) || + (file.name != null && regexp.test(file.name)); + } + var len = pattern.excludes.length; + while (len--) { + var exclude = new RegExp(pattern.excludes[len], 'i'); + valid = valid && (file.type == null || exclude.test(file.type)) && + (file.name == null || exclude.test(file.name)); + } + return valid; + }; + + upload.ratioToFloat = function (val) { + var r = val.toString(), xIndex = r.search(/[x:]/i); + if (xIndex > -1) { + r = parseFloat(r.substring(0, xIndex)) / parseFloat(r.substring(xIndex + 1)); + } else { + r = parseFloat(r); + } + return r; + }; + + upload.registerModelChangeValidator = function (ngModel, attr, scope) { + if (ngModel) { + ngModel.$formatters.push(function (files) { + if (ngModel.$dirty) { + var filesArray = files; + if (files && !angular.isArray(files)) { + filesArray = [files]; + } + upload.validate(filesArray, 0, ngModel, attr, scope).then(function () { + upload.applyModelValidation(ngModel, filesArray); + }); + } + return files; + }); + } + }; + + function markModelAsDirty(ngModel, files) { + if (files != null && !ngModel.$dirty) { + if (ngModel.$setDirty) { + ngModel.$setDirty(); + } else { + ngModel.$dirty = true; + } + } + } + + upload.applyModelValidation = function (ngModel, files) { + markModelAsDirty(ngModel, files); + angular.forEach(ngModel.$ngfValidations, function (validation) { + ngModel.$setValidity(validation.name, validation.valid); + }); + }; + + upload.getValidationAttr = function (attr, scope, name, validationName, file) { + var dName = 'ngf' + name[0].toUpperCase() + name.substr(1); + var val = upload.attrGetter(dName, attr, scope, {$file: file}); + if (val == null) { + val = upload.attrGetter('ngfValidate', attr, scope, {$file: file}); + if (val) { + var split = (validationName || name).split('.'); + val = val[split[0]]; + if (split.length > 1) { + val = val && val[split[1]]; + } + } + } + return val; + }; + + upload.validate = function (files, prevLength, ngModel, attr, scope) { + ngModel = ngModel || {}; + ngModel.$ngfValidations = ngModel.$ngfValidations || []; + + angular.forEach(ngModel.$ngfValidations, function (v) { + v.valid = true; + }); + + var attrGetter = function (name, params) { + return upload.attrGetter(name, attr, scope, params); + }; + + var ignoredErrors = (upload.attrGetter('ngfIgnoreInvalid', attr, scope) || '').split(' '); + var runAllValidation = upload.attrGetter('ngfRunAllValidations', attr, scope); + + if (files == null || files.length === 0) { + return upload.emptyPromise({'validFiles': files, 'invalidFiles': []}); + } + + files = files.length === undefined ? [files] : files.slice(0); + var invalidFiles = []; + + function validateSync(name, validationName, fn) { + if (files) { + var i = files.length, valid = null; + while (i--) { + var file = files[i]; + if (file) { + var val = upload.getValidationAttr(attr, scope, name, validationName, file); + if (val != null) { + if (!fn(file, val, i)) { + if (ignoredErrors.indexOf(name) === -1) { + file.$error = name; + (file.$errorMessages = (file.$errorMessages || {}))[name] = true; + file.$errorParam = val; + if (invalidFiles.indexOf(file) === -1) { + invalidFiles.push(file); + } + if (!runAllValidation) { + files.splice(i, 1); + } + valid = false; + } else { + files.splice(i, 1); + } + } + } + } + } + if (valid !== null) { + ngModel.$ngfValidations.push({name: name, valid: valid}); + } + } + } + + validateSync('pattern', null, upload.validatePattern); + validateSync('minSize', 'size.min', function (file, val) { + return file.size + 0.1 >= upload.translateScalars(val); + }); + validateSync('maxSize', 'size.max', function (file, val) { + return file.size - 0.1 <= upload.translateScalars(val); + }); + var totalSize = 0; + validateSync('maxTotalSize', null, function (file, val) { + totalSize += file.size; + if (totalSize > upload.translateScalars(val)) { + files.splice(0, files.length); + return false; + } + return true; + }); + + validateSync('validateFn', null, function (file, r) { + return r === true || r === null || r === ''; + }); + + if (!files.length) { + return upload.emptyPromise({'validFiles': [], 'invalidFiles': invalidFiles}); + } + + function validateAsync(name, validationName, type, asyncFn, fn) { + function resolveResult(defer, file, val) { + function resolveInternal(fn) { + if (fn()) { + if (ignoredErrors.indexOf(name) === -1) { + file.$error = name; + (file.$errorMessages = (file.$errorMessages || {}))[name] = true; + file.$errorParam = val; + if (invalidFiles.indexOf(file) === -1) { + invalidFiles.push(file); + } + if (!runAllValidation) { + var i = files.indexOf(file); + if (i > -1) files.splice(i, 1); + } + defer.resolve(false); + } else { + var j = files.indexOf(file); + if (j > -1) files.splice(j, 1); + defer.resolve(true); + } + } else { + defer.resolve(true); + } + } + + if (val != null) { + asyncFn(file, val).then(function (d) { + resolveInternal(function () { + return !fn(d, val); + }); + }, function () { + resolveInternal(function () { + return attrGetter('ngfValidateForce', {$file: file}); + }); + }); + } else { + defer.resolve(true); + } + } + + var promises = [upload.emptyPromise(true)]; + if (files) { + files = files.length === undefined ? [files] : files; + angular.forEach(files, function (file) { + var defer = $q.defer(); + promises.push(defer.promise); + if (type && (file.type == null || file.type.search(type) !== 0)) { + defer.resolve(true); + return; + } + if (name === 'dimensions' && upload.attrGetter('ngfDimensions', attr) != null) { + upload.imageDimensions(file).then(function (d) { + resolveResult(defer, file, + attrGetter('ngfDimensions', {$file: file, $width: d.width, $height: d.height})); + }, function () { + defer.resolve(false); + }); + } else if (name === 'duration' && upload.attrGetter('ngfDuration', attr) != null) { + upload.mediaDuration(file).then(function (d) { + resolveResult(defer, file, + attrGetter('ngfDuration', {$file: file, $duration: d})); + }, function () { + defer.resolve(false); + }); + } else { + resolveResult(defer, file, + upload.getValidationAttr(attr, scope, name, validationName, file)); + } + }); + } + var deffer = $q.defer(); + $q.all(promises).then(function (values) { + var isValid = true; + for (var i = 0; i < values.length; i++) { + if (!values[i]) { + isValid = false; + break; + } + } + ngModel.$ngfValidations.push({name: name, valid: isValid}); + deffer.resolve(isValid); + }); + return deffer.promise; + } + + var deffer = $q.defer(); + var promises = []; + + promises.push(validateAsync('maxHeight', 'height.max', /image/, + this.imageDimensions, function (d, val) { + return d.height <= val; + })); + promises.push(validateAsync('minHeight', 'height.min', /image/, + this.imageDimensions, function (d, val) { + return d.height >= val; + })); + promises.push(validateAsync('maxWidth', 'width.max', /image/, + this.imageDimensions, function (d, val) { + return d.width <= val; + })); + promises.push(validateAsync('minWidth', 'width.min', /image/, + this.imageDimensions, function (d, val) { + return d.width >= val; + })); + promises.push(validateAsync('dimensions', null, /image/, + function (file, val) { + return upload.emptyPromise(val); + }, function (r) { + return r; + })); + promises.push(validateAsync('ratio', null, /image/, + this.imageDimensions, function (d, val) { + var split = val.toString().split(','), valid = false; + for (var i = 0; i < split.length; i++) { + if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.01) { + valid = true; + } + } + return valid; + })); + promises.push(validateAsync('maxRatio', 'ratio.max', /image/, + this.imageDimensions, function (d, val) { + return (d.width / d.height) - upload.ratioToFloat(val) < 0.0001; + })); + promises.push(validateAsync('minRatio', 'ratio.min', /image/, + this.imageDimensions, function (d, val) { + return (d.width / d.height) - upload.ratioToFloat(val) > -0.0001; + })); + promises.push(validateAsync('maxDuration', 'duration.max', /audio|video/, + this.mediaDuration, function (d, val) { + return d <= upload.translateScalars(val); + })); + promises.push(validateAsync('minDuration', 'duration.min', /audio|video/, + this.mediaDuration, function (d, val) { + return d >= upload.translateScalars(val); + })); + promises.push(validateAsync('duration', null, /audio|video/, + function (file, val) { + return upload.emptyPromise(val); + }, function (r) { + return r; + })); + + promises.push(validateAsync('validateAsyncFn', null, null, + function (file, val) { + return val; + }, function (r) { + return r === true || r === null || r === ''; + })); + + $q.all(promises).then(function () { + + if (runAllValidation) { + for (var i = 0; i < files.length; i++) { + var file = files[i]; + if (file.$error) { + files.splice(i--, 1); + } + } + } + + runAllValidation = false; + validateSync('maxFiles', null, function (file, val, i) { + return prevLength + i < val; + }); + + deffer.resolve({'validFiles': files, 'invalidFiles': invalidFiles}); + }); + return deffer.promise; + }; + + upload.imageDimensions = function (file) { + if (file.$ngfWidth && file.$ngfHeight) { + var d = $q.defer(); + $timeout(function () { + d.resolve({width: file.$ngfWidth, height: file.$ngfHeight}); + }); + return d.promise; + } + if (file.$ngfDimensionPromise) return file.$ngfDimensionPromise; + + var deferred = $q.defer(); + $timeout(function () { + if (file.type.indexOf('image') !== 0) { + deferred.reject('not image'); + return; + } + upload.dataUrl(file).then(function (dataUrl) { + var img = angular.element('').attr('src', dataUrl) + .css('visibility', 'hidden').css('position', 'fixed') + .css('max-width', 'none !important').css('max-height', 'none !important'); + + function success() { + var width = img[0].naturalWidth || img[0].clientWidth; + var height = img[0].naturalHeight || img[0].clientHeight; + img.remove(); + file.$ngfWidth = width; + file.$ngfHeight = height; + deferred.resolve({width: width, height: height}); + } + + function error() { + img.remove(); + deferred.reject('load error'); + } + + img.on('load', success); + img.on('error', error); + + var secondsCounter = 0; + function checkLoadErrorInCaseOfNoCallback() { + $timeout(function () { + if (img[0].parentNode) { + if (img[0].clientWidth) { + success(); + } else if (secondsCounter++ > 10) { + error(); + } else { + checkLoadErrorInCaseOfNoCallback(); + } + } + }, 1000); + } + + checkLoadErrorInCaseOfNoCallback(); + + angular.element(document.getElementsByTagName('body')[0]).append(img); + }, function () { + deferred.reject('load error'); + }); + }); + + file.$ngfDimensionPromise = deferred.promise; + file.$ngfDimensionPromise['finally'](function () { + delete file.$ngfDimensionPromise; + }); + return file.$ngfDimensionPromise; + }; + + upload.mediaDuration = function (file) { + if (file.$ngfDuration) { + var d = $q.defer(); + $timeout(function () { + d.resolve(file.$ngfDuration); + }); + return d.promise; + } + if (file.$ngfDurationPromise) return file.$ngfDurationPromise; + + var deferred = $q.defer(); + $timeout(function () { + if (file.type.indexOf('audio') !== 0 && file.type.indexOf('video') !== 0) { + deferred.reject('not media'); + return; + } + upload.dataUrl(file).then(function (dataUrl) { + var el = angular.element(file.type.indexOf('audio') === 0 ? '
-
JUTUT
+
JUTUT
-
-
-
-
{{ post.title }}
+
+
+
+
+

{{ post.title }}

+
+
{{ post.author.first_name }} {{ post.author.last_name }}
+
{{ post.date }}
+
+
From b94cc3e056ae2270317652a004b568fdbeb42761 Mon Sep 17 00:00:00 2001 From: okalintu Date: Tue, 4 Oct 2016 14:34:46 +0300 Subject: [PATCH 10/11] fixed bug in infoscreen imageupload --- infoscreen/static/html/generic_image_create.html | 2 +- infoscreen/static/js/infoadmin_controllers.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/infoscreen/static/html/generic_image_create.html b/infoscreen/static/html/generic_image_create.html index 6a76c50..f1d1d9d 100644 --- a/infoscreen/static/html/generic_image_create.html +++ b/infoscreen/static/html/generic_image_create.html @@ -1,7 +1,7 @@
- +
diff --git a/infoscreen/static/js/infoadmin_controllers.js b/infoscreen/static/js/infoadmin_controllers.js index 8351541..2fcbf6d 100644 --- a/infoscreen/static/js/infoadmin_controllers.js +++ b/infoscreen/static/js/infoadmin_controllers.js @@ -109,9 +109,10 @@ app.controller('infoadmin_abbitem_create', function($scope, $http,ItemList){ app.controller('infoadmin_image_create', ['$scope', 'Upload', '$timeout',"ItemList", function ($scope, Upload, $timeout,ItemList) { $scope.send = function(file) { + console.log("name: "+$scope.imagename); file.upload = Upload.upload({ url: '/infoscreen/create_image', - data: {name: $scope.name, image: file}, + data: {name: $scope.imagename, image: file}, }).then(function (response) { $timeout(function () { file.result = response.data; From 7f97b8ada8e32f12a79b5397eeb696d00516023c Mon Sep 17 00:00:00 2001 From: okalintu Date: Tue, 4 Oct 2016 17:58:50 +0300 Subject: [PATCH 11/11] added paid field to memberlist --- members/migrations/0005_member_paid.py | 20 + members/models.py | 35 +- members/static/html/jasenlista.html | 5 + members/static/js/members_controllers.js | 7 +- members/static/js/moment.js | 4234 ++++++++++++++++++++++ members/templates/members_index.html | 1 + 6 files changed, 4293 insertions(+), 9 deletions(-) create mode 100644 members/migrations/0005_member_paid.py create mode 100644 members/static/js/moment.js diff --git a/members/migrations/0005_member_paid.py b/members/migrations/0005_member_paid.py new file mode 100644 index 0000000..bdbf762 --- /dev/null +++ b/members/migrations/0005_member_paid.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2016-10-04 12:40 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('members', '0004_auto_20160915_1606'), + ] + + operations = [ + migrations.AddField( + model_name='member', + name='paid', + field=models.DateTimeField(blank=True, default=None, null=True), + ), + ] diff --git a/members/models.py b/members/models.py index 75496fd..220cfbc 100644 --- a/members/models.py +++ b/members/models.py @@ -1,5 +1,6 @@ from django.db import models from django.utils import timezone +from datetime import datetime from io import StringIO import csv @@ -15,6 +16,7 @@ class Member(models.Model): AYY = models.BooleanField(default=False) jas = models.BooleanField(default=False) created = models.DateTimeField(default=timezone.now) + paid = models.DateTimeField(default=None,null=True,blank=True) def get_dict(self): return { @@ -25,7 +27,8 @@ class Member(models.Model): 'POR':self.POR, 'AYY':self.AYY, 'jas':self.jas, - 'created':self.created.isoformat(' '), + 'created':date2str(self.created), + 'paid':date2str(self.paid), } @classmethod @@ -36,16 +39,17 @@ class Member(models.Model): def update_from_dict(self,d): dmap = { - 'first_name': 'first_name', - 'last_name': 'last_name', - 'email': 'email', - 'POR': 'POR', - 'AYY': 'AYY', - 'jas': 'jas', + 'first_name': reverseMap('first_name'), + 'last_name': reverseMap('last_name'), + 'email': reverseMap('email'), + 'POR': reverseMap('POR'), + 'AYY': reverseMap('AYY'), + 'jas': reverseMap('jas'), + 'paid': reverseMap('paid',str2date), } for k,v in d.items(): try: - self.__setattr__(dmap[k],v) + self.__setattr__(dmap[k].key,dmap[k].parser(v)) except KeyError: pass self.save() @@ -66,6 +70,7 @@ class Member(models.Model): POR=row[3], AYY=row[4].lower() in ['yes','y','1','true',"kyllä", "khyl"], jas=row[5].lower() in ['yes','y','1','true',"kyllä", "khyl"], + paid = timezone.now() ) print("added obj {}".format(obj)) except: @@ -85,3 +90,17 @@ class MemberRequest(models.Model): 'id': self.id, 'member': self.member.get_dict(), } + +def date2str(date): + if not date: + return 'Ei koskaan' + return date.strftime("%Y-%m-%d %H:%M:%S") + +def str2date(s): + return datetime.strptime(s,"%Y-%m-%d %H:%M:%S") + +class reverseMap: + def __init__(self, key, parser=lambda x:x): + self.key = key + self.parser = parser + diff --git a/members/static/html/jasenlista.html b/members/static/html/jasenlista.html index b83549c..1bc0901 100644 --- a/members/static/html/jasenlista.html +++ b/members/static/html/jasenlista.html @@ -10,6 +10,7 @@ JAS-listalla Asuinpaikka Lisätty + Maksanut @@ -21,6 +22,10 @@ {{ x.jas }} {{ x.POR }} {{ x.created }} + {{ x.paid }} + + + Muokkaa diff --git a/members/static/js/members_controllers.js b/members/static/js/members_controllers.js index 4e09385..15ca673 100644 --- a/members/static/js/members_controllers.js +++ b/members/static/js/members_controllers.js @@ -78,6 +78,11 @@ app.controller("getController", function($scope, $http, $window, $location){ }); }; $scope.getFunction(); + $scope.updatePayment= function(id){ + $http.put("/members/api/member/"+id,{paid:moment().format("YYYY-MM-DD kk:mm:ss") }).then(function(resp){ + $scope.getFunction(); + }); + } $scope.delete_member = function(id) { $http.delete("/members/api/member/" + id).then( function(response) { @@ -155,4 +160,4 @@ app.controller("addManyController", function($scope, $http, $window) { } ); }; -}); \ No newline at end of file +}); diff --git a/members/static/js/moment.js b/members/static/js/moment.js new file mode 100644 index 0000000..b78115b --- /dev/null +++ b/members/static/js/moment.js @@ -0,0 +1,4234 @@ +//! moment.js +//! version : 2.15.1 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com + +;(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + global.moment = factory() +}(this, function () { 'use strict'; + + var hookCallback; + + function utils_hooks__hooks () { + return hookCallback.apply(null, arguments); + } + + // This is done to register the method called with moment() + // without creating circular dependencies. + function setHookCallback (callback) { + hookCallback = callback; + } + + function isArray(input) { + return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]'; + } + + function isObject(input) { + // IE8 will treat undefined and null as object if it wasn't for + // input != null + return input != null && Object.prototype.toString.call(input) === '[object Object]'; + } + + function isObjectEmpty(obj) { + var k; + for (k in obj) { + // even if its not own property I'd still call it non-empty + return false; + } + return true; + } + + function isDate(input) { + return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; + } + + function map(arr, fn) { + var res = [], i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); + } + return res; + } + + function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); + } + + function extend(a, b) { + for (var i in b) { + if (hasOwnProp(b, i)) { + a[i] = b[i]; + } + } + + if (hasOwnProp(b, 'toString')) { + a.toString = b.toString; + } + + if (hasOwnProp(b, 'valueOf')) { + a.valueOf = b.valueOf; + } + + return a; + } + + function create_utc__createUTC (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, true).utc(); + } + + function defaultParsingFlags() { + // We need to deep clone this object. + return { + empty : false, + unusedTokens : [], + unusedInput : [], + overflow : -2, + charsLeftOver : 0, + nullInput : false, + invalidMonth : null, + invalidFormat : false, + userInvalidated : false, + iso : false, + parsedDateParts : [], + meridiem : null + }; + } + + function getParsingFlags(m) { + if (m._pf == null) { + m._pf = defaultParsingFlags(); + } + return m._pf; + } + + var some; + if (Array.prototype.some) { + some = Array.prototype.some; + } else { + some = function (fun) { + var t = Object(this); + var len = t.length >>> 0; + + for (var i = 0; i < len; i++) { + if (i in t && fun.call(this, t[i], i, t)) { + return true; + } + } + + return false; + }; + } + + function valid__isValid(m) { + if (m._isValid == null) { + var flags = getParsingFlags(m); + var parsedParts = some.call(flags.parsedDateParts, function (i) { + return i != null; + }); + var isNowValid = !isNaN(m._d.getTime()) && + flags.overflow < 0 && + !flags.empty && + !flags.invalidMonth && + !flags.invalidWeekday && + !flags.nullInput && + !flags.invalidFormat && + !flags.userInvalidated && + (!flags.meridiem || (flags.meridiem && parsedParts)); + + if (m._strict) { + isNowValid = isNowValid && + flags.charsLeftOver === 0 && + flags.unusedTokens.length === 0 && + flags.bigHour === undefined; + } + + if (Object.isFrozen == null || !Object.isFrozen(m)) { + m._isValid = isNowValid; + } + else { + return isNowValid; + } + } + return m._isValid; + } + + function valid__createInvalid (flags) { + var m = create_utc__createUTC(NaN); + if (flags != null) { + extend(getParsingFlags(m), flags); + } + else { + getParsingFlags(m).userInvalidated = true; + } + + return m; + } + + function isUndefined(input) { + return input === void 0; + } + + // Plugins that add properties should also add the key here (null value), + // so we can properly clone ourselves. + var momentProperties = utils_hooks__hooks.momentProperties = []; + + function copyConfig(to, from) { + var i, prop, val; + + if (!isUndefined(from._isAMomentObject)) { + to._isAMomentObject = from._isAMomentObject; + } + if (!isUndefined(from._i)) { + to._i = from._i; + } + if (!isUndefined(from._f)) { + to._f = from._f; + } + if (!isUndefined(from._l)) { + to._l = from._l; + } + if (!isUndefined(from._strict)) { + to._strict = from._strict; + } + if (!isUndefined(from._tzm)) { + to._tzm = from._tzm; + } + if (!isUndefined(from._isUTC)) { + to._isUTC = from._isUTC; + } + if (!isUndefined(from._offset)) { + to._offset = from._offset; + } + if (!isUndefined(from._pf)) { + to._pf = getParsingFlags(from); + } + if (!isUndefined(from._locale)) { + to._locale = from._locale; + } + + if (momentProperties.length > 0) { + for (i in momentProperties) { + prop = momentProperties[i]; + val = from[prop]; + if (!isUndefined(val)) { + to[prop] = val; + } + } + } + + return to; + } + + var updateInProgress = false; + + // Moment prototype object + function Moment(config) { + copyConfig(this, config); + this._d = new Date(config._d != null ? config._d.getTime() : NaN); + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + utils_hooks__hooks.updateOffset(this); + updateInProgress = false; + } + } + + function isMoment (obj) { + return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); + } + + function absFloor (number) { + if (number < 0) { + // -0 -> 0 + return Math.ceil(number) || 0; + } else { + return Math.floor(number); + } + } + + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + value = absFloor(coercedNumber); + } + + return value; + } + + // compare two arrays, return the number of differences + function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ((dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { + diffs++; + } + } + return diffs + lengthDiff; + } + + function warn(msg) { + if (utils_hooks__hooks.suppressDeprecationWarnings === false && + (typeof console !== 'undefined') && console.warn) { + console.warn('Deprecation warning: ' + msg); + } + } + + function deprecate(msg, fn) { + var firstTime = true; + + return extend(function () { + if (utils_hooks__hooks.deprecationHandler != null) { + utils_hooks__hooks.deprecationHandler(null, msg); + } + if (firstTime) { + var args = []; + var arg; + for (var i = 0; i < arguments.length; i++) { + arg = ''; + if (typeof arguments[i] === 'object') { + arg += '\n[' + i + '] '; + for (var key in arguments[0]) { + arg += key + ': ' + arguments[0][key] + ', '; + } + arg = arg.slice(0, -2); // Remove trailing comma and space + } else { + arg = arguments[i]; + } + args.push(arg); + } + warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); + } + + var deprecations = {}; + + function deprecateSimple(name, msg) { + if (utils_hooks__hooks.deprecationHandler != null) { + utils_hooks__hooks.deprecationHandler(name, msg); + } + if (!deprecations[name]) { + warn(msg); + deprecations[name] = true; + } + } + + utils_hooks__hooks.suppressDeprecationWarnings = false; + utils_hooks__hooks.deprecationHandler = null; + + function isFunction(input) { + return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]'; + } + + function locale_set__set (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (isFunction(prop)) { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + this._config = config; + // Lenient ordinal parsing accepts just a number in addition to + // number + (possibly) stuff coming from _ordinalParseLenient. + this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + (/\d{1,2}/).source); + } + + function mergeConfigs(parentConfig, childConfig) { + var res = extend({}, parentConfig), prop; + for (prop in childConfig) { + if (hasOwnProp(childConfig, prop)) { + if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { + res[prop] = {}; + extend(res[prop], parentConfig[prop]); + extend(res[prop], childConfig[prop]); + } else if (childConfig[prop] != null) { + res[prop] = childConfig[prop]; + } else { + delete res[prop]; + } + } + } + for (prop in parentConfig) { + if (hasOwnProp(parentConfig, prop) && + !hasOwnProp(childConfig, prop) && + isObject(parentConfig[prop])) { + // make sure changes to properties don't modify parent config + res[prop] = extend({}, res[prop]); + } + } + return res; + } + + function Locale(config) { + if (config != null) { + this.set(config); + } + } + + var keys; + + if (Object.keys) { + keys = Object.keys; + } else { + keys = function (obj) { + var i, res = []; + for (i in obj) { + if (hasOwnProp(obj, i)) { + res.push(i); + } + } + return res; + }; + } + + var defaultCalendar = { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }; + + function locale_calendar__calendar (key, mom, now) { + var output = this._calendar[key] || this._calendar['sameElse']; + return isFunction(output) ? output.call(mom, now) : output; + } + + var defaultLongDateFormat = { + LTS : 'h:mm:ss A', + LT : 'h:mm A', + L : 'MM/DD/YYYY', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY h:mm A', + LLLL : 'dddd, MMMM D, YYYY h:mm A' + }; + + function longDateFormat (key) { + var format = this._longDateFormat[key], + formatUpper = this._longDateFormat[key.toUpperCase()]; + + if (format || !formatUpper) { + return format; + } + + this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); + + return this._longDateFormat[key]; + } + + var defaultInvalidDate = 'Invalid date'; + + function invalidDate () { + return this._invalidDate; + } + + var defaultOrdinal = '%d'; + var defaultOrdinalParse = /\d{1,2}/; + + function ordinal (number) { + return this._ordinal.replace('%d', number); + } + + var defaultRelativeTime = { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }; + + function relative__relativeTime (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (isFunction(output)) ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); + } + + function pastFuture (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return isFunction(format) ? format(output) : format.replace(/%s/i, output); + } + + var aliases = {}; + + function addUnitAlias (unit, shorthand) { + var lowerCase = unit.toLowerCase(); + aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; + } + + function normalizeUnits(units) { + return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; + } + + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; + } + + var priorities = {}; + + function addUnitPriority(unit, priority) { + priorities[unit] = priority; + } + + function getPrioritizedUnits(unitsObj) { + var units = []; + for (var u in unitsObj) { + units.push({unit: u, priority: priorities[u]}); + } + units.sort(function (a, b) { + return a.priority - b.priority; + }); + return units; + } + + function makeGetSet (unit, keepTime) { + return function (value) { + if (value != null) { + get_set__set(this, unit, value); + utils_hooks__hooks.updateOffset(this, keepTime); + return this; + } else { + return get_set__get(this, unit); + } + }; + } + + function get_set__get (mom, unit) { + return mom.isValid() ? + mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN; + } + + function get_set__set (mom, unit, value) { + if (mom.isValid()) { + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } + } + + // MOMENTS + + function stringGet (units) { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](); + } + return this; + } + + + function stringSet (units, value) { + if (typeof units === 'object') { + units = normalizeObjectUnits(units); + var prioritized = getPrioritizedUnits(units); + for (var i = 0; i < prioritized.length; i++) { + this[prioritized[i].unit](units[prioritized[i].unit]); + } + } else { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](value); + } + } + return this; + } + + function zeroFill(number, targetLength, forceSign) { + var absNumber = '' + Math.abs(number), + zerosToFill = targetLength - absNumber.length, + sign = number >= 0; + return (sign ? (forceSign ? '+' : '') : '-') + + Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber; + } + + var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g; + + var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; + + var formatFunctions = {}; + + var formatTokenFunctions = {}; + + // token: 'M' + // padded: ['MM', 2] + // ordinal: 'Mo' + // callback: function () { this.month() + 1 } + function addFormatToken (token, padded, ordinal, callback) { + var func = callback; + if (typeof callback === 'string') { + func = function () { + return this[callback](); + }; + } + if (token) { + formatTokenFunctions[token] = func; + } + if (padded) { + formatTokenFunctions[padded[0]] = function () { + return zeroFill(func.apply(this, arguments), padded[1], padded[2]); + }; + } + if (ordinal) { + formatTokenFunctions[ordinal] = function () { + return this.localeData().ordinal(func.apply(this, arguments), token); + }; + } + } + + function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ''); + } + return input.replace(/\\/g, ''); + } + + function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = '', i; + for (i = 0; i < length; i++) { + output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; + } + return output; + }; + } + + // format date using native date object + function formatMoment(m, format) { + if (!m.isValid()) { + return m.localeData().invalidDate(); + } + + format = expandFormat(format, m.localeData()); + formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format); + + return formatFunctions[format](m); + } + + function expandFormat(format, locale) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return locale.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; + } + + var match1 = /\d/; // 0 - 9 + var match2 = /\d\d/; // 00 - 99 + var match3 = /\d{3}/; // 000 - 999 + var match4 = /\d{4}/; // 0000 - 9999 + var match6 = /[+-]?\d{6}/; // -999999 - 999999 + var match1to2 = /\d\d?/; // 0 - 99 + var match3to4 = /\d\d\d\d?/; // 999 - 9999 + var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999 + var match1to3 = /\d{1,3}/; // 0 - 999 + var match1to4 = /\d{1,4}/; // 0 - 9999 + var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 + + var matchUnsigned = /\d+/; // 0 - inf + var matchSigned = /[+-]?\d+/; // -inf - inf + + var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z + var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z + + var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 + + // any word (or two) characters or numbers including two/three word month in arabic. + // includes scottish gaelic two word and hyphenated months + var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i; + + + var regexes = {}; + + function addRegexToken (token, regex, strictRegex) { + regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) { + return (isStrict && strictRegex) ? strictRegex : regex; + }; + } + + function getParseRegexForToken (token, config) { + if (!hasOwnProp(regexes, token)) { + return new RegExp(unescapeFormat(token)); + } + + return regexes[token](config._strict, config._locale); + } + + // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript + function unescapeFormat(s) { + return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + })); + } + + function regexEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } + + var tokens = {}; + + function addParseToken (token, callback) { + var i, func = callback; + if (typeof token === 'string') { + token = [token]; + } + if (typeof callback === 'number') { + func = function (input, array) { + array[callback] = toInt(input); + }; + } + for (i = 0; i < token.length; i++) { + tokens[token[i]] = func; + } + } + + function addWeekParseToken (token, callback) { + addParseToken(token, function (input, array, config, token) { + config._w = config._w || {}; + callback(input, config._w, config, token); + }); + } + + function addTimeToArrayFromToken(token, input, config) { + if (input != null && hasOwnProp(tokens, token)) { + tokens[token](input, config._a, config, token); + } + } + + var YEAR = 0; + var MONTH = 1; + var DATE = 2; + var HOUR = 3; + var MINUTE = 4; + var SECOND = 5; + var MILLISECOND = 6; + var WEEK = 7; + var WEEKDAY = 8; + + var indexOf; + + if (Array.prototype.indexOf) { + indexOf = Array.prototype.indexOf; + } else { + indexOf = function (o) { + // I know + var i; + for (i = 0; i < this.length; ++i) { + if (this[i] === o) { + return i; + } + } + return -1; + }; + } + + function daysInMonth(year, month) { + return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); + } + + // FORMATTING + + addFormatToken('M', ['MM', 2], 'Mo', function () { + return this.month() + 1; + }); + + addFormatToken('MMM', 0, 0, function (format) { + return this.localeData().monthsShort(this, format); + }); + + addFormatToken('MMMM', 0, 0, function (format) { + return this.localeData().months(this, format); + }); + + // ALIASES + + addUnitAlias('month', 'M'); + + // PRIORITY + + addUnitPriority('month', 8); + + // PARSING + + addRegexToken('M', match1to2); + addRegexToken('MM', match1to2, match2); + addRegexToken('MMM', function (isStrict, locale) { + return locale.monthsShortRegex(isStrict); + }); + addRegexToken('MMMM', function (isStrict, locale) { + return locale.monthsRegex(isStrict); + }); + + addParseToken(['M', 'MM'], function (input, array) { + array[MONTH] = toInt(input) - 1; + }); + + addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { + var month = config._locale.monthsParse(input, token, config._strict); + // if we didn't find a month name, mark the date as invalid. + if (month != null) { + array[MONTH] = month; + } else { + getParsingFlags(config).invalidMonth = input; + } + }); + + // LOCALES + + var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/; + var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); + function localeMonths (m, format) { + if (!m) { + return this._months; + } + return isArray(this._months) ? this._months[m.month()] : + this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()]; + } + + var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); + function localeMonthsShort (m, format) { + if (!m) { + return this._monthsShort; + } + return isArray(this._monthsShort) ? this._monthsShort[m.month()] : + this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()]; + } + + function units_month__handleStrictParse(monthName, format, strict) { + var i, ii, mom, llc = monthName.toLocaleLowerCase(); + if (!this._monthsParse) { + // this is not used + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + for (i = 0; i < 12; ++i) { + mom = create_utc__createUTC([2000, i]); + this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase(); + this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } + } + + function localeMonthsParse (monthName, format, strict) { + var i, mom, regex; + + if (this._monthsParseExact) { + return units_month__handleStrictParse.call(this, monthName, format, strict); + } + + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } + + // TODO: add sorting + // Sorting makes sure if one month (or abbr) is a prefix of another + // see sorting in computeMonthsParse + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = create_utc__createUTC([2000, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); + this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); + } + if (!strict && !this._monthsParse[i]) { + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { + return i; + } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { + return i; + } + } + } + + // MOMENTS + + function setMonth (mom, value) { + var dayOfMonth; + + if (!mom.isValid()) { + // No op + return mom; + } + + if (typeof value === 'string') { + if (/^\d+$/.test(value)) { + value = toInt(value); + } else { + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (typeof value !== 'number') { + return mom; + } + } + } + + dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; + } + + function getSetMonth (value) { + if (value != null) { + setMonth(this, value); + utils_hooks__hooks.updateOffset(this, true); + return this; + } else { + return get_set__get(this, 'Month'); + } + } + + function getDaysInMonth () { + return daysInMonth(this.year(), this.month()); + } + + var defaultMonthsShortRegex = matchWord; + function monthsShortRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsShortStrictRegex; + } else { + return this._monthsShortRegex; + } + } else { + if (!hasOwnProp(this, '_monthsShortRegex')) { + this._monthsShortRegex = defaultMonthsShortRegex; + } + return this._monthsShortStrictRegex && isStrict ? + this._monthsShortStrictRegex : this._monthsShortRegex; + } + } + + var defaultMonthsRegex = matchWord; + function monthsRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsStrictRegex; + } else { + return this._monthsRegex; + } + } else { + if (!hasOwnProp(this, '_monthsRegex')) { + this._monthsRegex = defaultMonthsRegex; + } + return this._monthsStrictRegex && isStrict ? + this._monthsStrictRegex : this._monthsRegex; + } + } + + function computeMonthsParse () { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var shortPieces = [], longPieces = [], mixedPieces = [], + i, mom; + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = create_utc__createUTC([2000, i]); + shortPieces.push(this.monthsShort(mom, '')); + longPieces.push(this.months(mom, '')); + mixedPieces.push(this.months(mom, '')); + mixedPieces.push(this.monthsShort(mom, '')); + } + // Sorting makes sure if one month (or abbr) is a prefix of another it + // will match the longer piece. + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 12; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + } + for (i = 0; i < 24; i++) { + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._monthsShortRegex = this._monthsRegex; + this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); + } + + // FORMATTING + + addFormatToken('Y', 0, 0, function () { + var y = this.year(); + return y <= 9999 ? '' + y : '+' + y; + }); + + addFormatToken(0, ['YY', 2], 0, function () { + return this.year() % 100; + }); + + addFormatToken(0, ['YYYY', 4], 0, 'year'); + addFormatToken(0, ['YYYYY', 5], 0, 'year'); + addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); + + // ALIASES + + addUnitAlias('year', 'y'); + + // PRIORITIES + + addUnitPriority('year', 1); + + // PARSING + + addRegexToken('Y', matchSigned); + addRegexToken('YY', match1to2, match2); + addRegexToken('YYYY', match1to4, match4); + addRegexToken('YYYYY', match1to6, match6); + addRegexToken('YYYYYY', match1to6, match6); + + addParseToken(['YYYYY', 'YYYYYY'], YEAR); + addParseToken('YYYY', function (input, array) { + array[YEAR] = input.length === 2 ? utils_hooks__hooks.parseTwoDigitYear(input) : toInt(input); + }); + addParseToken('YY', function (input, array) { + array[YEAR] = utils_hooks__hooks.parseTwoDigitYear(input); + }); + addParseToken('Y', function (input, array) { + array[YEAR] = parseInt(input, 10); + }); + + // HELPERS + + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; + } + + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } + + // HOOKS + + utils_hooks__hooks.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + }; + + // MOMENTS + + var getSetYear = makeGetSet('FullYear', true); + + function getIsLeapYear () { + return isLeapYear(this.year()); + } + + function createDate (y, m, d, h, M, s, ms) { + //can't just apply() to create a date: + //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply + var date = new Date(y, m, d, h, M, s, ms); + + //the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0 && isFinite(date.getFullYear())) { + date.setFullYear(y); + } + return date; + } + + function createUTCDate (y) { + var date = new Date(Date.UTC.apply(null, arguments)); + + //the Date.UTC function remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) { + date.setUTCFullYear(y); + } + return date; + } + + // start-of-first-week - start-of-year + function firstWeekOffset(year, dow, doy) { + var // first-week day -- which january is always in the first week (4 for iso, 1 for other) + fwd = 7 + dow - doy, + // first-week day local weekday -- which local weekday is fwd + fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; + + return -fwdlw + fwd - 1; + } + + //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday + function dayOfYearFromWeeks(year, week, weekday, dow, doy) { + var localWeekday = (7 + weekday - dow) % 7, + weekOffset = firstWeekOffset(year, dow, doy), + dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, + resYear, resDayOfYear; + + if (dayOfYear <= 0) { + resYear = year - 1; + resDayOfYear = daysInYear(resYear) + dayOfYear; + } else if (dayOfYear > daysInYear(year)) { + resYear = year + 1; + resDayOfYear = dayOfYear - daysInYear(year); + } else { + resYear = year; + resDayOfYear = dayOfYear; + } + + return { + year: resYear, + dayOfYear: resDayOfYear + }; + } + + function weekOfYear(mom, dow, doy) { + var weekOffset = firstWeekOffset(mom.year(), dow, doy), + week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, + resWeek, resYear; + + if (week < 1) { + resYear = mom.year() - 1; + resWeek = week + weeksInYear(resYear, dow, doy); + } else if (week > weeksInYear(mom.year(), dow, doy)) { + resWeek = week - weeksInYear(mom.year(), dow, doy); + resYear = mom.year() + 1; + } else { + resYear = mom.year(); + resWeek = week; + } + + return { + week: resWeek, + year: resYear + }; + } + + function weeksInYear(year, dow, doy) { + var weekOffset = firstWeekOffset(year, dow, doy), + weekOffsetNext = firstWeekOffset(year + 1, dow, doy); + return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; + } + + // FORMATTING + + addFormatToken('w', ['ww', 2], 'wo', 'week'); + addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); + + // ALIASES + + addUnitAlias('week', 'w'); + addUnitAlias('isoWeek', 'W'); + + // PRIORITIES + + addUnitPriority('week', 5); + addUnitPriority('isoWeek', 5); + + // PARSING + + addRegexToken('w', match1to2); + addRegexToken('ww', match1to2, match2); + addRegexToken('W', match1to2); + addRegexToken('WW', match1to2, match2); + + addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { + week[token.substr(0, 1)] = toInt(input); + }); + + // HELPERS + + // LOCALES + + function localeWeek (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + } + + var defaultLocaleWeek = { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + }; + + function localeFirstDayOfWeek () { + return this._week.dow; + } + + function localeFirstDayOfYear () { + return this._week.doy; + } + + // MOMENTS + + function getSetWeek (input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); + } + + function getSetISOWeek (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, 'd'); + } + + // FORMATTING + + addFormatToken('d', 0, 'do', 'day'); + + addFormatToken('dd', 0, 0, function (format) { + return this.localeData().weekdaysMin(this, format); + }); + + addFormatToken('ddd', 0, 0, function (format) { + return this.localeData().weekdaysShort(this, format); + }); + + addFormatToken('dddd', 0, 0, function (format) { + return this.localeData().weekdays(this, format); + }); + + addFormatToken('e', 0, 0, 'weekday'); + addFormatToken('E', 0, 0, 'isoWeekday'); + + // ALIASES + + addUnitAlias('day', 'd'); + addUnitAlias('weekday', 'e'); + addUnitAlias('isoWeekday', 'E'); + + // PRIORITY + addUnitPriority('day', 11); + addUnitPriority('weekday', 11); + addUnitPriority('isoWeekday', 11); + + // PARSING + + addRegexToken('d', match1to2); + addRegexToken('e', match1to2); + addRegexToken('E', match1to2); + addRegexToken('dd', function (isStrict, locale) { + return locale.weekdaysMinRegex(isStrict); + }); + addRegexToken('ddd', function (isStrict, locale) { + return locale.weekdaysShortRegex(isStrict); + }); + addRegexToken('dddd', function (isStrict, locale) { + return locale.weekdaysRegex(isStrict); + }); + + addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { + var weekday = config._locale.weekdaysParse(input, token, config._strict); + // if we didn't get a weekday name, mark the date as invalid + if (weekday != null) { + week.d = weekday; + } else { + getParsingFlags(config).invalidWeekday = input; + } + }); + + addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { + week[token] = toInt(input); + }); + + // HELPERS + + function parseWeekday(input, locale) { + if (typeof input !== 'string') { + return input; + } + + if (!isNaN(input)) { + return parseInt(input, 10); + } + + input = locale.weekdaysParse(input); + if (typeof input === 'number') { + return input; + } + + return null; + } + + function parseIsoWeekday(input, locale) { + if (typeof input === 'string') { + return locale.weekdaysParse(input) % 7 || 7; + } + return isNaN(input) ? null : input; + } + + // LOCALES + + var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); + function localeWeekdays (m, format) { + if (!m) { + return this._weekdays; + } + return isArray(this._weekdays) ? this._weekdays[m.day()] : + this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()]; + } + + var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); + function localeWeekdaysShort (m) { + return (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort; + } + + var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); + function localeWeekdaysMin (m) { + return (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin; + } + + function day_of_week__handleStrictParse(weekdayName, format, strict) { + var i, ii, mom, llc = weekdayName.toLocaleLowerCase(); + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._shortWeekdaysParse = []; + this._minWeekdaysParse = []; + + for (i = 0; i < 7; ++i) { + mom = create_utc__createUTC([2000, 1]).day(i); + this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase(); + this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase(); + this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } + } + + function localeWeekdaysParse (weekdayName, format, strict) { + var i, mom, regex; + + if (this._weekdaysParseExact) { + return day_of_week__handleStrictParse.call(this, weekdayName, format, strict); + } + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._minWeekdaysParse = []; + this._shortWeekdaysParse = []; + this._fullWeekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + + mom = create_utc__createUTC([2000, 1]).day(i); + if (strict && !this._fullWeekdaysParse[i]) { + this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i'); + this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i'); + this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i'); + } + if (!this._weekdaysParse[i]) { + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + } + + // MOMENTS + + function getSetDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); + } else { + return day; + } + } + + function getSetLocaleDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); + } + + function getSetISODayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + + if (input != null) { + var weekday = parseIsoWeekday(input, this.localeData()); + return this.day(this.day() % 7 ? weekday : weekday - 7); + } else { + return this.day() || 7; + } + } + + var defaultWeekdaysRegex = matchWord; + function weekdaysRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysStrictRegex; + } else { + return this._weekdaysRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysRegex')) { + this._weekdaysRegex = defaultWeekdaysRegex; + } + return this._weekdaysStrictRegex && isStrict ? + this._weekdaysStrictRegex : this._weekdaysRegex; + } + } + + var defaultWeekdaysShortRegex = matchWord; + function weekdaysShortRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysShortStrictRegex; + } else { + return this._weekdaysShortRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysShortRegex')) { + this._weekdaysShortRegex = defaultWeekdaysShortRegex; + } + return this._weekdaysShortStrictRegex && isStrict ? + this._weekdaysShortStrictRegex : this._weekdaysShortRegex; + } + } + + var defaultWeekdaysMinRegex = matchWord; + function weekdaysMinRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysMinStrictRegex; + } else { + return this._weekdaysMinRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysMinRegex')) { + this._weekdaysMinRegex = defaultWeekdaysMinRegex; + } + return this._weekdaysMinStrictRegex && isStrict ? + this._weekdaysMinStrictRegex : this._weekdaysMinRegex; + } + } + + + function computeWeekdaysParse () { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [], + i, mom, minp, shortp, longp; + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + mom = create_utc__createUTC([2000, 1]).day(i); + minp = this.weekdaysMin(mom, ''); + shortp = this.weekdaysShort(mom, ''); + longp = this.weekdays(mom, ''); + minPieces.push(minp); + shortPieces.push(shortp); + longPieces.push(longp); + mixedPieces.push(minp); + mixedPieces.push(shortp); + mixedPieces.push(longp); + } + // Sorting makes sure if one weekday (or abbr) is a prefix of another it + // will match the longer piece. + minPieces.sort(cmpLenRev); + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 7; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._weekdaysShortRegex = this._weekdaysRegex; + this._weekdaysMinRegex = this._weekdaysRegex; + + this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); + this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i'); + } + + // FORMATTING + + function hFormat() { + return this.hours() % 12 || 12; + } + + function kFormat() { + return this.hours() || 24; + } + + addFormatToken('H', ['HH', 2], 0, 'hour'); + addFormatToken('h', ['hh', 2], 0, hFormat); + addFormatToken('k', ['kk', 2], 0, kFormat); + + addFormatToken('hmm', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); + }); + + addFormatToken('hmmss', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); + }); + + addFormatToken('Hmm', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2); + }); + + addFormatToken('Hmmss', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); + }); + + function meridiem (token, lowercase) { + addFormatToken(token, 0, 0, function () { + return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); + }); + } + + meridiem('a', true); + meridiem('A', false); + + // ALIASES + + addUnitAlias('hour', 'h'); + + // PRIORITY + addUnitPriority('hour', 13); + + // PARSING + + function matchMeridiem (isStrict, locale) { + return locale._meridiemParse; + } + + addRegexToken('a', matchMeridiem); + addRegexToken('A', matchMeridiem); + addRegexToken('H', match1to2); + addRegexToken('h', match1to2); + addRegexToken('HH', match1to2, match2); + addRegexToken('hh', match1to2, match2); + + addRegexToken('hmm', match3to4); + addRegexToken('hmmss', match5to6); + addRegexToken('Hmm', match3to4); + addRegexToken('Hmmss', match5to6); + + addParseToken(['H', 'HH'], HOUR); + addParseToken(['a', 'A'], function (input, array, config) { + config._isPm = config._locale.isPM(input); + config._meridiem = input; + }); + addParseToken(['h', 'hh'], function (input, array, config) { + array[HOUR] = toInt(input); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('Hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + }); + addParseToken('Hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + }); + + // LOCALES + + function localeIsPM (input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return ((input + '').toLowerCase().charAt(0) === 'p'); + } + + var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; + function localeMeridiem (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + } + + + // MOMENTS + + // Setting the hour should keep the time, because the user explicitly + // specified which hour he wants. So trying to maintain the same hour (in + // a new timezone) makes sense. Adding/subtracting hours does not follow + // this rule. + var getSetHour = makeGetSet('Hours', true); + + var baseConfig = { + calendar: defaultCalendar, + longDateFormat: defaultLongDateFormat, + invalidDate: defaultInvalidDate, + ordinal: defaultOrdinal, + ordinalParse: defaultOrdinalParse, + relativeTime: defaultRelativeTime, + + months: defaultLocaleMonths, + monthsShort: defaultLocaleMonthsShort, + + week: defaultLocaleWeek, + + weekdays: defaultLocaleWeekdays, + weekdaysMin: defaultLocaleWeekdaysMin, + weekdaysShort: defaultLocaleWeekdaysShort, + + meridiemParse: defaultLocaleMeridiemParse + }; + + // internal storage for locale config files + var locales = {}; + var globalLocale; + + function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; + } + + // pick the locale from the array + // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + function chooseLocale(names) { + var i = 0, j, next, locale, split; + + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return null; + } + + function loadLocale(name) { + var oldLocale = null; + // TODO: Find a better way to register and load all the locales in Node + if (!locales[name] && (typeof module !== 'undefined') && + module && module.exports) { + try { + oldLocale = globalLocale._abbr; + require('./locale/' + name); + // because defineLocale currently also sets the global locale, we + // want to undo that for lazy loaded locales + locale_locales__getSetGlobalLocale(oldLocale); + } catch (e) { } + } + return locales[name]; + } + + // This function will load locale and then set the global locale. If + // no arguments are passed in, it will simply return the current global + // locale key. + function locale_locales__getSetGlobalLocale (key, values) { + var data; + if (key) { + if (isUndefined(values)) { + data = locale_locales__getLocale(key); + } + else { + data = defineLocale(key, values); + } + + if (data) { + // moment.duration._locale = moment._locale = data; + globalLocale = data; + } + } + + return globalLocale._abbr; + } + + function defineLocale (name, config) { + if (config !== null) { + var parentConfig = baseConfig; + config.abbr = name; + if (locales[name] != null) { + deprecateSimple('defineLocaleOverride', + 'use moment.updateLocale(localeName, config) to change ' + + 'an existing locale. moment.defineLocale(localeName, ' + + 'config) should only be used for creating a new locale ' + + 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'); + parentConfig = locales[name]._config; + } else if (config.parentLocale != null) { + if (locales[config.parentLocale] != null) { + parentConfig = locales[config.parentLocale]._config; + } else { + // treat as if there is no base config + deprecateSimple('parentLocaleUndefined', + 'specified parentLocale is not defined yet. See http://momentjs.com/guides/#/warnings/parent-locale/'); + } + } + locales[name] = new Locale(mergeConfigs(parentConfig, config)); + + // backwards compat for now: also set the locale + locale_locales__getSetGlobalLocale(name); + + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } + } + + function updateLocale(name, config) { + if (config != null) { + var locale, parentConfig = baseConfig; + // MERGE + if (locales[name] != null) { + parentConfig = locales[name]._config; + } + config = mergeConfigs(parentConfig, config); + locale = new Locale(config); + locale.parentLocale = locales[name]; + locales[name] = locale; + + // backwards compat for now: also set the locale + locale_locales__getSetGlobalLocale(name); + } else { + // pass null for config to unupdate, useful for tests + if (locales[name] != null) { + if (locales[name].parentLocale != null) { + locales[name] = locales[name].parentLocale; + } else if (locales[name] != null) { + delete locales[name]; + } + } + } + return locales[name]; + } + + // returns locale data + function locale_locales__getLocale (key) { + var locale; + + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } + + if (!key) { + return globalLocale; + } + + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; + } + + return chooseLocale(key); + } + + function locale_locales__listLocales() { + return keys(locales); + } + + function checkOverflow (m) { + var overflow; + var a = m._a; + + if (a && getParsingFlags(m).overflow === -2) { + overflow = + a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : + a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : + a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : + a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : + a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : + a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : + -1; + + if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } + if (getParsingFlags(m)._overflowWeeks && overflow === -1) { + overflow = WEEK; + } + if (getParsingFlags(m)._overflowWeekday && overflow === -1) { + overflow = WEEKDAY; + } + + getParsingFlags(m).overflow = overflow; + } + + return m; + } + + // iso 8601 regex + // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) + var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/; + var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/; + + var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/; + + var isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], + ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], + ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], + ['GGGG-[W]WW', /\d{4}-W\d\d/, false], + ['YYYY-DDD', /\d{4}-\d{3}/], + ['YYYY-MM', /\d{4}-\d\d/, false], + ['YYYYYYMMDD', /[+-]\d{10}/], + ['YYYYMMDD', /\d{8}/], + // YYYYMM is NOT allowed by the standard + ['GGGG[W]WWE', /\d{4}W\d{3}/], + ['GGGG[W]WW', /\d{4}W\d{2}/, false], + ['YYYYDDD', /\d{7}/] + ]; + + // iso time formats and regexes + var isoTimes = [ + ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], + ['HH:mm:ss', /\d\d:\d\d:\d\d/], + ['HH:mm', /\d\d:\d\d/], + ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], + ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], + ['HHmmss', /\d\d\d\d\d\d/], + ['HHmm', /\d\d\d\d/], + ['HH', /\d\d/] + ]; + + var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; + + // date from iso format + function configFromISO(config) { + var i, l, + string = config._i, + match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), + allowTime, dateFormat, timeFormat, tzFormat; + + if (match) { + getParsingFlags(config).iso = true; + + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(match[1])) { + dateFormat = isoDates[i][0]; + allowTime = isoDates[i][2] !== false; + break; + } + } + if (dateFormat == null) { + config._isValid = false; + return; + } + if (match[3]) { + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(match[3])) { + // match[2] should be 'T' or space + timeFormat = (match[2] || ' ') + isoTimes[i][0]; + break; + } + } + if (timeFormat == null) { + config._isValid = false; + return; + } + } + if (!allowTime && timeFormat != null) { + config._isValid = false; + return; + } + if (match[4]) { + if (tzRegex.exec(match[4])) { + tzFormat = 'Z'; + } else { + config._isValid = false; + return; + } + } + config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); + configFromStringAndFormat(config); + } else { + config._isValid = false; + } + } + + // date from iso format or fallback + function configFromString(config) { + var matched = aspNetJsonRegex.exec(config._i); + + if (matched !== null) { + config._d = new Date(+matched[1]); + return; + } + + configFromISO(config); + if (config._isValid === false) { + delete config._isValid; + utils_hooks__hooks.createFromInputFallback(config); + } + } + + utils_hooks__hooks.createFromInputFallback = deprecate( + 'value provided is not in a recognized ISO format. moment construction falls back to js Date(), ' + + 'which is not reliable across all browsers and versions. Non ISO date formats are ' + + 'discouraged and will be removed in an upcoming major release. Please refer to ' + + 'http://momentjs.com/guides/#/warnings/js-date/ for more info.', + function (config) { + config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); + } + ); + + // Pick the first defined of two or three arguments. + function defaults(a, b, c) { + if (a != null) { + return a; + } + if (b != null) { + return b; + } + return c; + } + + function currentDateArray(config) { + // hooks is actually the exported moment object + var nowValue = new Date(utils_hooks__hooks.now()); + if (config._useUTC) { + return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()]; + } + return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function configFromArray (config) { + var i, date, input = [], currentDate, yearToUse; + + if (config._d) { + return; + } + + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + dayOfYearFromWeekInfo(config); + } + + //if the day of the year is set, figure out what it is + if (config._dayOfYear) { + yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); + + if (config._dayOfYear > daysInYear(yearToUse)) { + getParsingFlags(config)._overflowDayOfYear = true; + } + + date = createUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } + + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } + + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // Check for 24:00:00.000 + if (config._a[HOUR] === 24 && + config._a[MINUTE] === 0 && + config._a[SECOND] === 0 && + config._a[MILLISECOND] === 0) { + config._nextDay = true; + config._a[HOUR] = 0; + } + + config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); + // Apply timezone offset from input. The actual utcOffset can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + } + + if (config._nextDay) { + config._a[HOUR] = 24; + } + } + + function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; + + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(local__createLocal(), 1, 4).year); + week = defaults(w.W, 1); + weekday = defaults(w.E, 1); + if (weekday < 1 || weekday > 7) { + weekdayOverflow = true; + } + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; + + weekYear = defaults(w.gg, config._a[YEAR], weekOfYear(local__createLocal(), dow, doy).year); + week = defaults(w.w, 1); + + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < 0 || weekday > 6) { + weekdayOverflow = true; + } + } else if (w.e != null) { + // local weekday -- counting starts from begining of week + weekday = w.e + dow; + if (w.e < 0 || w.e > 6) { + weekdayOverflow = true; + } + } else { + // default to begining of week + weekday = dow; + } + } + if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { + getParsingFlags(config)._overflowWeeks = true; + } else if (weekdayOverflow != null) { + getParsingFlags(config)._overflowWeekday = true; + } else { + temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + } + + // constant that refers to the ISO standard + utils_hooks__hooks.ISO_8601 = function () {}; + + // date from string and format string + function configFromStringAndFormat(config) { + // TODO: Move this to another part of the creation flow to prevent circular deps + if (config._f === utils_hooks__hooks.ISO_8601) { + configFromISO(config); + return; + } + + config._a = []; + getParsingFlags(config).empty = true; + + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var string = '' + config._i, + i, parsedInput, tokens, token, skipped, + stringLength = string.length, + totalParsedInputLength = 0; + + tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; + // console.log('token', token, 'parsedInput', parsedInput, + // 'regex', getParseRegexForToken(token, config)); + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + getParsingFlags(config).unusedInput.push(skipped); + } + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + getParsingFlags(config).empty = false; + } + else { + getParsingFlags(config).unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } + else if (config._strict && !parsedInput) { + getParsingFlags(config).unusedTokens.push(token); + } + } + + // add remaining unparsed input length to the string + getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + getParsingFlags(config).unusedInput.push(string); + } + + // clear _12h flag if hour is <= 12 + if (config._a[HOUR] <= 12 && + getParsingFlags(config).bigHour === true && + config._a[HOUR] > 0) { + getParsingFlags(config).bigHour = undefined; + } + + getParsingFlags(config).parsedDateParts = config._a.slice(0); + getParsingFlags(config).meridiem = config._meridiem; + // handle meridiem + config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); + + configFromArray(config); + checkOverflow(config); + } + + + function meridiemFixWrap (locale, hour, meridiem) { + var isPm; + + if (meridiem == null) { + // nothing to do + return hour; + } + if (locale.meridiemHour != null) { + return locale.meridiemHour(hour, meridiem); + } else if (locale.isPM != null) { + // Fallback + isPm = locale.isPM(meridiem); + if (isPm && hour < 12) { + hour += 12; + } + if (!isPm && hour === 12) { + hour = 0; + } + return hour; + } else { + // this is not supposed to happen + return hour; + } + } + + // date from string and array of format strings + function configFromStringAndArray(config) { + var tempConfig, + bestMoment, + + scoreToBeat, + i, + currentScore; + + if (config._f.length === 0) { + getParsingFlags(config).invalidFormat = true; + config._d = new Date(NaN); + return; + } + + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; + } + tempConfig._f = config._f[i]; + configFromStringAndFormat(tempConfig); + + if (!valid__isValid(tempConfig)) { + continue; + } + + // if there is any input that was not parsed add a penalty for that format + currentScore += getParsingFlags(tempConfig).charsLeftOver; + + //or tokens + currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; + + getParsingFlags(tempConfig).score = currentScore; + + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + + extend(config, bestMoment || tempConfig); + } + + function configFromObject(config) { + if (config._d) { + return; + } + + var i = normalizeObjectUnits(config._i); + config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) { + return obj && parseInt(obj, 10); + }); + + configFromArray(config); + } + + function createFromConfig (config) { + var res = new Moment(checkOverflow(prepareConfig(config))); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; + } + + return res; + } + + function prepareConfig (config) { + var input = config._i, + format = config._f; + + config._locale = config._locale || locale_locales__getLocale(config._l); + + if (input === null || (format === undefined && input === '')) { + return valid__createInvalid({nullInput: true}); + } + + if (typeof input === 'string') { + config._i = input = config._locale.preparse(input); + } + + if (isMoment(input)) { + return new Moment(checkOverflow(input)); + } else if (isArray(format)) { + configFromStringAndArray(config); + } else if (isDate(input)) { + config._d = input; + } else if (format) { + configFromStringAndFormat(config); + } else { + configFromInput(config); + } + + if (!valid__isValid(config)) { + config._d = null; + } + + return config; + } + + function configFromInput(config) { + var input = config._i; + if (input === undefined) { + config._d = new Date(utils_hooks__hooks.now()); + } else if (isDate(input)) { + config._d = new Date(input.valueOf()); + } else if (typeof input === 'string') { + configFromString(config); + } else if (isArray(input)) { + config._a = map(input.slice(0), function (obj) { + return parseInt(obj, 10); + }); + configFromArray(config); + } else if (typeof(input) === 'object') { + configFromObject(config); + } else if (typeof(input) === 'number') { + // from milliseconds + config._d = new Date(input); + } else { + utils_hooks__hooks.createFromInputFallback(config); + } + } + + function createLocalOrUTC (input, format, locale, strict, isUTC) { + var c = {}; + + if (typeof(locale) === 'boolean') { + strict = locale; + locale = undefined; + } + + if ((isObject(input) && isObjectEmpty(input)) || + (isArray(input) && input.length === 0)) { + input = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c._isAMomentObject = true; + c._useUTC = c._isUTC = isUTC; + c._l = locale; + c._i = input; + c._f = format; + c._strict = strict; + + return createFromConfig(c); + } + + function local__createLocal (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, false); + } + + var prototypeMin = deprecate( + 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = local__createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other < this ? this : other; + } else { + return valid__createInvalid(); + } + } + ); + + var prototypeMax = deprecate( + 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = local__createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other > this ? this : other; + } else { + return valid__createInvalid(); + } + } + ); + + // Pick a moment m from moments so that m[fn](other) is true for all + // other. This relies on the function fn to be transitive. + // + // moments should either be an array of moment objects or an array, whose + // first element is an array of moment objects. + function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return local__createLocal(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (!moments[i].isValid() || moments[i][fn](res)) { + res = moments[i]; + } + } + return res; + } + + // TODO: Use [].sort instead? + function min () { + var args = [].slice.call(arguments, 0); + + return pickBy('isBefore', args); + } + + function max () { + var args = [].slice.call(arguments, 0); + + return pickBy('isAfter', args); + } + + var now = function () { + return Date.now ? Date.now() : +(new Date()); + }; + + function Duration (duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; + + // representation for dateAddRemove + this._milliseconds = +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + + weeks * 7; + // It is impossible translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + + quarters * 3 + + years * 12; + + this._data = {}; + + this._locale = locale_locales__getLocale(); + + this._bubble(); + } + + function isDuration (obj) { + return obj instanceof Duration; + } + + function absRound (number) { + if (number < 0) { + return Math.round(-1 * number) * -1; + } else { + return Math.round(number); + } + } + + // FORMATTING + + function offset (token, separator) { + addFormatToken(token, 0, 0, function () { + var offset = this.utcOffset(); + var sign = '+'; + if (offset < 0) { + offset = -offset; + sign = '-'; + } + return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); + }); + } + + offset('Z', ':'); + offset('ZZ', ''); + + // PARSING + + addRegexToken('Z', matchShortOffset); + addRegexToken('ZZ', matchShortOffset); + addParseToken(['Z', 'ZZ'], function (input, array, config) { + config._useUTC = true; + config._tzm = offsetFromString(matchShortOffset, input); + }); + + // HELPERS + + // timezone chunker + // '+10:00' > ['10', '00'] + // '-1530' > ['-15', '30'] + var chunkOffset = /([\+\-]|\d\d)/gi; + + function offsetFromString(matcher, string) { + var matches = ((string || '').match(matcher) || []); + var chunk = matches[matches.length - 1] || []; + var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; + var minutes = +(parts[1] * 60) + toInt(parts[2]); + + return parts[0] === '+' ? minutes : -minutes; + } + + // Return a moment from input, that is local/utc/zone equivalent to model. + function cloneWithOffset(input, model) { + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = (isMoment(input) || isDate(input) ? input.valueOf() : local__createLocal(input).valueOf()) - res.valueOf(); + // Use low-level api, because this fn is low-level api. + res._d.setTime(res._d.valueOf() + diff); + utils_hooks__hooks.updateOffset(res, false); + return res; + } else { + return local__createLocal(input).local(); + } + } + + function getDateOffset (m) { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(m._d.getTimezoneOffset() / 15) * 15; + } + + // HOOKS + + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + utils_hooks__hooks.updateOffset = function () {}; + + // MOMENTS + + // keepLocalTime = true means only change the timezone, without + // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> + // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset + // +0200, so we adjust the time as needed, to be valid. + // + // Keeping the time actually adds/subtracts (one hour) + // from the actual represented time. That is why we call updateOffset + // a second time. In case it wants us to change the offset again + // _changeInProgress == true case, then we have to adjust, because + // there is no such time in the given timezone. + function getSetOffset (input, keepLocalTime) { + var offset = this._offset || 0, + localAdjust; + if (!this.isValid()) { + return input != null ? this : NaN; + } + if (input != null) { + if (typeof input === 'string') { + input = offsetFromString(matchShortOffset, input); + } else if (Math.abs(input) < 16) { + input = input * 60; + } + if (!this._isUTC && keepLocalTime) { + localAdjust = getDateOffset(this); + } + this._offset = input; + this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, 'm'); + } + if (offset !== input) { + if (!keepLocalTime || this._changeInProgress) { + add_subtract__addSubtract(this, create__createDuration(input - offset, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + utils_hooks__hooks.updateOffset(this, true); + this._changeInProgress = null; + } + } + return this; + } else { + return this._isUTC ? offset : getDateOffset(this); + } + } + + function getSetZone (input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; + } + + this.utcOffset(input, keepLocalTime); + + return this; + } else { + return -this.utcOffset(); + } + } + + function setOffsetToUTC (keepLocalTime) { + return this.utcOffset(0, keepLocalTime); + } + + function setOffsetToLocal (keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; + + if (keepLocalTime) { + this.subtract(getDateOffset(this), 'm'); + } + } + return this; + } + + function setOffsetToParsedOffset () { + if (this._tzm) { + this.utcOffset(this._tzm); + } else if (typeof this._i === 'string') { + var tZone = offsetFromString(matchOffset, this._i); + + if (tZone === 0) { + this.utcOffset(0, true); + } else { + this.utcOffset(offsetFromString(matchOffset, this._i)); + } + } + return this; + } + + function hasAlignedHourOffset (input) { + if (!this.isValid()) { + return false; + } + input = input ? local__createLocal(input).utcOffset() : 0; + + return (this.utcOffset() - input) % 60 === 0; + } + + function isDaylightSavingTime () { + return ( + this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset() + ); + } + + function isDaylightSavingTimeShifted () { + if (!isUndefined(this._isDSTShifted)) { + return this._isDSTShifted; + } + + var c = {}; + + copyConfig(c, this); + c = prepareConfig(c); + + if (c._a) { + var other = c._isUTC ? create_utc__createUTC(c._a) : local__createLocal(c._a); + this._isDSTShifted = this.isValid() && + compareArrays(c._a, other.toArray()) > 0; + } else { + this._isDSTShifted = false; + } + + return this._isDSTShifted; + } + + function isLocal () { + return this.isValid() ? !this._isUTC : false; + } + + function isUtcOffset () { + return this.isValid() ? this._isUTC : false; + } + + function isUtc () { + return this.isValid() ? this._isUTC && this._offset === 0 : false; + } + + // ASP.NET json date format regex + var aspNetRegex = /^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/; + + // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html + // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere + // and further modified to allow for strings containing both week and day + var isoRegex = /^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/; + + function create__createDuration (input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + diffRes; + + if (isDuration(input)) { + duration = { + ms : input._milliseconds, + d : input._days, + M : input._months + }; + } else if (typeof input === 'number') { + duration = {}; + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } else if (!!(match = aspNetRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : 0, + d : toInt(match[DATE]) * sign, + h : toInt(match[HOUR]) * sign, + m : toInt(match[MINUTE]) * sign, + s : toInt(match[SECOND]) * sign, + ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match + }; + } else if (!!(match = isoRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : parseIso(match[2], sign), + M : parseIso(match[3], sign), + w : parseIso(match[4], sign), + d : parseIso(match[5], sign), + h : parseIso(match[6], sign), + m : parseIso(match[7], sign), + s : parseIso(match[8], sign) + }; + } else if (duration == null) {// checks for null or undefined + duration = {}; + } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { + diffRes = momentsDifference(local__createLocal(duration.from), local__createLocal(duration.to)); + + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; + } + + ret = new Duration(duration); + + if (isDuration(input) && hasOwnProp(input, '_locale')) { + ret._locale = input._locale; + } + + return ret; + } + + create__createDuration.fn = Duration.prototype; + + function parseIso (inp, sign) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; + } + + function positiveMomentsDifference(base, other) { + var res = {milliseconds: 0, months: 0}; + + res.months = other.month() - base.month() + + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; + } + + res.milliseconds = +other - +(base.clone().add(res.months, 'M')); + + return res; + } + + function momentsDifference(base, other) { + var res; + if (!(base.isValid() && other.isValid())) { + return {milliseconds: 0, months: 0}; + } + + other = cloneWithOffset(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; + } + + return res; + } + + // TODO: remove 'name' arg after deprecation is removed + function createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' + + 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'); + tmp = val; val = period; period = tmp; + } + + val = typeof val === 'string' ? +val : val; + dur = create__createDuration(val, period); + add_subtract__addSubtract(this, dur, direction); + return this; + }; + } + + function add_subtract__addSubtract (mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = absRound(duration._days), + months = absRound(duration._months); + + if (!mom.isValid()) { + // No op + return; + } + + updateOffset = updateOffset == null ? true : updateOffset; + + if (milliseconds) { + mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); + } + if (days) { + get_set__set(mom, 'Date', get_set__get(mom, 'Date') + days * isAdding); + } + if (months) { + setMonth(mom, get_set__get(mom, 'Month') + months * isAdding); + } + if (updateOffset) { + utils_hooks__hooks.updateOffset(mom, days || months); + } + } + + var add_subtract__add = createAdder(1, 'add'); + var add_subtract__subtract = createAdder(-1, 'subtract'); + + function getCalendarFormat(myMoment, now) { + var diff = myMoment.diff(now, 'days', true); + return diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; + } + + function moment_calendar__calendar (time, formats) { + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're local/utc/offset or not. + var now = time || local__createLocal(), + sod = cloneWithOffset(now, this).startOf('day'), + format = utils_hooks__hooks.calendarFormat(this, sod) || 'sameElse'; + + var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]); + + return this.format(output || this.localeData().calendar(format, this, local__createLocal(now))); + } + + function clone () { + return new Moment(this); + } + + function isAfter (input, units) { + var localInput = isMoment(input) ? input : local__createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() > localInput.valueOf(); + } else { + return localInput.valueOf() < this.clone().startOf(units).valueOf(); + } + } + + function isBefore (input, units) { + var localInput = isMoment(input) ? input : local__createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() < localInput.valueOf(); + } else { + return this.clone().endOf(units).valueOf() < localInput.valueOf(); + } + } + + function isBetween (from, to, units, inclusivity) { + inclusivity = inclusivity || '()'; + return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) && + (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units)); + } + + function isSame (input, units) { + var localInput = isMoment(input) ? input : local__createLocal(input), + inputMs; + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units || 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() === localInput.valueOf(); + } else { + inputMs = localInput.valueOf(); + return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf(); + } + } + + function isSameOrAfter (input, units) { + return this.isSame(input, units) || this.isAfter(input,units); + } + + function isSameOrBefore (input, units) { + return this.isSame(input, units) || this.isBefore(input,units); + } + + function diff (input, units, asFloat) { + var that, + zoneDelta, + delta, output; + + if (!this.isValid()) { + return NaN; + } + + that = cloneWithOffset(input, this); + + if (!that.isValid()) { + return NaN; + } + + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; + + units = normalizeUnits(units); + + if (units === 'year' || units === 'month' || units === 'quarter') { + output = monthDiff(this, that); + if (units === 'quarter') { + output = output / 3; + } else if (units === 'year') { + output = output / 12; + } + } else { + delta = this - that; + output = units === 'second' ? delta / 1e3 : // 1000 + units === 'minute' ? delta / 6e4 : // 1000 * 60 + units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60 + units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst + units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst + delta; + } + return asFloat ? output : absFloor(output); + } + + function monthDiff (a, b) { + // difference in months + var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), + // b is in (anchor - 1 month, anchor + 1 month) + anchor = a.clone().add(wholeMonthDiff, 'months'), + anchor2, adjust; + + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor2 - anchor); + } + + //check for negative zero, return zero if negative zero + return -(wholeMonthDiff + adjust) || 0; + } + + utils_hooks__hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; + utils_hooks__hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; + + function toString () { + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); + } + + function moment_format__toISOString () { + var m = this.clone().utc(); + if (0 < m.year() && m.year() <= 9999) { + if (isFunction(Date.prototype.toISOString)) { + // native implementation is ~50x faster, use it when we can + return this.toDate().toISOString(); + } else { + return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } + } else { + return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } + } + + function format (inputString) { + if (!inputString) { + inputString = this.isUtc() ? utils_hooks__hooks.defaultFormatUtc : utils_hooks__hooks.defaultFormat; + } + var output = formatMoment(this, inputString); + return this.localeData().postformat(output); + } + + function from (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + local__createLocal(time).isValid())) { + return create__createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + + function fromNow (withoutSuffix) { + return this.from(local__createLocal(), withoutSuffix); + } + + function to (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + local__createLocal(time).isValid())) { + return create__createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + + function toNow (withoutSuffix) { + return this.to(local__createLocal(), withoutSuffix); + } + + // If passed a locale key, it will set the locale for this + // instance. Otherwise, it will return the locale configuration + // variables for this instance. + function locale (key) { + var newLocaleData; + + if (key === undefined) { + return this._locale._abbr; + } else { + newLocaleData = locale_locales__getLocale(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } + return this; + } + } + + var lang = deprecate( + 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + return this.locale(key); + } + } + ); + + function localeData () { + return this._locale; + } + + function startOf (units) { + units = normalizeUnits(units); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'quarter': + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'isoWeek': + case 'day': + case 'date': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + } + + // weeks are a special case + if (units === 'week') { + this.weekday(0); + } + if (units === 'isoWeek') { + this.isoWeekday(1); + } + + // quarters are also special + if (units === 'quarter') { + this.month(Math.floor(this.month() / 3) * 3); + } + + return this; + } + + function endOf (units) { + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond') { + return this; + } + + // 'date' is an alias for 'day', so it should be considered as such. + if (units === 'date') { + units = 'day'; + } + + return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); + } + + function to_type__valueOf () { + return this._d.valueOf() - ((this._offset || 0) * 60000); + } + + function unix () { + return Math.floor(this.valueOf() / 1000); + } + + function toDate () { + return new Date(this.valueOf()); + } + + function toArray () { + var m = this; + return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; + } + + function toObject () { + var m = this; + return { + years: m.year(), + months: m.month(), + date: m.date(), + hours: m.hours(), + minutes: m.minutes(), + seconds: m.seconds(), + milliseconds: m.milliseconds() + }; + } + + function toJSON () { + // new Date(NaN).toJSON() === null + return this.isValid() ? this.toISOString() : null; + } + + function moment_valid__isValid () { + return valid__isValid(this); + } + + function parsingFlags () { + return extend({}, getParsingFlags(this)); + } + + function invalidAt () { + return getParsingFlags(this).overflow; + } + + function creationData() { + return { + input: this._i, + format: this._f, + locale: this._locale, + isUTC: this._isUTC, + strict: this._strict + }; + } + + // FORMATTING + + addFormatToken(0, ['gg', 2], 0, function () { + return this.weekYear() % 100; + }); + + addFormatToken(0, ['GG', 2], 0, function () { + return this.isoWeekYear() % 100; + }); + + function addWeekYearFormatToken (token, getter) { + addFormatToken(0, [token, token.length], 0, getter); + } + + addWeekYearFormatToken('gggg', 'weekYear'); + addWeekYearFormatToken('ggggg', 'weekYear'); + addWeekYearFormatToken('GGGG', 'isoWeekYear'); + addWeekYearFormatToken('GGGGG', 'isoWeekYear'); + + // ALIASES + + addUnitAlias('weekYear', 'gg'); + addUnitAlias('isoWeekYear', 'GG'); + + // PRIORITY + + addUnitPriority('weekYear', 1); + addUnitPriority('isoWeekYear', 1); + + + // PARSING + + addRegexToken('G', matchSigned); + addRegexToken('g', matchSigned); + addRegexToken('GG', match1to2, match2); + addRegexToken('gg', match1to2, match2); + addRegexToken('GGGG', match1to4, match4); + addRegexToken('gggg', match1to4, match4); + addRegexToken('GGGGG', match1to6, match6); + addRegexToken('ggggg', match1to6, match6); + + addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { + week[token.substr(0, 2)] = toInt(input); + }); + + addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { + week[token] = utils_hooks__hooks.parseTwoDigitYear(input); + }); + + // MOMENTS + + function getSetWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, + this.week(), + this.weekday(), + this.localeData()._week.dow, + this.localeData()._week.doy); + } + + function getSetISOWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, this.isoWeek(), this.isoWeekday(), 1, 4); + } + + function getISOWeeksInYear () { + return weeksInYear(this.year(), 1, 4); + } + + function getWeeksInYear () { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); + } + + function getSetWeekYearHelper(input, week, weekday, dow, doy) { + var weeksTarget; + if (input == null) { + return weekOfYear(this, dow, doy).year; + } else { + weeksTarget = weeksInYear(input, dow, doy); + if (week > weeksTarget) { + week = weeksTarget; + } + return setWeekAll.call(this, input, week, weekday, dow, doy); + } + } + + function setWeekAll(weekYear, week, weekday, dow, doy) { + var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), + date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); + + this.year(date.getUTCFullYear()); + this.month(date.getUTCMonth()); + this.date(date.getUTCDate()); + return this; + } + + // FORMATTING + + addFormatToken('Q', 0, 'Qo', 'quarter'); + + // ALIASES + + addUnitAlias('quarter', 'Q'); + + // PRIORITY + + addUnitPriority('quarter', 7); + + // PARSING + + addRegexToken('Q', match1); + addParseToken('Q', function (input, array) { + array[MONTH] = (toInt(input) - 1) * 3; + }); + + // MOMENTS + + function getSetQuarter (input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); + } + + // FORMATTING + + addFormatToken('D', ['DD', 2], 'Do', 'date'); + + // ALIASES + + addUnitAlias('date', 'D'); + + // PRIOROITY + addUnitPriority('date', 9); + + // PARSING + + addRegexToken('D', match1to2); + addRegexToken('DD', match1to2, match2); + addRegexToken('Do', function (isStrict, locale) { + return isStrict ? locale._ordinalParse : locale._ordinalParseLenient; + }); + + addParseToken(['D', 'DD'], DATE); + addParseToken('Do', function (input, array) { + array[DATE] = toInt(input.match(match1to2)[0], 10); + }); + + // MOMENTS + + var getSetDayOfMonth = makeGetSet('Date', true); + + // FORMATTING + + addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); + + // ALIASES + + addUnitAlias('dayOfYear', 'DDD'); + + // PRIORITY + addUnitPriority('dayOfYear', 4); + + // PARSING + + addRegexToken('DDD', match1to3); + addRegexToken('DDDD', match3); + addParseToken(['DDD', 'DDDD'], function (input, array, config) { + config._dayOfYear = toInt(input); + }); + + // HELPERS + + // MOMENTS + + function getSetDayOfYear (input) { + var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); + } + + // FORMATTING + + addFormatToken('m', ['mm', 2], 0, 'minute'); + + // ALIASES + + addUnitAlias('minute', 'm'); + + // PRIORITY + + addUnitPriority('minute', 14); + + // PARSING + + addRegexToken('m', match1to2); + addRegexToken('mm', match1to2, match2); + addParseToken(['m', 'mm'], MINUTE); + + // MOMENTS + + var getSetMinute = makeGetSet('Minutes', false); + + // FORMATTING + + addFormatToken('s', ['ss', 2], 0, 'second'); + + // ALIASES + + addUnitAlias('second', 's'); + + // PRIORITY + + addUnitPriority('second', 15); + + // PARSING + + addRegexToken('s', match1to2); + addRegexToken('ss', match1to2, match2); + addParseToken(['s', 'ss'], SECOND); + + // MOMENTS + + var getSetSecond = makeGetSet('Seconds', false); + + // FORMATTING + + addFormatToken('S', 0, 0, function () { + return ~~(this.millisecond() / 100); + }); + + addFormatToken(0, ['SS', 2], 0, function () { + return ~~(this.millisecond() / 10); + }); + + addFormatToken(0, ['SSS', 3], 0, 'millisecond'); + addFormatToken(0, ['SSSS', 4], 0, function () { + return this.millisecond() * 10; + }); + addFormatToken(0, ['SSSSS', 5], 0, function () { + return this.millisecond() * 100; + }); + addFormatToken(0, ['SSSSSS', 6], 0, function () { + return this.millisecond() * 1000; + }); + addFormatToken(0, ['SSSSSSS', 7], 0, function () { + return this.millisecond() * 10000; + }); + addFormatToken(0, ['SSSSSSSS', 8], 0, function () { + return this.millisecond() * 100000; + }); + addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { + return this.millisecond() * 1000000; + }); + + + // ALIASES + + addUnitAlias('millisecond', 'ms'); + + // PRIORITY + + addUnitPriority('millisecond', 16); + + // PARSING + + addRegexToken('S', match1to3, match1); + addRegexToken('SS', match1to3, match2); + addRegexToken('SSS', match1to3, match3); + + var token; + for (token = 'SSSS'; token.length <= 9; token += 'S') { + addRegexToken(token, matchUnsigned); + } + + function parseMs(input, array) { + array[MILLISECOND] = toInt(('0.' + input) * 1000); + } + + for (token = 'S'; token.length <= 9; token += 'S') { + addParseToken(token, parseMs); + } + // MOMENTS + + var getSetMillisecond = makeGetSet('Milliseconds', false); + + // FORMATTING + + addFormatToken('z', 0, 0, 'zoneAbbr'); + addFormatToken('zz', 0, 0, 'zoneName'); + + // MOMENTS + + function getZoneAbbr () { + return this._isUTC ? 'UTC' : ''; + } + + function getZoneName () { + return this._isUTC ? 'Coordinated Universal Time' : ''; + } + + var momentPrototype__proto = Moment.prototype; + + momentPrototype__proto.add = add_subtract__add; + momentPrototype__proto.calendar = moment_calendar__calendar; + momentPrototype__proto.clone = clone; + momentPrototype__proto.diff = diff; + momentPrototype__proto.endOf = endOf; + momentPrototype__proto.format = format; + momentPrototype__proto.from = from; + momentPrototype__proto.fromNow = fromNow; + momentPrototype__proto.to = to; + momentPrototype__proto.toNow = toNow; + momentPrototype__proto.get = stringGet; + momentPrototype__proto.invalidAt = invalidAt; + momentPrototype__proto.isAfter = isAfter; + momentPrototype__proto.isBefore = isBefore; + momentPrototype__proto.isBetween = isBetween; + momentPrototype__proto.isSame = isSame; + momentPrototype__proto.isSameOrAfter = isSameOrAfter; + momentPrototype__proto.isSameOrBefore = isSameOrBefore; + momentPrototype__proto.isValid = moment_valid__isValid; + momentPrototype__proto.lang = lang; + momentPrototype__proto.locale = locale; + momentPrototype__proto.localeData = localeData; + momentPrototype__proto.max = prototypeMax; + momentPrototype__proto.min = prototypeMin; + momentPrototype__proto.parsingFlags = parsingFlags; + momentPrototype__proto.set = stringSet; + momentPrototype__proto.startOf = startOf; + momentPrototype__proto.subtract = add_subtract__subtract; + momentPrototype__proto.toArray = toArray; + momentPrototype__proto.toObject = toObject; + momentPrototype__proto.toDate = toDate; + momentPrototype__proto.toISOString = moment_format__toISOString; + momentPrototype__proto.toJSON = toJSON; + momentPrototype__proto.toString = toString; + momentPrototype__proto.unix = unix; + momentPrototype__proto.valueOf = to_type__valueOf; + momentPrototype__proto.creationData = creationData; + + // Year + momentPrototype__proto.year = getSetYear; + momentPrototype__proto.isLeapYear = getIsLeapYear; + + // Week Year + momentPrototype__proto.weekYear = getSetWeekYear; + momentPrototype__proto.isoWeekYear = getSetISOWeekYear; + + // Quarter + momentPrototype__proto.quarter = momentPrototype__proto.quarters = getSetQuarter; + + // Month + momentPrototype__proto.month = getSetMonth; + momentPrototype__proto.daysInMonth = getDaysInMonth; + + // Week + momentPrototype__proto.week = momentPrototype__proto.weeks = getSetWeek; + momentPrototype__proto.isoWeek = momentPrototype__proto.isoWeeks = getSetISOWeek; + momentPrototype__proto.weeksInYear = getWeeksInYear; + momentPrototype__proto.isoWeeksInYear = getISOWeeksInYear; + + // Day + momentPrototype__proto.date = getSetDayOfMonth; + momentPrototype__proto.day = momentPrototype__proto.days = getSetDayOfWeek; + momentPrototype__proto.weekday = getSetLocaleDayOfWeek; + momentPrototype__proto.isoWeekday = getSetISODayOfWeek; + momentPrototype__proto.dayOfYear = getSetDayOfYear; + + // Hour + momentPrototype__proto.hour = momentPrototype__proto.hours = getSetHour; + + // Minute + momentPrototype__proto.minute = momentPrototype__proto.minutes = getSetMinute; + + // Second + momentPrototype__proto.second = momentPrototype__proto.seconds = getSetSecond; + + // Millisecond + momentPrototype__proto.millisecond = momentPrototype__proto.milliseconds = getSetMillisecond; + + // Offset + momentPrototype__proto.utcOffset = getSetOffset; + momentPrototype__proto.utc = setOffsetToUTC; + momentPrototype__proto.local = setOffsetToLocal; + momentPrototype__proto.parseZone = setOffsetToParsedOffset; + momentPrototype__proto.hasAlignedHourOffset = hasAlignedHourOffset; + momentPrototype__proto.isDST = isDaylightSavingTime; + momentPrototype__proto.isLocal = isLocal; + momentPrototype__proto.isUtcOffset = isUtcOffset; + momentPrototype__proto.isUtc = isUtc; + momentPrototype__proto.isUTC = isUtc; + + // Timezone + momentPrototype__proto.zoneAbbr = getZoneAbbr; + momentPrototype__proto.zoneName = getZoneName; + + // Deprecations + momentPrototype__proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); + momentPrototype__proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); + momentPrototype__proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); + momentPrototype__proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone); + momentPrototype__proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted); + + var momentPrototype = momentPrototype__proto; + + function moment__createUnix (input) { + return local__createLocal(input * 1000); + } + + function moment__createInZone () { + return local__createLocal.apply(null, arguments).parseZone(); + } + + function preParsePostFormat (string) { + return string; + } + + var prototype__proto = Locale.prototype; + + prototype__proto.calendar = locale_calendar__calendar; + prototype__proto.longDateFormat = longDateFormat; + prototype__proto.invalidDate = invalidDate; + prototype__proto.ordinal = ordinal; + prototype__proto.preparse = preParsePostFormat; + prototype__proto.postformat = preParsePostFormat; + prototype__proto.relativeTime = relative__relativeTime; + prototype__proto.pastFuture = pastFuture; + prototype__proto.set = locale_set__set; + + // Month + prototype__proto.months = localeMonths; + prototype__proto.monthsShort = localeMonthsShort; + prototype__proto.monthsParse = localeMonthsParse; + prototype__proto.monthsRegex = monthsRegex; + prototype__proto.monthsShortRegex = monthsShortRegex; + + // Week + prototype__proto.week = localeWeek; + prototype__proto.firstDayOfYear = localeFirstDayOfYear; + prototype__proto.firstDayOfWeek = localeFirstDayOfWeek; + + // Day of Week + prototype__proto.weekdays = localeWeekdays; + prototype__proto.weekdaysMin = localeWeekdaysMin; + prototype__proto.weekdaysShort = localeWeekdaysShort; + prototype__proto.weekdaysParse = localeWeekdaysParse; + + prototype__proto.weekdaysRegex = weekdaysRegex; + prototype__proto.weekdaysShortRegex = weekdaysShortRegex; + prototype__proto.weekdaysMinRegex = weekdaysMinRegex; + + // Hours + prototype__proto.isPM = localeIsPM; + prototype__proto.meridiem = localeMeridiem; + + function lists__get (format, index, field, setter) { + var locale = locale_locales__getLocale(); + var utc = create_utc__createUTC().set(setter, index); + return locale[field](utc, format); + } + + function listMonthsImpl (format, index, field) { + if (typeof format === 'number') { + index = format; + format = undefined; + } + + format = format || ''; + + if (index != null) { + return lists__get(format, index, field, 'month'); + } + + var i; + var out = []; + for (i = 0; i < 12; i++) { + out[i] = lists__get(format, i, field, 'month'); + } + return out; + } + + // () + // (5) + // (fmt, 5) + // (fmt) + // (true) + // (true, 5) + // (true, fmt, 5) + // (true, fmt) + function listWeekdaysImpl (localeSorted, format, index, field) { + if (typeof localeSorted === 'boolean') { + if (typeof format === 'number') { + index = format; + format = undefined; + } + + format = format || ''; + } else { + format = localeSorted; + index = format; + localeSorted = false; + + if (typeof format === 'number') { + index = format; + format = undefined; + } + + format = format || ''; + } + + var locale = locale_locales__getLocale(), + shift = localeSorted ? locale._week.dow : 0; + + if (index != null) { + return lists__get(format, (index + shift) % 7, field, 'day'); + } + + var i; + var out = []; + for (i = 0; i < 7; i++) { + out[i] = lists__get(format, (i + shift) % 7, field, 'day'); + } + return out; + } + + function lists__listMonths (format, index) { + return listMonthsImpl(format, index, 'months'); + } + + function lists__listMonthsShort (format, index) { + return listMonthsImpl(format, index, 'monthsShort'); + } + + function lists__listWeekdays (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); + } + + function lists__listWeekdaysShort (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); + } + + function lists__listWeekdaysMin (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); + } + + locale_locales__getSetGlobalLocale('en', { + ordinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal : function (number) { + var b = number % 10, + output = (toInt(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); + + // Side effect imports + utils_hooks__hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', locale_locales__getSetGlobalLocale); + utils_hooks__hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', locale_locales__getLocale); + + var mathAbs = Math.abs; + + function duration_abs__abs () { + var data = this._data; + + this._milliseconds = mathAbs(this._milliseconds); + this._days = mathAbs(this._days); + this._months = mathAbs(this._months); + + data.milliseconds = mathAbs(data.milliseconds); + data.seconds = mathAbs(data.seconds); + data.minutes = mathAbs(data.minutes); + data.hours = mathAbs(data.hours); + data.months = mathAbs(data.months); + data.years = mathAbs(data.years); + + return this; + } + + function duration_add_subtract__addSubtract (duration, input, value, direction) { + var other = create__createDuration(input, value); + + duration._milliseconds += direction * other._milliseconds; + duration._days += direction * other._days; + duration._months += direction * other._months; + + return duration._bubble(); + } + + // supports only 2.0-style add(1, 's') or add(duration) + function duration_add_subtract__add (input, value) { + return duration_add_subtract__addSubtract(this, input, value, 1); + } + + // supports only 2.0-style subtract(1, 's') or subtract(duration) + function duration_add_subtract__subtract (input, value) { + return duration_add_subtract__addSubtract(this, input, value, -1); + } + + function absCeil (number) { + if (number < 0) { + return Math.floor(number); + } else { + return Math.ceil(number); + } + } + + function bubble () { + var milliseconds = this._milliseconds; + var days = this._days; + var months = this._months; + var data = this._data; + var seconds, minutes, hours, years, monthsFromDays; + + // if we have a mix of positive and negative values, bubble down first + // check: https://github.com/moment/moment/issues/2166 + if (!((milliseconds >= 0 && days >= 0 && months >= 0) || + (milliseconds <= 0 && days <= 0 && months <= 0))) { + milliseconds += absCeil(monthsToDays(months) + days) * 864e5; + days = 0; + months = 0; + } + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absFloor(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absFloor(seconds / 60); + data.minutes = minutes % 60; + + hours = absFloor(minutes / 60); + data.hours = hours % 24; + + days += absFloor(hours / 24); + + // convert days to months + monthsFromDays = absFloor(daysToMonths(days)); + months += monthsFromDays; + days -= absCeil(monthsToDays(monthsFromDays)); + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + data.days = days; + data.months = months; + data.years = years; + + return this; + } + + function daysToMonths (days) { + // 400 years have 146097 days (taking into account leap year rules) + // 400 years have 12 months === 4800 + return days * 4800 / 146097; + } + + function monthsToDays (months) { + // the reverse of daysToMonths + return months * 146097 / 4800; + } + + function as (units) { + var days; + var months; + var milliseconds = this._milliseconds; + + units = normalizeUnits(units); + + if (units === 'month' || units === 'year') { + days = this._days + milliseconds / 864e5; + months = this._months + daysToMonths(days); + return units === 'month' ? months : months / 12; + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(monthsToDays(this._months)); + switch (units) { + case 'week' : return days / 7 + milliseconds / 6048e5; + case 'day' : return days + milliseconds / 864e5; + case 'hour' : return days * 24 + milliseconds / 36e5; + case 'minute' : return days * 1440 + milliseconds / 6e4; + case 'second' : return days * 86400 + milliseconds / 1000; + // Math.floor prevents floating point math errors here + case 'millisecond': return Math.floor(days * 864e5) + milliseconds; + default: throw new Error('Unknown unit ' + units); + } + } + } + + // TODO: Use this.as('ms')? + function duration_as__valueOf () { + return ( + this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6 + ); + } + + function makeAs (alias) { + return function () { + return this.as(alias); + }; + } + + var asMilliseconds = makeAs('ms'); + var asSeconds = makeAs('s'); + var asMinutes = makeAs('m'); + var asHours = makeAs('h'); + var asDays = makeAs('d'); + var asWeeks = makeAs('w'); + var asMonths = makeAs('M'); + var asYears = makeAs('y'); + + function duration_get__get (units) { + units = normalizeUnits(units); + return this[units + 's'](); + } + + function makeGetter(name) { + return function () { + return this._data[name]; + }; + } + + var milliseconds = makeGetter('milliseconds'); + var seconds = makeGetter('seconds'); + var minutes = makeGetter('minutes'); + var hours = makeGetter('hours'); + var days = makeGetter('days'); + var months = makeGetter('months'); + var years = makeGetter('years'); + + function weeks () { + return absFloor(this.days() / 7); + } + + var round = Math.round; + var thresholds = { + s: 45, // seconds to minute + m: 45, // minutes to hour + h: 22, // hours to day + d: 26, // days to month + M: 11 // months to year + }; + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function duration_humanize__relativeTime (posNegDuration, withoutSuffix, locale) { + var duration = create__createDuration(posNegDuration).abs(); + var seconds = round(duration.as('s')); + var minutes = round(duration.as('m')); + var hours = round(duration.as('h')); + var days = round(duration.as('d')); + var months = round(duration.as('M')); + var years = round(duration.as('y')); + + var a = seconds < thresholds.s && ['s', seconds] || + minutes <= 1 && ['m'] || + minutes < thresholds.m && ['mm', minutes] || + hours <= 1 && ['h'] || + hours < thresholds.h && ['hh', hours] || + days <= 1 && ['d'] || + days < thresholds.d && ['dd', days] || + months <= 1 && ['M'] || + months < thresholds.M && ['MM', months] || + years <= 1 && ['y'] || ['yy', years]; + + a[2] = withoutSuffix; + a[3] = +posNegDuration > 0; + a[4] = locale; + return substituteTimeAgo.apply(null, a); + } + + // This function allows you to set the rounding function for relative time strings + function duration_humanize__getSetRelativeTimeRounding (roundingFunction) { + if (roundingFunction === undefined) { + return round; + } + if (typeof(roundingFunction) === 'function') { + round = roundingFunction; + return true; + } + return false; + } + + // This function allows you to set a threshold for relative time strings + function duration_humanize__getSetRelativeTimeThreshold (threshold, limit) { + if (thresholds[threshold] === undefined) { + return false; + } + if (limit === undefined) { + return thresholds[threshold]; + } + thresholds[threshold] = limit; + return true; + } + + function humanize (withSuffix) { + var locale = this.localeData(); + var output = duration_humanize__relativeTime(this, !withSuffix, locale); + + if (withSuffix) { + output = locale.pastFuture(+this, output); + } + + return locale.postformat(output); + } + + var iso_string__abs = Math.abs; + + function iso_string__toISOString() { + // for ISO strings we do not use the normal bubbling rules: + // * milliseconds bubble up until they become hours + // * days do not bubble at all + // * months bubble up until they become years + // This is because there is no context-free conversion between hours and days + // (think of clock changes) + // and also not between days and months (28-31 days per month) + var seconds = iso_string__abs(this._milliseconds) / 1000; + var days = iso_string__abs(this._days); + var months = iso_string__abs(this._months); + var minutes, hours, years; + + // 3600 seconds -> 60 minutes -> 1 hour + minutes = absFloor(seconds / 60); + hours = absFloor(minutes / 60); + seconds %= 60; + minutes %= 60; + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var Y = years; + var M = months; + var D = days; + var h = hours; + var m = minutes; + var s = seconds; + var total = this.asSeconds(); + + if (!total) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + return (total < 0 ? '-' : '') + + 'P' + + (Y ? Y + 'Y' : '') + + (M ? M + 'M' : '') + + (D ? D + 'D' : '') + + ((h || m || s) ? 'T' : '') + + (h ? h + 'H' : '') + + (m ? m + 'M' : '') + + (s ? s + 'S' : ''); + } + + var duration_prototype__proto = Duration.prototype; + + duration_prototype__proto.abs = duration_abs__abs; + duration_prototype__proto.add = duration_add_subtract__add; + duration_prototype__proto.subtract = duration_add_subtract__subtract; + duration_prototype__proto.as = as; + duration_prototype__proto.asMilliseconds = asMilliseconds; + duration_prototype__proto.asSeconds = asSeconds; + duration_prototype__proto.asMinutes = asMinutes; + duration_prototype__proto.asHours = asHours; + duration_prototype__proto.asDays = asDays; + duration_prototype__proto.asWeeks = asWeeks; + duration_prototype__proto.asMonths = asMonths; + duration_prototype__proto.asYears = asYears; + duration_prototype__proto.valueOf = duration_as__valueOf; + duration_prototype__proto._bubble = bubble; + duration_prototype__proto.get = duration_get__get; + duration_prototype__proto.milliseconds = milliseconds; + duration_prototype__proto.seconds = seconds; + duration_prototype__proto.minutes = minutes; + duration_prototype__proto.hours = hours; + duration_prototype__proto.days = days; + duration_prototype__proto.weeks = weeks; + duration_prototype__proto.months = months; + duration_prototype__proto.years = years; + duration_prototype__proto.humanize = humanize; + duration_prototype__proto.toISOString = iso_string__toISOString; + duration_prototype__proto.toString = iso_string__toISOString; + duration_prototype__proto.toJSON = iso_string__toISOString; + duration_prototype__proto.locale = locale; + duration_prototype__proto.localeData = localeData; + + // Deprecations + duration_prototype__proto.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', iso_string__toISOString); + duration_prototype__proto.lang = lang; + + // Side effect imports + + // FORMATTING + + addFormatToken('X', 0, 0, 'unix'); + addFormatToken('x', 0, 0, 'valueOf'); + + // PARSING + + addRegexToken('x', matchSigned); + addRegexToken('X', matchTimestamp); + addParseToken('X', function (input, array, config) { + config._d = new Date(parseFloat(input, 10) * 1000); + }); + addParseToken('x', function (input, array, config) { + config._d = new Date(toInt(input)); + }); + + // Side effect imports + + + utils_hooks__hooks.version = '2.15.1'; + + setHookCallback(local__createLocal); + + utils_hooks__hooks.fn = momentPrototype; + utils_hooks__hooks.min = min; + utils_hooks__hooks.max = max; + utils_hooks__hooks.now = now; + utils_hooks__hooks.utc = create_utc__createUTC; + utils_hooks__hooks.unix = moment__createUnix; + utils_hooks__hooks.months = lists__listMonths; + utils_hooks__hooks.isDate = isDate; + utils_hooks__hooks.locale = locale_locales__getSetGlobalLocale; + utils_hooks__hooks.invalid = valid__createInvalid; + utils_hooks__hooks.duration = create__createDuration; + utils_hooks__hooks.isMoment = isMoment; + utils_hooks__hooks.weekdays = lists__listWeekdays; + utils_hooks__hooks.parseZone = moment__createInZone; + utils_hooks__hooks.localeData = locale_locales__getLocale; + utils_hooks__hooks.isDuration = isDuration; + utils_hooks__hooks.monthsShort = lists__listMonthsShort; + utils_hooks__hooks.weekdaysMin = lists__listWeekdaysMin; + utils_hooks__hooks.defineLocale = defineLocale; + utils_hooks__hooks.updateLocale = updateLocale; + utils_hooks__hooks.locales = locale_locales__listLocales; + utils_hooks__hooks.weekdaysShort = lists__listWeekdaysShort; + utils_hooks__hooks.normalizeUnits = normalizeUnits; + utils_hooks__hooks.relativeTimeRounding = duration_humanize__getSetRelativeTimeRounding; + utils_hooks__hooks.relativeTimeThreshold = duration_humanize__getSetRelativeTimeThreshold; + utils_hooks__hooks.calendarFormat = getCalendarFormat; + utils_hooks__hooks.prototype = momentPrototype; + + var _moment = utils_hooks__hooks; + + return _moment; + +})); \ No newline at end of file diff --git a/members/templates/members_index.html b/members/templates/members_index.html index b86ec36..d8bcfc7 100644 --- a/members/templates/members_index.html +++ b/members/templates/members_index.html @@ -20,6 +20,7 @@ +