'),TermsNoticeDirective=function($config){var privacyPolicyUrl,templateFn,termsOfServiceUrl;return privacyPolicyUrl=$config.get("privacyPolicyUrl"),termsOfServiceUrl=$config.get("termsOfServiceUrl"),templateFn=function(){var ctx;return privacyPolicyUrl&&termsOfServiceUrl?(ctx={termsUrl:termsOfServiceUrl,privacyUrl:privacyPolicyUrl},template(ctx)):""},{scope:{},restrict:"AE",template:templateFn}},module.directive("tgTermsNotice",["$tgConfig",TermsNoticeDirective])}.call(this),function(){var module;module=angular.module("taigaPlugins",["ngRoute"])}.call(this),angular.module("taigaBase").value("localesEn",{checksley:{defaultMessage:"This value seems to be invalid.","type-email":"This value should be a valid email.","type-url":"This value should be a valid url.","type-urlstrict":"This value should be a valid url.","type-number":"This value should be a valid number.","type-digits":"This value should be digits.","type-dateIso":"This value should be a valid date (YYYY-MM-DD).","type-alphanum":"This value should be alphanumeric.","type-phone":"This value should be a valid phone number.",notnull:"This value should not be null.",notblank:"This value should not be blank.",required:"This value is required.",regexp:"This value seems to be invalid.",min:"This value should be greater than or equal to %s.",max:"This value should be lower than or equal to %s.",range:"This value should be between %s and %s.",minlength:"This value is too short. It should have %s characters or more.",maxlength:"This value is too long. It should have %s characters or less.",rangelength:"This value length is invalid. It should be between %s and %s characters long.",mincheck:"You must select at least %s choices.",maxcheck:"You must select %s choices or less.",rangecheck:"You must select between %s and %s choices.",equalto:"This value should be the same."},common:{subject:"Subject",save:"Save",blocked:"Blocked",cancel:"Cancel",status:"Status","new-bulk":"New bulk insert","one-item-line":"One item per line..."},pagination:{next:"Next",prev:"Previous"},"markdown-editor":{"heading-1":"First Level Heading","heading-2":"Second Level Heading","heading-3":"Third Level Heading",bold:"Bold",italic:"Italic",strike:"Strike","bulleted-list":"Bulleted List","numeric-list":"Numeric List",picture:"Picture",link:"Link",quotes:"Quotes","code-block":"Code Block / Code",preview:"Preview",help:"Help",placeholder:"Your title here...","link-placeholder":"Your text to link here..."},us:{"title-new":"New User Story","team-requirement":"Team Requirement","client-requirement":"Client Requirement"}});
+return $ctrl=$el.closest(".wrapper").controller(),selectedFilters=[],showFilters=function(title,type){return $el.find(".filters-cats").hide(),$el.find(".filter-list").removeClass("hidden"),$el.find("h2.breadcrumb").removeClass("hidden"),$el.find("h2 a.subfilter span.title").html(title),$el.find("h2 a.subfilter span.title").prop("data-type",type)},showCategories=function(){return $el.find(".filters-cats").show(),$el.find(".filter-list").addClass("hidden"),$el.find("h2.breadcrumb").addClass("hidden")},initializeSelectedFilters=function(filters){var name,val,values,_i,_len;showCategories(),selectedFilters=[];for(name in filters)for(values=filters[name],_i=0,_len=values.length;_len>_i;_i++)val=values[_i],val.selected&&selectedFilters.push(val);return renderSelectedFilters()},renderSelectedFilters=function(){var html;return html=templateSelected({filters:selectedFilters}),$el.find(".filters-applied").html(html)},renderFilters=function(filters){var html;return html=template({filters:filters}),$el.find(".filter-list").html(html)},toggleFilterSelection=function(type,id){var currentFiltersType,filter,filters;return filters=$scope.filters[type],filter=_.find(filters,{id:taiga.toString(id)}),filter.selected=!filter.selected,filter.selected?(selectedFilters.push(filter),$scope.$apply(function(){return $ctrl.selectFilter(type,id),$ctrl.filterVisibleUserstories()})):(selectedFilters=_.reject(selectedFilters,filter),$scope.$apply(function(){return $ctrl.unselectFilter(type,id),$ctrl.filterVisibleUserstories()})),renderSelectedFilters(selectedFilters),currentFiltersType=$el.find("h2 a.subfilter span.title").prop("data-type"),type===currentFiltersType&&renderFilters(_.reject(filters,"selected")),$ctrl.loadUserstories()},selectQFilter=debounceLeading(100,function(value){return void 0!==value?(0===value.length?$ctrl.replaceFilter("q",null):$ctrl.replaceFilter("q",value),$ctrl.loadUserstories()):void 0}),$scope.$watch("filtersQ",selectQFilter),$scope.$on("filters:loaded",function(ctx,filters){return initializeSelectedFilters(filters)}),$el.on("click",".filters-cats > ul > li > a",function(event){var tags,target;return event.preventDefault(),target=angular.element(event.currentTarget),tags=$scope.filters[target.data("type")],renderFilters(_.reject(tags,"selected")),showFilters(target.attr("title"),target.data("type"))}),$el.on("click",".filters-inner > .filters-step-cat > .breadcrumb > .back",function(event){return event.preventDefault(),showCategories()}),$el.on("click",".filters-applied a",function(event){var id,target,type;return event.preventDefault(),target=angular.element(event.currentTarget),id=target.data("id"),type=target.data("type"),toggleFilterSelection(type,id)}),$el.on("click",".filter-list .single-filter",function(event){var id,target,type;return event.preventDefault(),target=angular.element(event.currentTarget),target.hasClass("active")?target.removeClass("active"):target.addClass("active"),id=target.data("id"),type=target.data("type"),toggleFilterSelection(type,id)})},{link:link}},module.directive("tgBacklogFilters",["$log","$tgLocation",BacklogFiltersDirective])}.call(this),function(){var CreateEditSprint,bindOnce,debounce,module,taiga;taiga=this.taiga,bindOnce=this.taiga.bindOnce,debounce=this.taiga.debounce,module=angular.module("taigaBacklog"),CreateEditSprint=function($repo,$confirm,$rs,$rootscope,lightboxService,$loading){var link;return link=function($scope,$el){var createSprint,hasErrors,remove,submit,submitButton;return hasErrors=!1,createSprint=!0,$scope.sprint={project:null,name:null,estimated_start:null,estimated_finish:null},submit=debounce(2e3,function(){return function(event){var broadcastEvent,form,newSprint,promise,target;return event.preventDefault(),target=angular.element(event.currentTarget),form=$el.find("form").checksley(),form.validate()?(hasErrors=!1,newSprint=angular.copy($scope.sprint),broadcastEvent=null,createSprint?(newSprint.estimated_start=moment(newSprint.estimated_start).format("YYYY-MM-DD"),newSprint.estimated_finish=moment(newSprint.estimated_finish).format("YYYY-MM-DD"),promise=$repo.create("milestones",newSprint),broadcastEvent="sprintform:create:success"):(newSprint.setAttr("estimated_start",moment(newSprint.estimated_start).format("YYYY-MM-DD")),newSprint.setAttr("estimated_finish",moment(newSprint.estimated_finish).format("YYYY-MM-DD")),promise=$repo.save(newSprint),broadcastEvent="sprintform:edit:success"),$loading.start(submitButton),promise.then(function(data){return $loading.finish(submitButton),createSprint&&($scope.sprintsCounter+=1),$rootscope.$broadcast(broadcastEvent,data),lightboxService.close($el)}),promise.then(null,function(data){return $loading.finish(submitButton),form.setErrors(data),data._error_message?$confirm.notify("light-error",data._error_message):data.__all__?$confirm.notify("light-error",data.__all__[0]):void 0})):(hasErrors=!0,void $el.find(".last-sprint-name").addClass("disappear"))}}(this)),remove=function(){var message,title;return title="Delete sprint",message=$scope.sprint.name,$confirm.askOnDelete(title,message).then(function(){return function(finish){var onError,onSuccess;return onSuccess=function(){return finish(),$scope.milestonesCounter-=1,lightboxService.close($el),$rootscope.$broadcast("sprintform:remove:success")},onError=function(){return finish(!1),$confirm.notify("error")},$repo.remove($scope.sprint).then(onSuccess,onError)}}(this))},$scope.$on("sprintform:create",function(event,projectId){var estimatedFinish,estimatedStart,lastSprint,lastSprintNameDom;return createSprint=!0,$scope.sprint.project=projectId,$scope.sprint.name=null,$scope.sprint.slug=null,lastSprint=$scope.sprints[0],estimatedStart=moment(),$scope.sprint.estimated_start?estimatedStart=moment($scope.sprint.estimated_start):null!=lastSprint&&(estimatedStart=moment(lastSprint.estimated_finish)),$scope.sprint.estimated_start=estimatedStart.format("DD MMM YYYY"),estimatedFinish=moment().add(2,"weeks"),$scope.sprint.estimated_finish?estimatedFinish=moment($scope.sprint.estimated_finish):null!=lastSprint&&(estimatedFinish=moment(lastSprint.estimated_finish).add(2,"weeks")),$scope.sprint.estimated_finish=estimatedFinish.format("DD MMM YYYY"),lastSprintNameDom=$el.find(".last-sprint-name"),null!=(null!=lastSprint?lastSprint.name:void 0)&&lastSprintNameDom.html(" last sprint is "+lastSprint.name+" ;-) "),$el.find(".delete-sprint").addClass("hidden"),$el.find(".title").text("New sprint"),$el.find(".button-green").text("Create"),lightboxService.open($el),$el.find(".sprint-name").focus()}),$scope.$on("sprintform:edit",function(ctx,sprint){return createSprint=!1,$scope.$apply(function(){return $scope.sprint=sprint,$scope.sprint.estimated_start=moment($scope.sprint.estimated_start).format("DD MMM YYYY"),$scope.sprint.estimated_finish=moment($scope.sprint.estimated_finish).format("DD MMM YYYY")}),$el.find(".delete-sprint").removeClass("hidden"),$el.find(".title").text("Edit sprint"),$el.find(".button-green").text("Save"),lightboxService.open($el),$el.find(".sprint-name").focus().select(),$el.find(".last-sprint-name").addClass("disappear")}),$el.on("keyup",".sprint-name",function(){return $el.find(".sprint-name").val().length>0||hasErrors?$el.find(".last-sprint-name").addClass("disappear"):$el.find(".last-sprint-name").removeClass("disappear")}),submitButton=$el.find(".submit-button"),$el.on("submit","form",submit),$el.on("click",".submit-button",submit),$el.on("click",".delete-sprint .icon-delete",function(event){return event.preventDefault(),remove()}),$scope.$on("$destroy",function(){return $el.off()})},{link:link}},module.directive("tgLbCreateEditSprint",["$tgRepo","$tgConfirm","$tgResources","$rootScope","lightboxService","$tgLoading",CreateEditSprint])}.call(this),function(){var BacklogController,BacklogDirective,TgBacklogProgressBarDirective,UsPointsDirective,UsRolePointsSelectorDirective,bindMethods,bindOnce,groupBy,mixOf,module,scopeDefer,taiga,tgBacklogGraphDirective,timeout,toggleText,__hasProp={}.hasOwnProperty,__extends=function(child,parent){function ctor(){this.constructor=child}for(var key in parent)__hasProp.call(parent,key)&&(child[key]=parent[key]);return ctor.prototype=parent.prototype,child.prototype=new ctor,child.__super__=parent.prototype,child};taiga=this.taiga,mixOf=this.taiga.mixOf,toggleText=this.taiga.toggleText,scopeDefer=this.taiga.scopeDefer,bindOnce=this.taiga.bindOnce,groupBy=this.taiga.groupBy,timeout=this.taiga.timeout,bindMethods=this.taiga.bindMethods,module=angular.module("taigaBacklog"),BacklogController=function(_super){function BacklogController(scope,rootscope,repo,confirm,rs,params,q,location,appTitle,navUrls,events,analytics,tgLoader){var promise;this.scope=scope,this.rootscope=rootscope,this.repo=repo,this.confirm=confirm,this.rs=rs,this.params=params,this.q=q,this.location=location,this.appTitle=appTitle,this.navUrls=navUrls,this.events=events,this.analytics=analytics,bindMethods(this),this.scope.sectionName="Backlog",this.showTags=!1,this.activeFilters=!1,this.excludeClosedSprints=!0,this.initializeEventHandlers(),promise=this.loadInitialData(),promise.then(function(_this){return function(){return _this.appTitle.set("Backlog - "+_this.scope.project.name),_this.rs.userstories.getShowTags(_this.scope.projectId)?(_this.showTags=!0,_this.scope.$broadcast("showTags",_this.showTags)):void 0}}(this)),promise.then(null,this.onInitialDataError.bind(this)),promise["finally"](tgLoader.pageLoaded)}return __extends(BacklogController,_super),BacklogController.$inject=["$scope","$rootScope","$tgRepo","$tgConfirm","$tgResources","$routeParams","$q","$tgLocation","$appTitle","$tgNavUrls","$tgEvents","$tgAnalytics","tgLoader"],BacklogController.prototype.initializeEventHandlers=function(){return this.scope.$on("usform:bulk:success",function(_this){return function(){return _this.loadUserstories(),_this.loadProjectStats(),_this.analytics.trackEvent("userstory","create","bulk create userstory on backlog",1)}}(this)),this.scope.$on("sprintform:create:success",function(_this){return function(){return _this.loadSprints(),_this.loadProjectStats(),_this.analytics.trackEvent("sprint","create","create sprint on backlog",1)}}(this)),this.scope.$on("usform:new:success",function(_this){return function(){return _this.loadUserstories(),_this.loadProjectStats(),_this.analytics.trackEvent("userstory","create","create userstory on backlog",1)}}(this)),this.scope.$on("sprintform:edit:success",function(_this){return function(){return _this.loadProjectStats()}}(this)),this.scope.$on("sprintform:remove:success",function(_this){return function(){return _this.loadSprints(),_this.loadProjectStats(),_this.loadUserstories()}}(this)),this.scope.$on("usform:edit:success",function(_this){return function(){return _this.loadUserstories()}}(this)),this.scope.$on("sprint:us:move",this.moveUs),this.scope.$on("sprint:us:moved",this.loadSprints),this.scope.$on("sprint:us:moved",this.loadProjectStats),this.scope.$on("backlog:toggle-closed-sprints-visualization",this.toggleClosedSprintsVisualization)},BacklogController.prototype.initializeSubscription=function(){var routingKey1,routingKey2;return routingKey1="changes.project."+this.scope.projectId+".userstories",this.events.subscribe(this.scope,routingKey1,function(_this){return function(){return _this.loadUserstories(),_this.loadSprints()}}(this)),routingKey2="changes.project."+this.scope.projectId+".milestones",this.events.subscribe(this.scope,routingKey2,function(_this){return function(){return _this.loadSprints()}}(this))},BacklogController.prototype.toggleShowTags=function(){return this.scope.$apply(function(_this){return function(){return _this.showTags=!_this.showTags,_this.rs.userstories.storeShowTags(_this.scope.projectId,_this.showTags)}}(this))},BacklogController.prototype.toggleActiveFilters=function(){return this.activeFilters=!this.activeFilters},BacklogController.prototype.loadProjectStats=function(){return this.rs.projects.stats(this.scope.projectId).then(function(_this){return function(stats){return _this.scope.stats=stats,_this.scope.stats.completedPercentage=stats.total_points?Math.round(100*stats.closed_points/stats.total_points):0,stats}}(this))},BacklogController.prototype.refreshTagsColors=function(){return this.rs.projects.tagsColors(this.scope.projectId).then(function(_this){return function(tags_colors){return _this.scope.project.tags_colors=tags_colors}}(this))},BacklogController.prototype.loadSprints=function(){var params;return params={},this.excludeClosedSprints&&(params.closed=!1),this.rs.sprints.list(this.scope.projectId,params).then(function(_this){return function(sprints){var sprint,_i,_len;for(_i=0,_len=sprints.length;_len>_i;_i++)sprint=sprints[_i],sprint.user_stories=_.sortBy(sprint.user_stories,"sprint_order");return _this.scope.sprints=sprints,_this.scope.openSprints=_.filter(sprints,function(sprint){return!sprint.closed}).reverse(),_this.scope.closedSprints=_.filter(sprints,function(sprint){return sprint.closed}),_this.excludeClosedSprints||(_this.scope.totalClosedMilestones=_this.scope.closedSprints.length),_this.scope.sprintsCounter=sprints.length,_this.scope.sprintsById=groupBy(sprints,function(x){return x.id}),_this.rootscope.$broadcast("sprints:loaded",sprints),sprints}}(this))},BacklogController.prototype.resetFilters=function(){var selectedStatuses,selectedTags;return selectedTags=_.filter(this.scope.filters.tags,"selected"),selectedStatuses=_.filter(this.scope.filters.statuses,"selected"),this.scope.filtersQ="",_.each([selectedTags,selectedStatuses],function(_this){return function(filterGrp){return _.each(filterGrp,function(item){var filter,filters;return filters=_this.scope.filters[item.type],filter=_.find(filters,{id:taiga.toString(item.id)}),filter.selected=!1,_this.unselectFilter(item.type,item.id)})}}(this)),this.loadUserstories()},BacklogController.prototype.loadUserstories=function(){var promise;return this.scope.httpParams=this.getUrlFilters(),this.rs.userstories.storeQueryParams(this.scope.projectId,this.scope.httpParams),promise=this.q.all([this.refreshTagsColors(),this.rs.userstories.listUnassigned(this.scope.projectId,this.scope.httpParams)]),promise.then(function(_this){return function(data){var userstories;return userstories=data[1],_this.scope.userstories=_.sortBy(userstories,"backlog_order"),_this.generateFilters(),_this.filterVisibleUserstories(),_this.rootscope.$broadcast("filters:loaded",_this.scope.filters),scopeDefer(_this.scope,function(){return _this.scope.$broadcast("userstories:loaded")}),userstories}}(this))},BacklogController.prototype.loadBacklog=function(){return this.q.all([this.loadProjectStats(),this.loadSprints(),this.loadUserstories()])},BacklogController.prototype.loadProject=function(){return this.rs.projects.getBySlug(this.params.pslug).then(function(_this){return function(project){return _this.scope.projectId=project.id,_this.scope.project=project,_this.scope.totalClosedMilestones=project.total_closed_milestones,_this.scope.$emit("project:loaded",project),_this.scope.points=_.sortBy(project.points,"order"),_this.scope.pointsById=groupBy(project.points,function(x){return x.id}),_this.scope.usStatusById=groupBy(project.us_statuses,function(x){return x.id}),_this.scope.usStatusList=_.sortBy(project.us_statuses,"id"),project}}(this))},BacklogController.prototype.loadInitialData=function(){var promise;return promise=this.loadProject(),promise.then(function(_this){return function(project){return _this.fillUsersAndRoles(project.users,project.roles),_this.initializeSubscription()}}(this)),promise.then(function(_this){return function(){return _this.loadBacklog()}}(this))},BacklogController.prototype.toggleClosedSprintsVisualization=function(){return this.excludeClosedSprints=!this.excludeClosedSprints,this.loadSprints()},BacklogController.prototype.filterVisibleUserstories=function(){var selectedStatuses,selectedTags;return this.scope.visibleUserstories=[],selectedTags=_.filter(this.scope.filters.tags,"selected"),selectedTags=_.map(selectedTags,"name"),this.scope.visibleUserstories=0===selectedTags.length?_.clone(this.scope.userstories,!1):_.reject(this.scope.userstories,function(){return function(us){return 0===_.intersection(selectedTags,us.tags).length?!0:!1}}(this)),selectedStatuses=_.filter(this.scope.filters.statuses,"selected"),selectedStatuses=_.map(selectedStatuses,"id"),selectedStatuses.length>0&&(this.scope.visibleUserstories=_.reject(this.scope.visibleUserstories,function(){return function(us){var res;return res=_.find(selectedStatuses,function(x){return x===taiga.toString(us.status)}),!res}}(this))),this.rs.userstories.storeQueryParams(this.scope.projectId,{status:selectedStatuses,tags:selectedTags,project:this.scope.projectId,milestone:null})},BacklogController.prototype.prepareBulkUpdateData=function(uses,field){return null==field&&(field="backlog_order"),_.map(uses,function(x){return{us_id:x.id,order:x[field]}})},BacklogController.prototype.resortUserStories=function(uses,field){var index,item,items,_i,_len;for(null==field&&(field="backlog_order"),items=[],index=_i=0,_len=uses.length;_len>_i;index=++_i)item=uses[index],item[field]=index,item.isModified()&&items.push(item);return items},BacklogController.prototype.moveUs=function(ctx,usList,newUsIndex,newSprintId){var data,items,newSprint,oldSprintId,project,promise,promises,us,userstories,_i,_j,_k,_len,_len1,_len2;if(oldSprintId=usList[0].milestone,project=usList[0].project,newSprintId===oldSprintId)return items=null,userstories=null,userstories=null===newSprintId?this.scope.userstories:this.scope.sprintsById[newSprintId].user_stories,this.scope.$apply(function(){var args,key,r,us,_i,_len;for(key=_i=0,_len=usList.length;_len>_i;key=++_i)us=usList[key],r=userstories.indexOf(us),userstories.splice(r,1);return args=[newUsIndex,0].concat(usList),Array.prototype.splice.apply(userstories,args)}),null===newSprintId?(items=this.resortUserStories(userstories,"backlog_order"),data=this.prepareBulkUpdateData(items,"backlog_order"),this.rs.userstories.bulkUpdateBacklogOrder(project,data).then(function(_this){return function(){var us,_i,_len,_results;for(_results=[],_i=0,_len=usList.length;_len>_i;_i++)us=usList[_i],_results.push(_this.rootscope.$broadcast("sprint:us:moved",us,oldSprintId,newSprintId));return _results}}(this))):(items=this.resortUserStories(userstories,"sprint_order"),data=this.prepareBulkUpdateData(items,"sprint_order"),this.rs.userstories.bulkUpdateSprintOrder(project,data).then(function(_this){return function(){var us,_i,_len,_results;for(_results=[],_i=0,_len=usList.length;_len>_i;_i++)us=usList[_i],_results.push(_this.rootscope.$broadcast("sprint:us:moved",us,oldSprintId,newSprintId));return _results}}(this))),promise;if(null===newSprintId){for(_i=0,_len=usList.length;_len>_i;_i++)us=usList[_i],us.milestone=null;return this.scope.$apply(function(_this){return function(){var args,key,r,sprint,_j,_len1,_results;for(args=[newUsIndex,0].concat(usList),Array.prototype.splice.apply(_this.scope.userstories,args),Array.prototype.splice.apply(_this.scope.visibleUserstories,args),_this.filterVisibleUserstories(),sprint=_this.scope.sprintsById[oldSprintId],_results=[],key=_j=0,_len1=usList.length;_len1>_j;key=++_j)us=usList[key],r=sprint.user_stories.indexOf(us),_results.push(sprint.user_stories.splice(r,1));return _results}}(this)),promise=this.repo.save(us),promise=promise.then(function(_this){return function(){return items=_this.resortUserStories(_this.scope.userstories,"backlog_order"),data=_this.prepareBulkUpdateData(items,"backlog_order"),_this.rs.userstories.bulkUpdateBacklogOrder(us.project,data).then(function(){return _this.rootscope.$broadcast("sprint:us:moved",us,oldSprintId,newSprintId)})}}(this)),promise.then(null,function(){return console.log("FAIL")}),promise}if(newSprint=this.scope.sprintsById[newSprintId],null===oldSprintId){for(_j=0,_len1=usList.length;_len1>_j;_j++)us=usList[_j],us.milestone=newSprintId;this.scope.$apply(function(_this){return function(){var args,key,r,_k,_len2,_results;for(args=[newUsIndex,0].concat(usList),Array.prototype.splice.apply(newSprint.user_stories,args),_results=[],key=_k=0,_len2=usList.length;_len2>_k;key=++_k)us=usList[key],r=_this.scope.visibleUserstories.indexOf(us),_this.scope.visibleUserstories.splice(r,1),r=_this.scope.userstories.indexOf(us),_results.push(_this.scope.userstories.splice(r,1));return _results}}(this))}else{for(_k=0,_len2=usList.length;_len2>_k;_k++)us=usList[_k],us.milestone=newSprintId;this.scope.$apply(function(_this){return function(){var args,oldSprint,r,_l,_len3,_results;for(args=[newUsIndex,0].concat(usList),Array.prototype.splice.apply(newSprint.user_stories,args),_results=[],_l=0,_len3=usList.length;_len3>_l;_l++)us=usList[_l],oldSprint=_this.scope.sprintsById[oldSprintId],r=oldSprint.user_stories.indexOf(us),_results.push(oldSprint.user_stories.splice(r,1));return _results}}(this))}return promises=_.map(usList,function(_this){return function(us){return _this.repo.save(us)}}(this)),promise=this.q.all(promises).then(function(_this){return function(){return items=_this.resortUserStories(newSprint.user_stories,"sprint_order"),data=_this.prepareBulkUpdateData(items,"sprint_order"),_this.rs.userstories.bulkUpdateSprintOrder(project,data).then(function(){return _this.rootscope.$broadcast("sprint:us:moved",us,oldSprintId,newSprintId)}),_this.rs.userstories.bulkUpdateBacklogOrder(project,data).then(function(){var _l,_len3,_results;for(_results=[],_l=0,_len3=usList.length;_len3>_l;_l++)us=usList[_l],_results.push(_this.rootscope.$broadcast("sprint:us:moved",us,oldSprintId,newSprintId));return _results})}}(this)),promise.then(null,function(){return console.log("FAIL")}),promise},BacklogController.prototype.getUrlFilters=function(){return _.pick(this.location.search(),"statuses","tags","q")},BacklogController.prototype.generateFilters=function(){var isSelected,name,plainStatuses,plainTags,searchdata,urlfilters,val,value,_i,_len,_ref;urlfilters=this.getUrlFilters(),urlfilters.q&&(this.scope.filtersQ=urlfilters.q),searchdata={};for(name in urlfilters)for(value=urlfilters[name],null==searchdata[name]&&(searchdata[name]={}),_ref=taiga.toString(value).split(","),_i=0,_len=_ref.length;_len>_i;_i++)val=_ref[_i],searchdata[name][val]=!0;return isSelected=function(type,id){return null!=searchdata[type]&&searchdata[type][id]?!0:!1},this.scope.filters={},plainTags=_.flatten(_.filter(_.map(this.scope.userstories,"tags"))),plainTags.sort(),this.scope.filters.tags=_.map(_.countBy(plainTags),function(_this){return function(v,k){var obj;return obj={id:k,type:"tags",name:k,color:_this.scope.project.tags_colors[k],count:v},isSelected("tags",obj.id)&&(obj.selected=!0),obj}}(this)),plainStatuses=_.map(this.scope.userstories,"status"),plainStatuses=_.filter(plainStatuses,function(){return function(status){return status?status:void 0}}(this)),this.scope.filters.statuses=_.map(_.countBy(plainStatuses),function(_this){return function(v,k){var obj;return obj={id:k,type:"statuses",name:_this.scope.usStatusById[k].name,color:_this.scope.usStatusById[k].color,count:v},isSelected("statuses",obj.id)&&(obj.selected=!0),obj}}(this)),this.scope.filters},BacklogController.prototype.editUserStory=function(us){return this.rootscope.$broadcast("usform:edit",us)},BacklogController.prototype.deleteUserStory=function(us){var message,title;return title="Delete User Story",message=us.subject,this.confirm.askOnDelete(title,message).then(function(_this){return function(finish){var promise;return _this.scope.userstories=_.without(_this.scope.userstories,us),_this.filterVisibleUserstories(),promise=_this.repo.remove(us),promise.then(function(){return finish(),_this.loadBacklog()}),promise.then(null,function(){return finish(!1),_this.confirm.notify("error")})}}(this))},BacklogController.prototype.addNewUs=function(type){switch(type){case"standard":return this.rootscope.$broadcast("usform:new",this.scope.projectId,this.scope.project.default_us_status,this.scope.usStatusList);case"bulk":return this.rootscope.$broadcast("usform:bulk",this.scope.projectId,this.scope.project.default_us_status)}},BacklogController.prototype.addNewSprint=function(){return this.rootscope.$broadcast("sprintform:create",this.scope.projectId)},BacklogController}(mixOf(taiga.Controller,taiga.PageMixin,taiga.FiltersMixin)),module.controller("BacklogController",BacklogController),BacklogDirective=function($repo,$rootscope){var doomLineTemplate,link,linkDoomLine,linkFilters,linkToolbar,showHideFilter,showHideTags;return doomLineTemplate=_.template('
\");\n templateFn = function() {\n var publicRegisterEnabled;\n publicRegisterEnabled = $config.get(\"publicRegisterEnabled\");\n if (!publicRegisterEnabled) {\n return \"\";\n }\n return template({\n url: $navUrls.resolve(\"register\")\n });\n };\n return {\n restrict: \"AE\",\n scope: {},\n template: templateFn\n };\n };\n\n module.directive(\"tgPublicRegisterMessage\", [\"$tgConfig\", \"$tgNavUrls\", PublicRegisterMessageDirective]);\n\n LoginDirective = function($auth, $confirm, $location, $config, $routeParams, $navUrls, $events) {\n var link;\n link = function($scope, $el, $attrs) {\n var onError, onSuccess, submit;\n onSuccess = function(response) {\n var nextUrl;\n if ($routeParams['next'] && $routeParams['next'] !== $navUrls.resolve(\"login\")) {\n nextUrl = $routeParams['next'];\n } else {\n nextUrl = $navUrls.resolve(\"home\");\n }\n $events.setupConnection();\n return $location.path(nextUrl);\n };\n onError = function(response) {\n return $confirm.notify(\"light-error\", \"According to our Oompa Loompas, your username/email or password are incorrect.\");\n };\n submit = debounce(2000, (function(_this) {\n return function(event) {\n var data, form, promise;\n event.preventDefault();\n form = new checksley.Form($el.find(\"form.login-form\"));\n if (!form.validate()) {\n return;\n }\n data = {\n \"username\": $el.find(\"form.login-form input[name=username]\").val(),\n \"password\": $el.find(\"form.login-form input[name=password]\").val()\n };\n promise = $auth.login(data);\n return promise.then(onSuccess, onError);\n };\n })(this));\n $el.on(\"submit\", \"form\", submit);\n return $el.on(\"click\", \".submit-button\", submit);\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgLogin\", [\"$tgAuth\", \"$tgConfirm\", \"$tgLocation\", \"$tgConfig\", \"$routeParams\", \"$tgNavUrls\", \"$tgEvents\", LoginDirective]);\n\n RegisterDirective = function($auth, $confirm, $location, $navUrls, $config, $analytics) {\n var link;\n link = function($scope, $el, $attrs) {\n var form, onErrorSubmit, onSuccessSubmit, submit;\n if (!$config.get(\"publicRegisterEnabled\")) {\n $location.path($navUrls.resolve(\"not-found\"));\n $location.replace();\n }\n $scope.data = {};\n form = $el.find(\"form\").checksley({\n onlyOneErrorElement: true\n });\n onSuccessSubmit = function(response) {\n $analytics.trackEvent(\"auth\", \"register\", \"user registration\", 1);\n $confirm.notify(\"success\", \"Our Oompa Loompas are happy, welcome to Taiga.\");\n return $location.path($navUrls.resolve(\"home\"));\n };\n onErrorSubmit = function(response) {\n if (response.data._error_message != null) {\n $confirm.notify(\"light-error\", \"According to our Oompa Loompas there was an error. \" + response.data._error_message);\n }\n return form.setErrors(response.data);\n };\n submit = debounce(2000, (function(_this) {\n return function(event) {\n var promise;\n event.preventDefault();\n if (!form.validate()) {\n return;\n }\n promise = $auth.register($scope.data);\n return promise.then(onSuccessSubmit, onErrorSubmit);\n };\n })(this));\n $el.on(\"submit\", \"form\", submit);\n return $el.on(\"click\", \".submit-button\", submit);\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgRegister\", [\"$tgAuth\", \"$tgConfirm\", \"$tgLocation\", \"$tgNavUrls\", \"$tgConfig\", \"$tgAnalytics\", RegisterDirective]);\n\n ForgotPasswordDirective = function($auth, $confirm, $location, $navUrls) {\n var link;\n link = function($scope, $el, $attrs) {\n var form, onErrorSubmit, onSuccessSubmit, submit;\n $scope.data = {};\n form = $el.find(\"form\").checksley();\n onSuccessSubmit = function(response) {\n $location.path($navUrls.resolve(\"login\"));\n return $confirm.success(\"Check your inbox! We have sent a mail to \" + response.data.email + \" with the instructions to set a new password\");\n };\n onErrorSubmit = function(response) {\n return $confirm.notify(\"light-error\", \"According to our Oompa Loompas, your are not registered yet.\");\n };\n submit = debounce(2000, (function(_this) {\n return function(event) {\n var promise;\n event.preventDefault();\n if (!form.validate()) {\n return;\n }\n promise = $auth.forgotPassword($scope.data);\n return promise.then(onSuccessSubmit, onErrorSubmit);\n };\n })(this));\n $el.on(\"submit\", \"form\", submit);\n return $el.on(\"click\", \".submit-button\", submit);\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgForgotPassword\", [\"$tgAuth\", \"$tgConfirm\", \"$tgLocation\", \"$tgNavUrls\", ForgotPasswordDirective]);\n\n ChangePasswordFromRecoveryDirective = function($auth, $confirm, $location, $params, $navUrls) {\n var link;\n link = function($scope, $el, $attrs) {\n var form, onErrorSubmit, onSuccessSubmit, submit;\n $scope.data = {};\n if ($params.token != null) {\n $scope.tokenInParams = true;\n $scope.data.token = $params.token;\n } else {\n $scope.tokenInParams = false;\n }\n form = $el.find(\"form\").checksley();\n onSuccessSubmit = function(response) {\n $location.path($navUrls.resolve(\"login\"));\n return $confirm.success(\"Our Oompa Loompas saved your new password. Try to sign in with it.\");\n };\n onErrorSubmit = function(response) {\n return $confirm.notify(\"light-error\", \"One of our Oompa Loompas say '\" + response.data._error_message + \"'.\");\n };\n submit = debounce(2000, (function(_this) {\n return function(event) {\n var promise;\n event.preventDefault();\n if (!form.validate()) {\n return;\n }\n promise = $auth.changePasswordFromRecovery($scope.data);\n return promise.then(onSuccessSubmit, onErrorSubmit);\n };\n })(this));\n $el.on(\"submit\", \"form\", submit);\n return $el.on(\"click\", \".submit-button\", submit);\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgChangePasswordFromRecovery\", [\"$tgAuth\", \"$tgConfirm\", \"$tgLocation\", \"$routeParams\", \"$tgNavUrls\", ChangePasswordFromRecoveryDirective]);\n\n InvitationDirective = function($auth, $confirm, $location, $params, $navUrls, $analytics) {\n var link;\n link = function($scope, $el, $attrs) {\n var loginForm, onErrorSubmitLogin, onErrorSubmitRegister, onSuccessSubmitLogin, onSuccessSubmitRegister, promise, registerForm, submitLogin, submitRegister, token;\n token = $params.token;\n promise = $auth.getInvitation(token);\n promise.then(function(invitation) {\n return $scope.invitation = invitation;\n });\n promise.then(null, function(response) {\n $location.path($navUrls.resolve(\"login\"));\n return $confirm.success(\"Ooops, we have a problem Our Oompa Loompas can't find your invitation.\");\n });\n $scope.dataLogin = {\n token: token\n };\n loginForm = $el.find(\"form.login-form\").checksley({\n onlyOneErrorElement: true\n });\n onSuccessSubmitLogin = function(response) {\n $analytics.trackEvent(\"auth\", \"invitationAccept\", \"invitation accept with existing user\", 1);\n $location.path($navUrls.resolve(\"project\", {\n project: $scope.invitation.project_slug\n }));\n return $confirm.notify(\"success\", \"You've successfully joined this project\", \"Welcome to \" + (_.escape($scope.invitation.project_name)));\n };\n onErrorSubmitLogin = function(response) {\n return $confirm.notify(\"light-error\", \"According to our Oompa Loompas, your are not registered yet or typed an invalid password.\");\n };\n submitLogin = debounce(2000, (function(_this) {\n return function(event) {\n event.preventDefault();\n if (!loginForm.validate()) {\n return;\n }\n promise = $auth.acceptInvitiationWithExistingUser($scope.dataLogin);\n return promise.then(onSuccessSubmitLogin, onErrorSubmitLogin);\n };\n })(this));\n $el.on(\"submit\", \"form.login-form\", submitLogin);\n $el.on(\"click\", \".button-login\", submitLogin);\n $scope.dataRegister = {\n token: token\n };\n registerForm = $el.find(\"form.register-form\").checksley();\n onSuccessSubmitRegister = function(response) {\n $analytics.trackEvent(\"auth\", \"invitationAccept\", \"invitation accept with new user\", 1);\n $location.path($navUrls.resolve(\"project\", {\n project: $scope.invitation.project_slug\n }));\n return $confirm.notify(\"success\", \"You've successfully joined this project\", \"Welcome to \" + (_.escape($scope.invitation.project_name)));\n };\n onErrorSubmitRegister = function(response) {\n return $confirm.notify(\"light-error\", \"According to our Oompa Loompas, that username or email is already in use.\");\n };\n submitRegister = debounce(2000, (function(_this) {\n return function(event) {\n event.preventDefault();\n if (!registerForm.validate()) {\n return;\n }\n promise = $auth.acceptInvitiationWithNewUser($scope.dataRegister);\n return promise.then(onSuccessSubmitRegister, onErrorSubmitRegister);\n };\n })(this));\n $el.on(\"submit\", \"form.register-form\", submitRegister);\n return $el.on(\"click\", \".button-register\", submitRegister);\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgInvitation\", [\"$tgAuth\", \"$tgConfirm\", \"$tgLocation\", \"$routeParams\", \"$tgNavUrls\", \"$tgAnalytics\", InvitationDirective]);\n\n ChangeEmailDirective = function($repo, $model, $auth, $confirm, $location, $params, $navUrls) {\n var link;\n link = function($scope, $el, $attrs) {\n var form, onErrorSubmit, onSuccessSubmit, submit;\n $scope.data = {};\n $scope.data.email_token = $params.email_token;\n form = $el.find(\"form\").checksley();\n onSuccessSubmit = function(response) {\n return $repo.queryOne(\"users\", $auth.getUser().id).then((function(_this) {\n return function(data) {\n $auth.setUser(data);\n $location.path($navUrls.resolve(\"home\"));\n return $confirm.success(\"Our Oompa Loompas updated your email\");\n };\n })(this));\n };\n onErrorSubmit = function(response) {\n return $confirm.notify(\"error\", \"One of our Oompa Loompas says '\" + response.data._error_message + \"'.\");\n };\n submit = function() {\n var promise;\n if (!form.validate()) {\n return;\n }\n promise = $auth.changeEmail($scope.data);\n return promise.then(onSuccessSubmit, onErrorSubmit);\n };\n $el.on(\"submit\", function(event) {\n event.preventDefault();\n return submit();\n });\n return $el.on(\"click\", \"a.button-change-email\", function(event) {\n event.preventDefault();\n return submit();\n });\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgChangeEmail\", [\"$tgRepo\", \"$tgModel\", \"$tgAuth\", \"$tgConfirm\", \"$tgLocation\", \"$routeParams\", \"$tgNavUrls\", ChangeEmailDirective]);\n\n CancelAccountDirective = function($repo, $model, $auth, $confirm, $location, $params, $navUrls) {\n var link;\n link = function($scope, $el, $attrs) {\n var form, onErrorSubmit, onSuccessSubmit, submit;\n $scope.data = {};\n $scope.data.cancel_token = $params.cancel_token;\n form = $el.find(\"form\").checksley();\n onSuccessSubmit = function(response) {\n $auth.logout();\n $location.path($navUrls.resolve(\"home\"));\n return $confirm.success(\"Our Oompa Loompas removed your account\");\n };\n onErrorSubmit = function(response) {\n return $confirm.notify(\"error\", \"One of our Oompa Loompas says '\" + response.data._error_message + \"'.\");\n };\n submit = debounce(2000, (function(_this) {\n return function(event) {\n var promise;\n event.preventDefault();\n if (!form.validate()) {\n return;\n }\n promise = $auth.cancelAccount($scope.data);\n return promise.then(onSuccessSubmit, onErrorSubmit);\n };\n })(this));\n $el.on(\"submit\", \"form\", submit);\n return $el.on(\"click\", \".submit-button\", submit);\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgCancelAccount\", [\"$tgRepo\", \"$tgModel\", \"$tgAuth\", \"$tgConfirm\", \"$tgLocation\", \"$routeParams\", \"$tgNavUrls\", CancelAccountDirective]);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/backlog.coffee\n */\n\n(function() {\n var module;\n\n module = angular.module(\"taigaBacklog\", []);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/base.coffee\n */\n\n(function() {\n var TaigaMainDirective, bindOnce, groupBy, init, module, taiga, urls;\n\n taiga = this.taiga;\n\n groupBy = this.taiga.groupBy;\n\n bindOnce = this.taiga.bindOnce;\n\n module = angular.module(\"taigaBase\", [\"taigaLocales\"]);\n\n TaigaMainDirective = function($rootscope, $window) {\n var link;\n link = function($scope, $el, $attrs) {\n return $window.onresize = function() {\n return $rootscope.$broadcast(\"resize\");\n };\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgMain\", [\"$rootScope\", \"$window\", TaigaMainDirective]);\n\n urls = {\n \"home\": \"/\",\n \"error\": \"/error\",\n \"not-found\": \"/not-found\",\n \"permission-denied\": \"/permission-denied\",\n \"login\": \"/login\",\n \"forgot-password\": \"/forgot-password\",\n \"change-password\": \"/change-password/:token\",\n \"change-email\": \"/change-email/:token\",\n \"cancel-account\": \"/cancel-account/:token\",\n \"register\": \"/register\",\n \"invitation\": \"/invitation/:token\",\n \"create-project\": \"/create-project\",\n \"profile\": \"/:user\",\n \"project\": \"/project/:project\",\n \"project-backlog\": \"/project/:project/backlog\",\n \"project-taskboard\": \"/project/:project/taskboard/:sprint\",\n \"project-kanban\": \"/project/:project/kanban\",\n \"project-issues\": \"/project/:project/issues\",\n \"project-search\": \"/project/:project/search\",\n \"project-userstories-detail\": \"/project/:project/us/:ref\",\n \"project-tasks-detail\": \"/project/:project/task/:ref\",\n \"project-issues-detail\": \"/project/:project/issue/:ref\",\n \"project-wiki\": \"/project/:project/wiki\",\n \"project-wiki-page\": \"/project/:project/wiki/:slug\",\n \"project-team\": \"/project/:project/team\",\n \"project-admin-home\": \"/project/:project/admin/project-profile/details\",\n \"project-admin-project-profile-details\": \"/project/:project/admin/project-profile/details\",\n \"project-admin-project-profile-default-values\": \"/project/:project/admin/project-profile/default-values\",\n \"project-admin-project-profile-modules\": \"/project/:project/admin/project-profile/modules\",\n \"project-admin-project-values-us-status\": \"/project/:project/admin/project-values/us-status\",\n \"project-admin-project-values-us-points\": \"/project/:project/admin/project-values/us-points\",\n \"project-admin-project-values-task-status\": \"/project/:project/admin/project-values/task-status\",\n \"project-admin-project-values-issue-status\": \"/project/:project/admin/project-values/issue-status\",\n \"project-admin-project-values-issue-types\": \"/project/:project/admin/project-values/issue-types\",\n \"project-admin-project-values-issue-priorities\": \"/project/:project/admin/project-values/issue-priorities\",\n \"project-admin-project-values-issue-severities\": \"/project/:project/admin/project-values/issue-severities\",\n \"project-admin-memberships\": \"/project/:project/admin/memberships\",\n \"project-admin-roles\": \"/project/:project/admin/roles\",\n \"project-admin-third-parties-github\": \"/project/:project/admin/third-parties/github\",\n \"project-admin-third-parties-gitlab\": \"/project/:project/admin/third-parties/gitlab\",\n \"project-admin-third-parties-bitbucket\": \"/project/:project/admin/third-parties/bitbucket\",\n \"user-settings-user-profile\": \"/project/:project/user-settings/user-profile\",\n \"user-settings-user-change-password\": \"/project/:project/user-settings/user-change-password\",\n \"user-settings-user-avatar\": \"/project/:project/user-settings/user-avatar\",\n \"user-settings-mail-notifications\": \"/project/:project/user-settings/mail-notifications\"\n };\n\n init = function($log, $navurls) {\n $log.debug(\"Initialize navigation urls\");\n return $navurls.update(urls);\n };\n\n module.run([\"$log\", \"$tgNavUrls\", init]);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/common.coffee\n */\n\n(function() {\n var AnimationFrame, AppTitle, CheckPermissionDirective, LimitLineLengthDirective, ProjectUrl, Qqueue, SelectedText, ToggleCommentDirective, module, taiga,\n __slice = [].slice;\n\n taiga = this.taiga;\n\n module = angular.module(\"taigaCommon\", []);\n\n SelectedText = function($window, $document) {\n var get;\n get = function() {\n if ($window.getSelection) {\n return $window.getSelection().toString();\n } else if ($document.selection) {\n return $document.selection.createRange().text;\n }\n return \"\";\n };\n return {\n get: get\n };\n };\n\n module.factory(\"$selectedText\", [\"$window\", \"$document\", SelectedText]);\n\n CheckPermissionDirective = function() {\n var link, render;\n render = function($el, project, permission) {\n if (project.my_permissions.indexOf(permission) > -1) {\n return $el.removeClass('hidden');\n }\n };\n link = function($scope, $el, $attrs) {\n var permission;\n $el.addClass('hidden');\n permission = $attrs.tgCheckPermission;\n $scope.$watch(\"project\", function(project) {\n if (project != null) {\n return render($el, project, permission);\n }\n });\n return $scope.$on(\"$destroy\", function() {\n return $el.off();\n });\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgCheckPermission\", CheckPermissionDirective);\n\n AnimationFrame = function() {\n var add, animationFrame, performAnimation, tail;\n animationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;\n performAnimation = (function(_this) {\n return function(time) {\n var fn;\n fn = tail.shift();\n fn();\n if (tail.length) {\n return animationFrame(performAnimation);\n }\n };\n })(this);\n tail = [];\n add = function() {\n var fn, _i, _len, _results;\n _results = [];\n for (_i = 0, _len = arguments.length; _i < _len; _i++) {\n fn = arguments[_i];\n tail.push(fn);\n if (tail.length === 1) {\n _results.push(animationFrame(performAnimation));\n } else {\n _results.push(void 0);\n }\n }\n return _results;\n };\n return {\n add: add\n };\n };\n\n module.factory(\"animationFrame\", AnimationFrame);\n\n ToggleCommentDirective = function() {\n var link;\n link = function($scope, $el, $attrs) {\n return $el.find(\"textarea\").on(\"focus\", function() {\n return $el.addClass(\"active\");\n });\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgToggleComment\", ToggleCommentDirective);\n\n AppTitle = function() {\n var set;\n set = function(text) {\n return $(\"title\").text(text);\n };\n return {\n set: set\n };\n };\n\n module.factory(\"$appTitle\", AppTitle);\n\n ProjectUrl = function($navurls) {\n var get;\n get = function(project) {\n var ctx;\n ctx = {\n project: project.slug\n };\n if (project.is_backlog_activated && project.my_permissions.indexOf(\"view_us\") > -1) {\n return $navurls.resolve(\"project-backlog\", ctx);\n }\n if (project.is_kanban_activated && project.my_permissions.indexOf(\"view_us\") > -1) {\n return $navurls.resolve(\"project-kanban\", ctx);\n }\n if (project.is_wiki_activated && project.my_permissions.indexOf(\"view_wiki_pages\") > -1) {\n return $navurls.resolve(\"project-wiki\", ctx);\n }\n if (project.is_issues_activated && project.my_permissions.indexOf(\"view_issues\") > -1) {\n return $navurls.resolve(\"project-issues\", ctx);\n }\n return $navurls.resolve(\"project\", ctx);\n };\n return {\n get: get\n };\n };\n\n module.factory(\"$projectUrl\", [\"$tgNavUrls\", ProjectUrl]);\n\n LimitLineLengthDirective = function() {\n var link;\n link = function($scope, $el, $attrs) {\n var maxColsPerLine;\n maxColsPerLine = parseInt($el.attr(\"cols\"));\n return $el.on(\"keyup\", function(event) {\n var code, lines;\n code = event.keyCode;\n lines = $el.val().split(\"\\n\");\n _.each(lines, function(line, index) {\n return lines[index] = line.substring(0, maxColsPerLine - 2);\n });\n return $el.val(lines.join(\"\\n\"));\n });\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgLimitLineLength\", LimitLineLengthDirective);\n\n Qqueue = function($q) {\n var deferred, lastPromise, qqueue;\n deferred = $q.defer();\n deferred.resolve();\n lastPromise = deferred.promise;\n qqueue = {\n bindAdd: (function(_this) {\n return function(fn) {\n return function() {\n var args;\n args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n return lastPromise = lastPromise.then(function() {\n return fn.apply(_this, args);\n });\n };\n return qqueue;\n };\n })(this),\n add: (function(_this) {\n return function(fn) {\n if (!lastPromise) {\n lastPromise = fn();\n } else {\n lastPromise = lastPromise.then(fn);\n }\n return qqueue;\n };\n })(this)\n };\n return qqueue;\n };\n\n module.factory(\"$tgQqueue\", [\"$q\", Qqueue]);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/events.coffee\n */\n\n(function() {\n var EventsProvider, EventsService, bindMethods, module, startswith, taiga;\n\n taiga = this.taiga;\n\n startswith = this.taiga.startswith;\n\n bindMethods = this.taiga.bindMethods;\n\n module = angular.module(\"taigaEvents\", []);\n\n EventsService = (function() {\n function EventsService(win, log, config, auth) {\n this.win = win;\n this.log = log;\n this.config = config;\n this.auth = auth;\n bindMethods(this);\n }\n\n EventsService.prototype.initialize = function(sessionId) {\n this.sessionId = sessionId;\n this.subscriptions = {};\n this.connected = false;\n this.error = false;\n this.pendingMessages = [];\n if (this.win.WebSocket === void 0) {\n return this.log.info(\"WebSockets not supported on your browser\");\n }\n };\n\n EventsService.prototype.setupConnection = function() {\n var loc, path, scheme, url;\n this.stopExistingConnection();\n url = this.config.get(\"eventsUrl\");\n if (!url) {\n return;\n }\n if (!startswith(url, \"ws:\") && !startswith(url, \"wss:\")) {\n loc = this.win.location;\n scheme = loc.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n path = _.str.ltrim(url, \"/\");\n url = \"\" + scheme + \"//\" + loc.host + \"/\" + path;\n }\n this.ws = new this.win.WebSocket(url);\n this.ws.addEventListener(\"open\", this.onOpen);\n this.ws.addEventListener(\"message\", this.onMessage);\n this.ws.addEventListener(\"error\", this.onError);\n return this.ws.addEventListener(\"close\", this.onClose);\n };\n\n EventsService.prototype.stopExistingConnection = function() {\n if (this.ws === void 0) {\n return;\n }\n this.ws.removeEventListener(\"open\", this.onOpen);\n this.ws.removeEventListener(\"close\", this.onClose);\n this.ws.removeEventListener(\"error\", this.onError);\n this.ws.removeEventListener(\"message\", this.onMessage);\n this.ws.close();\n return delete this.ws;\n };\n\n EventsService.prototype.serialize = function(message) {\n if (_.isObject(message)) {\n return JSON.stringify(message);\n }\n return message;\n };\n\n EventsService.prototype.sendMessage = function(message) {\n var messages, msg, _i, _len, _results;\n this.pendingMessages.push(message);\n if (!this.connected) {\n return;\n }\n messages = _.map(this.pendingMessages, this.serialize);\n this.pendingMessages = [];\n _results = [];\n for (_i = 0, _len = messages.length; _i < _len; _i++) {\n msg = messages[_i];\n _results.push(this.ws.send(msg));\n }\n return _results;\n };\n\n EventsService.prototype.subscribe = function(scope, routingKey, callback) {\n var message, subscription;\n if (this.error) {\n return;\n }\n this.log.debug(\"Subscribe to: \" + routingKey);\n subscription = {\n scope: scope,\n routingKey: routingKey,\n callback: _.debounce(callback, 500, {\n \"leading\": true,\n \"trailing\": false\n })\n };\n message = {\n \"cmd\": \"subscribe\",\n \"routing_key\": routingKey\n };\n this.subscriptions[routingKey] = subscription;\n this.sendMessage(message);\n return scope.$on(\"$destroy\", (function(_this) {\n return function() {\n return _this.unsubscribe(routingKey);\n };\n })(this));\n };\n\n EventsService.prototype.unsubscribe = function(routingKey) {\n var message;\n if (this.error) {\n return;\n }\n this.log.debug(\"Unsubscribe from: \" + routingKey);\n message = {\n \"cmd\": \"unsubscribe\",\n \"routing_key\": routingKey\n };\n return this.sendMessage(message);\n };\n\n EventsService.prototype.onOpen = function() {\n var message, token;\n this.connected = true;\n this.log.debug(\"WebSocket connection opened\");\n token = this.auth.getToken();\n message = {\n cmd: \"auth\",\n data: {\n token: token,\n sessionId: this.sessionId\n }\n };\n return this.sendMessage(message);\n };\n\n EventsService.prototype.onMessage = function(event) {\n var data, routingKey, subscription;\n this.log.debug(\"WebSocket message received: \" + event.data);\n data = JSON.parse(event.data);\n routingKey = data.routing_key;\n if (this.subscriptions[routingKey] == null) {\n return;\n }\n subscription = this.subscriptions[routingKey];\n return subscription.scope.$apply(function() {\n return subscription.callback(data.data);\n });\n };\n\n EventsService.prototype.onError = function(error) {\n this.log.error(\"WebSocket error: \" + error);\n return this.error = true;\n };\n\n EventsService.prototype.onClose = function() {\n this.log.debug(\"WebSocket closed.\");\n return this.connected = false;\n };\n\n return EventsService;\n\n })();\n\n EventsProvider = (function() {\n function EventsProvider() {}\n\n EventsProvider.prototype.setSessionId = function(sessionId) {\n return this.sessionId = sessionId;\n };\n\n EventsProvider.prototype.$get = function($win, $log, $conf, $auth) {\n var service;\n service = new EventsService($win, $log, $conf, $auth);\n service.initialize(this.sessionId);\n return service;\n };\n\n EventsProvider.prototype.$get.$inject = [\"$window\", \"$log\", \"$tgConfig\", \"$tgAuth\"];\n\n return EventsProvider;\n\n })();\n\n module.provider(\"$tgEvents\", EventsProvider);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/feedback.coffee\n */\n\n(function() {\n var FeedbackDirective, bindOnce, debounce, groupBy, mixOf, module, taiga, trim;\n\n taiga = this.taiga;\n\n groupBy = this.taiga.groupBy;\n\n bindOnce = this.taiga.bindOnce;\n\n mixOf = this.taiga.mixOf;\n\n debounce = this.taiga.debounce;\n\n trim = this.taiga.trim;\n\n module = angular.module(\"taigaFeedback\", []);\n\n FeedbackDirective = function($lightboxService, $repo, $confirm, $loading) {\n var link;\n link = function($scope, $el, $attrs) {\n var form, submit, submitButton;\n form = $el.find(\"form\").checksley();\n submit = debounce(2000, (function(_this) {\n return function(event) {\n var promise;\n event.preventDefault();\n if (!form.validate()) {\n return;\n }\n $loading.start(submitButton);\n promise = $repo.create(\"feedback\", $scope.feedback);\n promise.then(function(data) {\n $loading.finish(submitButton);\n $lightboxService.close($el);\n return $confirm.notify(\"success\", \"\\\\o/ we'll be happy to read your\");\n });\n return promise.then(null, function() {\n $loading.finish(submitButton);\n return $confirm.notify(\"error\");\n });\n };\n })(this));\n submitButton = $el.find(\".submit-button\");\n $el.on(\"submit\", \"form\", submit);\n $el.on(\"click\", \".submit-button\", submit);\n $scope.$on(\"feedback:show\", function() {\n $scope.$apply(function() {\n return $scope.feedback = {};\n });\n $lightboxService.open($el);\n return $el.find(\"textarea\").focus();\n });\n return $scope.$on(\"$destroy\", function() {\n return $el.off();\n });\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgLbFeedback\", [\"lightboxService\", \"$tgRepo\", \"$tgConfirm\", \"$tgLoading\", FeedbackDirective]);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/integrations.coffee\n */\n\n(function() {\n var module;\n\n module = angular.module(\"taigaIntegrations\", []);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/issues.coffee\n */\n\n(function() {\n var module;\n\n module = angular.module(\"taigaIssues\", []);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/kanban.coffee\n */\n\n(function() {\n var module;\n\n module = angular.module(\"taigaKanban\", []);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/locales.coffee\n */\n\n(function() {\n var module;\n\n module = angular.module(\"taigaLocales\", []);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/nav.coffee\n */\n\n(function() {\n var ProjectMenuDirective, ProjectsNavigationController, ProjectsNavigationDirective, bindOnce, groupBy, module, taiga, timeout,\n __hasProp = {}.hasOwnProperty,\n __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };\n\n taiga = this.taiga;\n\n groupBy = this.taiga.groupBy;\n\n bindOnce = this.taiga.bindOnce;\n\n timeout = this.taiga.timeout;\n\n module = angular.module(\"taigaNavMenu\", []);\n\n ProjectsNavigationController = (function(_super) {\n __extends(ProjectsNavigationController, _super);\n\n ProjectsNavigationController.$inject = [\"$scope\", \"$rootScope\", \"$tgResources\", \"$tgNavUrls\", \"$projectUrl\"];\n\n function ProjectsNavigationController(scope, rootscope, rs, navurls, projectUrl) {\n var promise;\n this.scope = scope;\n this.rootscope = rootscope;\n this.rs = rs;\n this.navurls = navurls;\n this.projectUrl = projectUrl;\n promise = this.loadInitialData();\n promise.then(null, function() {\n return console.log(\"FAIL\");\n });\n this.scope.$on(\"projects:reload\", (function(_this) {\n return function() {\n return _this.loadInitialData();\n };\n })(this));\n this.scope.$on(\"project:loaded\", (function(_this) {\n return function(ctx, project) {\n return _this.loadInitialData();\n };\n })(this));\n }\n\n ProjectsNavigationController.prototype.loadInitialData = function() {\n return this.rs.projects.list().then((function(_this) {\n return function(projects) {\n var project, _i, _len;\n for (_i = 0, _len = projects.length; _i < _len; _i++) {\n project = projects[_i];\n project.url = _this.projectUrl.get(project);\n }\n _this.scope.projects = projects;\n _this.scope.filteredProjects = projects;\n _this.scope.filterText = \"\";\n return projects;\n };\n })(this));\n };\n\n ProjectsNavigationController.prototype.newProject = function() {\n return this.scope.$apply((function(_this) {\n return function() {\n return _this.rootscope.$broadcast(\"projects:create\");\n };\n })(this));\n };\n\n ProjectsNavigationController.prototype.filterProjects = function(text) {\n this.scope.filteredProjects = _.filter(this.scope.projects, function(project) {\n return project.name.toLowerCase().indexOf(text) > -1;\n });\n this.scope.filterText = text;\n return this.rootscope.$broadcast(\"projects:filtered\");\n };\n\n return ProjectsNavigationController;\n\n })(taiga.Controller);\n\n module.controller(\"ProjectsNavigationController\", ProjectsNavigationController);\n\n ProjectsNavigationDirective = function($rootscope, animationFrame, $timeout, tgLoader, $location, $compile) {\n var baseTemplate, hideMenu, link, loadingStart, overlay, projectsTemplate;\n baseTemplate = _.template(\"
\\n\");\n getSectionName = function($el, sectionName, project) {\n var oldSectionName, _ref;\n oldSectionName = (_ref = $el.find(\"a.active\").parent().attr(\"id\")) != null ? _ref.replace(\"nav-\", \"\") : void 0;\n if (sectionName === \"backlog-kanban\") {\n if (oldSectionName === \"backlog\" || oldSectionName === \"kanban\") {\n sectionName = oldSectionName;\n } else if (project.is_backlog_activated && !project.is_kanban_activated) {\n sectionName = \"backlog\";\n } else if (!project.is_backlog_activated && project.is_kanban_activated) {\n sectionName = \"kanban\";\n }\n }\n return sectionName;\n };\n renderMainMenu = function($el) {\n var html;\n html = mainTemplate({});\n return $el.html(html);\n };\n renderMenuEntries = function($el, targetScope, project) {\n var container, ctx, dom, sectionName;\n if (project == null) {\n project = {};\n }\n container = $el.find(\".menu-container\");\n sectionName = getSectionName($el, targetScope.section, project);\n ctx = {\n user: $auth.getUser(),\n project: project,\n feedbackEnabled: $config.get(\"feedbackEnabled\")\n };\n dom = $compile(menuEntriesTemplate(ctx))(targetScope);\n dom.find(\"a.active\").removeClass(\"active\");\n dom.find(\"#nav-\" + sectionName + \" > a\").addClass(\"active\");\n return container.replaceWith(dom);\n };\n videoConferenceUrl = function(project) {\n var baseUrl, url;\n if (project.videoconferences === \"appear-in\") {\n baseUrl = \"https://appear.in/\";\n } else if (project.videoconferences === \"talky\") {\n baseUrl = \"https://talky.io/\";\n } else {\n return \"\";\n }\n if (project.videoconferences_salt) {\n url = \"\" + project.slug + \"-\" + project.videoconferences_salt;\n } else {\n url = \"\" + project.slug;\n }\n return baseUrl + url;\n };\n link = function($scope, $el, $attrs, $ctrl) {\n var project;\n renderMainMenu($el);\n project = null;\n $el.on(\"click\", \".logo\", function(event) {\n var target;\n event.preventDefault();\n target = angular.element(event.currentTarget);\n return $rootscope.$broadcast(\"nav:projects-list:open\");\n });\n $el.on(\"click\", \".user-settings .avatar\", function(event) {\n event.preventDefault();\n return $el.find(\".user-settings .popover\").popover().open();\n });\n $el.on(\"click\", \".logout\", function(event) {\n event.preventDefault();\n $auth.logout();\n return $scope.$apply(function() {\n return $location.path($navUrls.resolve(\"login\"));\n });\n });\n $el.on(\"click\", \"#nav-search > a\", function(event) {\n event.preventDefault();\n return $rootscope.$broadcast(\"search-box:show\", project);\n });\n $el.on(\"click\", \".feedback\", function(event) {\n event.preventDefault();\n return $rootscope.$broadcast(\"feedback:show\");\n });\n $scope.$on(\"projects:loaded\", function(listener) {\n $el.addClass(\"hidden\");\n return listener.stopPropagation();\n });\n return $scope.$on(\"project:loaded\", function(ctx, newProject) {\n project = newProject;\n if ($el.hasClass(\"hidden\")) {\n $el.removeClass(\"hidden\");\n }\n project.videoconferenceUrl = videoConferenceUrl(project);\n return renderMenuEntries($el, ctx.targetScope, project);\n });\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgProjectMenu\", [\"$log\", \"$compile\", \"$tgAuth\", \"$rootScope\", \"$tgAuth\", \"$tgLocation\", \"$tgNavUrls\", \"$tgConfig\", ProjectMenuDirective]);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/projects.coffee\n */\n\n(function() {\n var module;\n\n module = angular.module(\"taigaProject\", []);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/related-tasks.coffee\n */\n\n(function() {\n var RelatedTaskAssignedToInlineEditionDirective, RelatedTaskCreateButtonDirective, RelatedTaskCreateFormDirective, RelatedTaskRowDirective, RelatedTasksDirective, debounce, module, taiga, trim;\n\n taiga = this.taiga;\n\n trim = this.taiga.trim;\n\n debounce = this.taiga.debounce;\n\n module = angular.module(\"taigaRelatedTasks\", []);\n\n RelatedTaskRowDirective = function($repo, $compile, $confirm, $rootscope, $loading) {\n var link, templateEdit, templateView;\n templateView = _.template(\"
\");\n newTask = {\n subject: \"\",\n assigned_to: null\n };\n link = function($scope, $el, $attrs) {\n var createTask, render;\n createTask = debounce(2000, function(task) {\n var promise;\n task.subject = $el.find('input').val();\n task.assigned_to = $scope.newTask.assigned_to;\n task.status = $scope.newTask.status;\n $scope.newTask.status = $scope.project.default_task_status;\n $scope.newTask.assigned_to = null;\n $loading.start($el.find('.task-name'));\n promise = $repo.create(\"tasks\", task);\n promise.then(function() {\n $analytics.trackEvent(\"task\", \"create\", \"create task on userstory\", 1);\n $loading.finish($el.find('.task-name'));\n $scope.$emit(\"related-tasks:add\");\n return $confirm.notify(\"success\");\n });\n promise.then(null, function() {\n $el.find('input').val(task.subject);\n $loading.finish($el.find('.task-name'));\n return $confirm.notify(\"error\");\n });\n return promise;\n });\n render = function() {\n $el.off();\n $el.html($compile(template())($scope));\n $el.find('input').focus().select();\n $el.addClass('active');\n $el.on(\"keyup\", \"input\", function(event) {\n if (event.keyCode === 13) {\n return createTask(newTask).then(function() {\n return render();\n });\n } else if (event.keyCode === 27) {\n return $el.html(\"\");\n }\n });\n $el.on(\"click\", \".icon-delete\", function(event) {\n return $el.html(\"\");\n });\n return $el.on(\"click\", \".icon-floppy\", function(event) {\n return createTask(newTask).then(function() {\n return $el.html(\"\");\n });\n });\n };\n taiga.bindOnce($scope, \"us\", function(val) {\n newTask[\"status\"] = $scope.project.default_task_status;\n newTask[\"project\"] = $scope.project.id;\n newTask[\"user_story\"] = $scope.us.id;\n $scope.newTask = $tgmodel.make_model(\"tasks\", newTask);\n return $el.html(\"\");\n });\n $scope.$on(\"related-tasks:show-form\", function() {\n return render();\n });\n return $scope.$on(\"$destroy\", function() {\n return $el.off();\n });\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgRelatedTaskCreateForm\", [\"$tgRepo\", \"$compile\", \"$tgConfirm\", \"$tgModel\", \"$tgLoading\", \"$tgAnalytics\", RelatedTaskCreateFormDirective]);\n\n RelatedTaskCreateButtonDirective = function($repo, $compile, $confirm, $tgmodel) {\n var link, template;\n template = _.template(\"\");\n link = function($scope, $el, $attrs) {\n $scope.$watch(\"project\", function(val) {\n if (!val) {\n return;\n }\n $el.off();\n if ($scope.project.my_permissions.indexOf(\"add_task\") !== -1) {\n $el.html(template());\n } else {\n $el.html(\"\");\n }\n return $el.on(\"click\", \".icon\", function(event) {\n return $scope.$emit(\"related-tasks:add-new-clicked\");\n });\n });\n return $scope.$on(\"$destroy\", function() {\n return $el.off();\n });\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgRelatedTaskCreateButton\", [\"$tgRepo\", \"$compile\", \"$tgConfirm\", \"$tgModel\", RelatedTaskCreateButtonDirective]);\n\n RelatedTasksDirective = function($repo, $rs, $rootscope) {\n var link;\n link = function($scope, $el, $attrs) {\n var loadTasks;\n loadTasks = function() {\n return $rs.tasks.list($scope.projectId, null, $scope.usId).then((function(_this) {\n return function(tasks) {\n $scope.tasks = tasks;\n return tasks;\n };\n })(this));\n };\n $scope.$on(\"related-tasks:add\", function() {\n return loadTasks().then(function() {\n return $rootscope.$broadcast(\"related-tasks:update\");\n });\n });\n $scope.$on(\"related-tasks:delete\", function() {\n return loadTasks().then(function() {\n return $rootscope.$broadcast(\"related-tasks:update\");\n });\n });\n $scope.$on(\"related-tasks:add-new-clicked\", function() {\n return $scope.$broadcast(\"related-tasks:show-form\");\n });\n taiga.bindOnce($scope, \"us\", function(val) {\n return loadTasks();\n });\n return $scope.$on(\"$destroy\", function() {\n return $el.off();\n });\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgRelatedTasks\", [\"$tgRepo\", \"$tgResources\", \"$rootScope\", RelatedTasksDirective]);\n\n RelatedTaskAssignedToInlineEditionDirective = function($repo, $rootscope, popoverService) {\n var link, template;\n template = _.template(\"\\\" alt=\\\"<%- name %>\\\"/>\\n<%- name %>\");\n link = function($scope, $el, $attrs) {\n var $ctrl, autoSave, notAutoSave, task, updateRelatedTask;\n updateRelatedTask = function(task) {\n var ctx, member;\n ctx = {\n name: \"Unassigned\",\n imgurl: \"/images/unnamed.png\"\n };\n member = $scope.usersById[task.assigned_to];\n if (member) {\n ctx.imgurl = member.photo;\n ctx.name = member.full_name_display;\n }\n $el.find(\".avatar\").html(template(ctx));\n return $el.find(\".task-assignedto\").attr('title', ctx.name);\n };\n $ctrl = $el.controller();\n task = $scope.$eval($attrs.tgRelatedTaskAssignedToInlineEdition);\n notAutoSave = $scope.$eval($attrs.notAutoSave);\n autoSave = !notAutoSave;\n updateRelatedTask(task);\n $el.on(\"click\", \".task-assignedto\", function(event) {\n return $rootscope.$broadcast(\"assigned-to:add\", task);\n });\n taiga.bindOnce($scope, \"project\", function(project) {\n if (project.my_permissions.indexOf(\"modify_task\") === -1) {\n $el.unbind(\"click\");\n return $el.find(\"a\").addClass(\"not-clickable\");\n }\n });\n $scope.$on(\"assigned-to:added\", debounce(2000, (function(_this) {\n return function(ctx, userId, updatedRelatedTask) {\n if (updatedRelatedTask.id === task.id) {\n updatedRelatedTask.assigned_to = userId;\n if (autoSave) {\n $repo.save(updatedRelatedTask).then(function() {\n return $scope.$emit(\"related-tasks:assigned-to-changed\");\n });\n }\n return updateRelatedTask(updatedRelatedTask);\n }\n };\n })(this)));\n return $scope.$on(\"$destroy\", function() {\n return $el.off();\n });\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgRelatedTaskAssignedToInlineEdition\", [\"$tgRepo\", \"$rootScope\", RelatedTaskAssignedToInlineEditionDirective]);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/resources.coffee\n */\n\n(function() {\n var ResourcesService, initResources, initUrls, module, taiga, urls,\n __hasProp = {}.hasOwnProperty,\n __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };\n\n taiga = this.taiga;\n\n ResourcesService = (function(_super) {\n __extends(ResourcesService, _super);\n\n function ResourcesService() {\n return ResourcesService.__super__.constructor.apply(this, arguments);\n }\n\n return ResourcesService;\n\n })(taiga.Service);\n\n urls = {\n \"auth\": \"/auth\",\n \"auth-register\": \"/auth/register\",\n \"invitations\": \"/invitations\",\n \"permissions\": \"/permissions\",\n \"roles\": \"/roles\",\n \"projects\": \"/projects\",\n \"memberships\": \"/memberships\",\n \"notify-policies\": \"/notify-policies\",\n \"bulk-create-memberships\": \"/memberships/bulk_create\",\n \"milestones\": \"/milestones\",\n \"userstories\": \"/userstories\",\n \"bulk-create-us\": \"/userstories/bulk_create\",\n \"bulk-update-us-backlog-order\": \"/userstories/bulk_update_backlog_order\",\n \"bulk-update-us-sprint-order\": \"/userstories/bulk_update_sprint_order\",\n \"bulk-update-us-kanban-order\": \"/userstories/bulk_update_kanban_order\",\n \"userstories-restore\": \"/userstories/%s/restore\",\n \"tasks\": \"/tasks\",\n \"bulk-create-tasks\": \"/tasks/bulk_create\",\n \"bulk-update-task-taskboard-order\": \"/tasks/bulk_update_taskboard_order\",\n \"tasks-restore\": \"/tasks/%s/restore\",\n \"issues\": \"/issues\",\n \"bulk-create-issues\": \"/issues/bulk_create\",\n \"issues-restore\": \"/issues/%s/restore\",\n \"wiki\": \"/wiki\",\n \"wiki-restore\": \"/wiki/%s/restore\",\n \"wiki-links\": \"/wiki-links\",\n \"choices/userstory-statuses\": \"/userstory-statuses\",\n \"choices/userstory-statuses/bulk-update-order\": \"/userstory-statuses/bulk_update_order\",\n \"choices/points\": \"/points\",\n \"choices/points/bulk-update-order\": \"/points/bulk_update_order\",\n \"choices/task-statuses\": \"/task-statuses\",\n \"choices/task-statuses/bulk-update-order\": \"/task-statuses/bulk_update_order\",\n \"choices/issue-statuses\": \"/issue-statuses\",\n \"choices/issue-statuses/bulk-update-order\": \"/issue-statuses/bulk_update_order\",\n \"choices/issue-types\": \"/issue-types\",\n \"choices/issue-types/bulk-update-order\": \"/issue-types/bulk_update_order\",\n \"choices/priorities\": \"/priorities\",\n \"choices/priorities/bulk-update-order\": \"/priorities/bulk_update_order\",\n \"choices/severities\": \"/severities\",\n \"choices/severities/bulk-update-order\": \"/severities/bulk_update_order\",\n \"search\": \"/search\",\n \"sites\": \"/sites\",\n \"project-templates\": \"/project-templates\",\n \"site-members\": \"/site-members\",\n \"site-projects\": \"/site-projects\",\n \"users\": \"/users\",\n \"users-password-recovery\": \"/users/password_recovery\",\n \"users-change-password-from-recovery\": \"/users/change_password_from_recovery\",\n \"users-change-password\": \"/users/change_password\",\n \"users-change-email\": \"/users/change_email\",\n \"users-cancel-account\": \"/users/cancel\",\n \"user-storage\": \"/user-storage\",\n \"resolver\": \"/resolver\",\n \"userstory-statuses\": \"/userstory-statuses\",\n \"points\": \"/points\",\n \"task-statuses\": \"/task-statuses\",\n \"issue-statuses\": \"/issue-statuses\",\n \"issue-types\": \"/issue-types\",\n \"priorities\": \"/priorities\",\n \"severities\": \"/severities\",\n \"project-modules\": \"/projects/%s/modules\",\n \"history/us\": \"/history/userstory\",\n \"history/issue\": \"/history/issue\",\n \"history/task\": \"/history/task\",\n \"history/wiki\": \"/history/wiki\",\n \"attachments/us\": \"/userstories/attachments\",\n \"attachments/issue\": \"/issues/attachments\",\n \"attachments/task\": \"/tasks/attachments\",\n \"attachments/wiki_page\": \"/wiki/attachments\",\n \"feedback\": \"/feedback\"\n };\n\n initUrls = function($log, $urls) {\n $log.debug(\"Initialize api urls\");\n return $urls.update(urls);\n };\n\n initResources = function($log, $rs) {\n var provider, providers, _i, _len, _results;\n $log.debug(\"Initialize resources\");\n providers = _.toArray(arguments).slice(2);\n _results = [];\n for (_i = 0, _len = providers.length; _i < _len; _i++) {\n provider = providers[_i];\n _results.push(provider($rs));\n }\n return _results;\n };\n\n module = angular.module(\"taigaResources\", [\"taigaBase\"]);\n\n module.service(\"$tgResources\", ResourcesService);\n\n module.run([\"$log\", \"$tgUrls\", initUrls]);\n\n module.run([\"$log\", \"$tgResources\", \"$tgProjectsResourcesProvider\", \"$tgMembershipsResourcesProvider\", \"$tgNotifyPoliciesResourcesProvider\", \"$tgInvitationsResourcesProvider\", \"$tgRolesResourcesProvider\", \"$tgUserSettingsResourcesProvider\", \"$tgSprintsResourcesProvider\", \"$tgUserstoriesResourcesProvider\", \"$tgTasksResourcesProvider\", \"$tgIssuesResourcesProvider\", \"$tgWikiResourcesProvider\", \"$tgSearchResourcesProvider\", \"$tgAttachmentsResourcesProvider\", \"$tgMdRenderResourcesProvider\", \"$tgHistoryResourcesProvider\", \"$tgKanbanResourcesProvider\", \"$tgModulesResourcesProvider\", initResources]);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/search.coffee\n */\n\n(function() {\n var SearchBoxDirective, SearchController, SearchDirective, bindOnce, debounce, debounceLeading, groupBy, mixOf, module, taiga, trim,\n __hasProp = {}.hasOwnProperty,\n __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };\n\n taiga = this.taiga;\n\n groupBy = this.taiga.groupBy;\n\n bindOnce = this.taiga.bindOnce;\n\n mixOf = this.taiga.mixOf;\n\n debounceLeading = this.taiga.debounceLeading;\n\n trim = this.taiga.trim;\n\n debounce = this.taiga.debounce;\n\n module = angular.module(\"taigaSearch\", []);\n\n SearchController = (function(_super) {\n __extends(SearchController, _super);\n\n SearchController.$inject = [\"$scope\", \"$tgRepo\", \"$tgResources\", \"$routeParams\", \"$q\", \"$tgLocation\", \"$appTitle\", \"$tgNavUrls\", \"tgLoader\"];\n\n function SearchController(scope, repo, rs, params, q, location, appTitle, navUrls, tgLoader) {\n var loadSearchData, promise;\n this.scope = scope;\n this.repo = repo;\n this.rs = rs;\n this.params = params;\n this.q = q;\n this.location = location;\n this.appTitle = appTitle;\n this.navUrls = navUrls;\n this.tgLoader = tgLoader;\n this.scope.sectionName = \"Search\";\n promise = this.loadInitialData();\n promise.then((function(_this) {\n return function() {\n return _this.appTitle.set(\"Search\");\n };\n })(this));\n promise.then(null, this.onInitialDataError.bind(this));\n this.scope.searchTerm = \"\";\n loadSearchData = debounceLeading(100, (function(_this) {\n return function(t) {\n return _this.loadSearchData(t);\n };\n })(this));\n this.scope.$watch(\"searchTerm\", (function(_this) {\n return function(term) {\n if (!term) {\n return _this.tgLoader.pageLoaded();\n } else {\n return loadSearchData(term);\n }\n };\n })(this));\n }\n\n SearchController.prototype.loadFilters = function() {\n var defered;\n defered = this.q.defer();\n defered.resolve();\n return defered.promise;\n };\n\n SearchController.prototype.loadProject = function() {\n return this.rs.projects.getBySlug(this.params.pslug).then((function(_this) {\n return function(project) {\n _this.scope.project = project;\n _this.scope.$emit('project:loaded', project);\n _this.scope.issueStatusById = groupBy(project.issue_statuses, function(x) {\n return x.id;\n });\n _this.scope.taskStatusById = groupBy(project.task_statuses, function(x) {\n return x.id;\n });\n _this.scope.severityById = groupBy(project.severities, function(x) {\n return x.id;\n });\n _this.scope.priorityById = groupBy(project.priorities, function(x) {\n return x.id;\n });\n _this.scope.membersById = groupBy(project.memberships, function(x) {\n return x.user;\n });\n _this.scope.usStatusById = groupBy(project.us_statuses, function(x) {\n return x.id;\n });\n return project;\n };\n })(this));\n };\n\n SearchController.prototype.loadSearchData = function(term) {\n var promise;\n promise = this.rs.search[\"do\"](this.scope.projectId, term).then((function(_this) {\n return function(data) {\n _this.scope.searchResults = data;\n return data;\n };\n })(this));\n promise[\"finally\"]((function(_this) {\n return function() {\n return _this.tgLoader.pageLoaded();\n };\n })(this));\n return promise;\n };\n\n SearchController.prototype.loadInitialData = function() {\n return this.loadProject().then((function(_this) {\n return function(project) {\n _this.scope.projectId = project.id;\n return _this.fillUsersAndRoles(project.users, project.roles);\n };\n })(this));\n };\n\n return SearchController;\n\n })(mixOf(taiga.Controller, taiga.PageMixin));\n\n module.controller(\"SearchController\", SearchController);\n\n SearchBoxDirective = function($lightboxService, $navurls, $location, $route) {\n var link;\n link = function($scope, $el, $attrs) {\n var project, submit;\n project = null;\n submit = debounce(2000, (function(_this) {\n return function(event) {\n var form, text, url;\n event.preventDefault();\n form = $el.find(\"form\").checksley();\n if (!form.validate()) {\n return;\n }\n text = $el.find(\"#search-text\").val();\n url = $navurls.resolve(\"project-search\", {\n project: project.slug\n });\n $lightboxService.close($el);\n return $scope.$apply(function() {\n $location.path(url);\n $location.search(\"text\", text).path(url);\n return $route.reload();\n });\n };\n })(this));\n $scope.$on(\"search-box:show\", function(ctx, newProject) {\n project = newProject;\n $lightboxService.open($el);\n return $el.find(\"#search-text\").val(\"\");\n });\n $el.on(\"submit\", \"form\", submit);\n return $el.on(\"click\", \".submit-button\", submit);\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgSearchBox\", [\"lightboxService\", \"$tgNavUrls\", \"$tgLocation\", \"$route\", SearchBoxDirective]);\n\n SearchDirective = function($log, $compile, $templatecache, $routeparams, $location) {\n var link, linkTable;\n linkTable = function($scope, $el, $attrs, $ctrl) {\n var getActiveSection, lastSeatchResults, markSectionTabActive, renderFilterTabs, renderTableContent, tabsDom, templates;\n tabsDom = $el.find(\"section.search-filter\");\n lastSeatchResults = null;\n getActiveSection = function(data) {\n var maxVal, name, selectedSectionData, selectedSectionName, value;\n maxVal = 0;\n selectedSectionName = null;\n selectedSectionData = null;\n for (name in data) {\n value = data[name];\n if (name === \"count\") {\n continue;\n }\n if (value.length > maxVal) {\n maxVal = value.length;\n selectedSectionName = name;\n selectedSectionData = value;\n }\n }\n if (maxVal === 0) {\n return {\n name: \"userstories\",\n value: []\n };\n }\n return {\n name: selectedSectionName,\n value: selectedSectionData\n };\n };\n renderFilterTabs = function(data) {\n var name, value, _results;\n _results = [];\n for (name in data) {\n value = data[name];\n if (name === \"count\") {\n continue;\n }\n _results.push(tabsDom.find(\"li.\" + name + \" .num\").html(value.length));\n }\n return _results;\n };\n markSectionTabActive = function(section) {\n tabsDom.find(\"a.active\").removeClass(\"active\");\n return tabsDom.find(\"li.\" + section.name + \" a\").addClass(\"active\");\n };\n templates = {\n issues: $templatecache.get(\"search-issues\"),\n tasks: $templatecache.get(\"search-tasks\"),\n userstories: $templatecache.get(\"search-userstories\"),\n wikipages: $templatecache.get(\"search-wikipages\")\n };\n renderTableContent = function(section) {\n var element, oldElements, oldScope, scope, template;\n oldElements = $el.find(\".search-result-table\").children();\n oldScope = oldElements.scope();\n if (oldScope) {\n oldScope.$destroy();\n oldElements.remove();\n }\n scope = $scope.$new();\n scope[section.name] = section.value;\n template = angular.element.parseHTML(trim(templates[section.name]));\n element = $compile(template)(scope);\n return $el.find(\".search-result-table\").html(element);\n };\n $scope.$watch(\"searchResults\", function(data) {\n var activeSection;\n lastSeatchResults = data;\n activeSection = getActiveSection(data);\n renderFilterTabs(data);\n renderTableContent(activeSection);\n return markSectionTabActive(activeSection);\n });\n $scope.$watch(\"searchTerm\", function(searchTerm) {\n if (searchTerm) {\n return $location.search(\"text\", searchTerm);\n }\n });\n return $el.on(\"click\", \".search-filter li > a\", function(event) {\n var section, sectionData, sectionName, target;\n event.preventDefault();\n target = angular.element(event.currentTarget);\n sectionName = target.parent().data(\"name\");\n sectionData = lastSeatchResults[sectionName];\n section = {\n name: sectionName,\n value: sectionData\n };\n return $scope.$apply(function() {\n renderTableContent(section);\n return markSectionTabActive(section);\n });\n });\n };\n link = function($scope, $el, $attrs) {\n var $ctrl, searchText;\n $ctrl = $el.controller();\n linkTable($scope, $el, $attrs, $ctrl);\n searchText = $routeparams.text;\n return $scope.$watch(\"projectId\", function(projectId) {\n if (projectId != null) {\n return $scope.searchTerm = searchText;\n }\n });\n };\n return {\n link: link\n };\n };\n\n module.directive(\"tgSearch\", [\"$log\", \"$compile\", \"$templateCache\", \"$routeParams\", \"$tgLocation\", SearchDirective]);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/taskboard.coffee\n */\n\n(function() {\n var module;\n\n module = angular.module(\"taigaTaskboard\", []);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/tasks.coffee\n */\n\n(function() {\n var module;\n\n module = angular.module(\"taigaTasks\", []);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/team.coffee\n */\n\n(function() {\n var module;\n\n module = angular.module(\"taigaTeam\", []);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/user-settings.coffee\n */\n\n(function() {\n var module;\n\n module = angular.module(\"taigaUserSettings\", []);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/userstories.coffee\n */\n\n(function() {\n var module;\n\n module = angular.module(\"taigaUserStories\", []);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/wiki.coffee\n */\n\n(function() {\n var module;\n\n module = angular.module(\"taigaWiki\", []);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/common/analytics.coffee\n */\n\n(function() {\n var AnalyticsService, module, taiga,\n __hasProp = {}.hasOwnProperty,\n __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };\n\n taiga = this.taiga;\n\n module = angular.module(\"taigaCommon\");\n\n AnalyticsService = (function(_super) {\n __extends(AnalyticsService, _super);\n\n AnalyticsService.$inject = [\"$rootScope\", \"$log\", \"$tgConfig\", \"$window\", \"$document\", \"$location\"];\n\n function AnalyticsService(rootscope, log, config, win, doc, location) {\n var conf;\n this.rootscope = rootscope;\n this.log = log;\n this.config = config;\n this.win = win;\n this.doc = doc;\n this.location = location;\n this.initialized = false;\n conf = this.config.get(\"analytics\", {});\n this.accountId = conf.accountId;\n this.pageEvent = conf.pageEvent || \"$routeChangeSuccess\";\n this.trackRoutes = conf.trackRoutes || true;\n this.ignoreFirstPageLoad = conf.ignoreFirstPageLoad || false;\n }\n\n AnalyticsService.prototype.initialize = function() {\n if (!this.accountId) {\n this.log.debug(\"Analytics: no acount id provided. Disabling.\");\n return;\n }\n this.injectAnalytics();\n this.win.ga(\"create\", this.accountId, \"auto\");\n this.win.ga(\"require\", \"displayfeatures\");\n if (this.trackRoutes && (!this.ignoreFirstPageLoad)) {\n this.win.ga(\"send\", \"pageview\", this.getUrl());\n }\n if (this.trackRoutes) {\n this.rootscope.$on(this.pageEvent, (function(_this) {\n return function() {\n return _this.trackPage(_this.getUrl(), \"Taiga\");\n };\n })(this));\n }\n return this.initialized = true;\n };\n\n AnalyticsService.prototype.getUrl = function() {\n return this.location.path();\n };\n\n AnalyticsService.prototype.injectAnalytics = function() {\n var fn;\n fn = (function(i,s,o,g,r,a,m){i[\"GoogleAnalyticsObject\"]=r;i[r]=i[r]||function(){\n (i[r].q=i[r].q||[]).push(arguments);},i[r].l=1*new Date();a=s.createElement(o),\n m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m);});\n return fn(window, document, \"script\", \"//www.google-analytics.com/analytics.js\", \"ga\");\n };\n\n AnalyticsService.prototype.trackPage = function(url, title) {\n if (!this.initialized) {\n return;\n }\n if (!this.win.ga) {\n return;\n }\n title = title || this.doc[0].title;\n return this.win.ga(\"send\", \"pageview\", {\n \"page\": url,\n \"title\": title\n });\n };\n\n AnalyticsService.prototype.trackEvent = function(category, action, label, value) {\n if (!this.initialized) {\n return;\n }\n if (!this.win.ga) {\n return;\n }\n return this.win.ga(\"send\", \"event\", category, action, label, value);\n };\n\n return AnalyticsService;\n\n })(taiga.Service);\n\n module.service(\"$tgAnalytics\", AnalyticsService);\n\n}).call(this);\n\n\n/*\n * Copyright (C) 2014 Andrey Antukh \n * Copyright (C) 2014 Jesús Espino Garcia \n * Copyright (C) 2014 David Barragán Merino \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n * File: modules/common/attachments.coffee\n */\n\n(function() {\n var AttachmentDirective, AttachmentsController, AttachmentsDirective, bindMethods, bindOnce, module, sizeFormat, taiga,\n __hasProp = {}.hasOwnProperty,\n __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };\n\n taiga = this.taiga;\n\n sizeFormat = this.taiga.sizeFormat;\n\n bindOnce = this.taiga.bindOnce;\n\n bindMethods = this.taiga.bindMethods;\n\n module = angular.module(\"taigaCommon\");\n\n AttachmentsController = (function(_super) {\n __extends(AttachmentsController, _super);\n\n AttachmentsController.$inject = [\"$scope\", \"$rootScope\", \"$tgRepo\", \"$tgResources\", \"$tgConfirm\", \"$q\"];\n\n function AttachmentsController(scope, rootscope, repo, rs, confirm, q) {\n this.scope = scope;\n this.rootscope = rootscope;\n this.repo = repo;\n this.rs = rs;\n this.confirm = confirm;\n this.q = q;\n bindMethods(this);\n this.type = null;\n this.objectId = null;\n this.projectId = null;\n this.uploadingAttachments = [];\n this.attachments = [];\n this.attachmentsCount = 0;\n this.deprecatedAttachmentsCount = 0;\n this.showDeprecated = false;\n }\n\n AttachmentsController.prototype.initialize = function(type, objectId) {\n this.type = type;\n this.objectId = objectId;\n return this.projectId = this.scope.projectId;\n };\n\n AttachmentsController.prototype.loadAttachments = function() {\n var urlname;\n if (!this.objectId) {\n return this.attachments;\n }\n urlname = \"attachments/\" + this.type;\n return this.rs.attachments.list(urlname, this.objectId, this.projectId).then((function(_this) {\n return function(attachments) {\n _this.attachments = _.sortBy(attachments, \"order\");\n _this.updateCounters();\n return attachments;\n };\n })(this));\n };\n\n AttachmentsController.prototype.updateCounters = function() {\n this.attachmentsCount = this.attachments.length;\n return this.deprecatedAttachmentsCount = _.filter(this.attachments, {\n is_deprecated: true\n }).length;\n };\n\n AttachmentsController.prototype._createAttachment = function(attachment) {\n var promise, urlName;\n urlName = \"attachments/\" + this.type;\n promise = this.rs.attachments.create(urlName, this.projectId, this.objectId, attachment);\n promise = promise.then((function(_this) {\n return function(data) {\n var index;\n data.isCreatedRightNow = true;\n index = _this.uploadingAttachments.indexOf(attachment);\n _this.uploadingAttachments.splice(index, 1);\n _this.attachments.push(data);\n return _this.rootscope.$broadcast(\"attachment:create\");\n };\n })(this));\n promise = promise.then(null, (function(_this) {\n return function(data) {\n var index;\n if (data.status === 413) {\n _this.scope.$emit(\"attachments:size-error\");\n }\n index = _this.uploadingAttachments.indexOf(attachment);\n _this.uploadingAttachments.splice(index, 1);\n _this.confirm.notify(\"error\", \"We have not been able to upload '\" + attachment.name + \"'. \" + data.data._error_message);\n return _this.q.reject(data);\n };\n })(this));\n return promise;\n };\n\n AttachmentsController.prototype.createAttachments = function(attachments) {\n var promises;\n promises = _.map(attachments, (function(_this) {\n return function(x) {\n return _this._createAttachment(x);\n };\n })(this));\n return this.q.all(promises).then((function(_this) {\n return function() {\n return _this.updateCounters();\n };\n })(this));\n };\n\n AttachmentsController.prototype.addUploadingAttachments = function(attachments) {\n return this.uploadingAttachments = _.union(this.uploadingAttachments, attachments);\n };\n\n AttachmentsController.prototype.reorderAttachment = function(attachment, newIndex) {\n var oldIndex;\n oldIndex = this.attachments.indexOf(attachment);\n if (oldIndex === newIndex) {\n return;\n }\n this.attachments.splice(oldIndex, 1);\n this.attachments.splice(newIndex, 0, attachment);\n return _.each(this.attachments, function(x, i) {\n return x.order = i + 1;\n });\n };\n\n AttachmentsController.prototype.updateAttachment = function(attachment) {\n var onError, onSuccess;\n onSuccess = (function(_this) {\n return function() {\n _this.updateCounters();\n return _this.rootscope.$broadcast(\"attachment:edit\");\n };\n })(this);\n onError = (function(_this) {\n return function(response) {\n if (response.status === 413) {\n $scope.$emit(\"attachments:size-error\");\n }\n _this.confirm.notify(\"error\");\n return _this.q.reject();\n };\n })(this);\n return this.repo.save(attachment).then(onSuccess, onError);\n };\n\n AttachmentsController.prototype.saveAttachments = function() {\n return this.repo.saveAll(this.attachments).then(null, (function(_this) {\n return function() {\n var item, _i, _len, _ref;\n _ref = _this.attachments;\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n item = _ref[_i];\n item.revert();\n }\n return _this.attachments = _.sorBy(_this.attachments, \"order\");\n };\n })(this));\n };\n\n AttachmentsController.prototype.removeAttachment = function(attachment) {\n var message, title;\n title = \"Delete attachment\";\n message = \"the attachment '\" + attachment.name + \"'\";\n return this.confirm.askOnDelete(title, message).then((function(_this) {\n return function(finish) {\n var onError, onSuccess;\n onSuccess = function() {\n var index;\n finish();\n index = _this.attachments.indexOf(attachment);\n _this.attachments.splice(index, 1);\n _this.updateCounters();\n return _this.rootscope.$broadcast(\"attachment:delete\");\n };\n onError = function() {\n finish(false);\n _this.confirm.notify(\"error\", null, \"We have not been able to delete \" + message + \".\");\n return _this.q.reject();\n };\n return _this.repo.remove(attachment).then(onSuccess, onError);\n };\n })(this));\n };\n\n AttachmentsController.prototype.filterAttachments = function(item) {\n if (this.showDeprecated) {\n return true;\n }\n return !item.is_deprecated;\n };\n\n return AttachmentsController;\n\n })(taiga.Controller);\n\n AttachmentsDirective = function($config, $confirm) {\n var link, template, templateFn;\n template = _.template(\"\\n