diff --git a/README.md b/README.md index 352e8c3d..c532a834 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # assignment_djello Project management with that great wobbly taste. + +[Alice](https://github.com/aliceFung/assignment_djello) & [Olga](https://github.com/ayva/assignment_djello) \ No newline at end of file diff --git a/djello/.gitignore b/djello/.gitignore new file mode 100644 index 00000000..050c9d95 --- /dev/null +++ b/djello/.gitignore @@ -0,0 +1,17 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-journal + +# Ignore all logfiles and tempfiles. +/log/* +!/log/.keep +/tmp diff --git a/djello/Gemfile b/djello/Gemfile new file mode 100644 index 00000000..1e767e6d --- /dev/null +++ b/djello/Gemfile @@ -0,0 +1,68 @@ +source 'https://rubygems.org' + + +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '4.2.3' + +gem 'pg' +# Use sqlite3 as the database for Active Record +# gem 'sqlite3' +# Use SCSS for stylesheets +gem 'sass-rails', '~> 5.0' +# Use Uglifier as compressor for JavaScript assets +gem 'uglifier', '>= 1.3.0' +# Use CoffeeScript for .coffee assets and views +gem 'coffee-rails', '~> 4.1.0' +# See https://github.com/rails/execjs#readme for more supported runtimes +# gem 'therubyracer', platforms: :ruby + +# Use jquery as the JavaScript library +gem 'jquery-rails' +# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks +gem 'turbolinks' +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +gem 'jbuilder', '~> 2.0' +# bundle exec rake doc:rails generates the API under doc/api. +gem 'sdoc', '~> 0.4.0', group: :doc + +#angular front-end gem dependencies +gem 'angularjs-rails' +gem 'angular_rails_csrf' + +gem 'faker' +gem 'devise' + +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# Use Unicorn as the app server +# gem 'unicorn' + +# Use Capistrano for deployment +# gem 'capistrano-rails', group: :development + +group :development, :test do + gem 'pry-byebug' + gem 'rspec-rails' + gem 'factory_girl_rails' + gem 'guard-rspec' + + gem 'better_errors' + gem 'binding_of_caller' + gem 'jazz_hands', github: 'nixme/jazz_hands', branch: 'bring-your-own-debugger' + + + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + # gem 'byebug' + + # Access an IRB console on exception pages or by using <%= console %> in views + gem 'web-console', '~> 2.0' + + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' +end + +source "https://rails-assets.org" do + gem "rails-assets-angular-devise" +end + diff --git a/djello/Gemfile.lock b/djello/Gemfile.lock new file mode 100644 index 00000000..066ed47b --- /dev/null +++ b/djello/Gemfile.lock @@ -0,0 +1,301 @@ +GIT + remote: git://github.com/nixme/jazz_hands.git + revision: 5e4b48f145883ecb14b55bf04eacc28ac9662676 + branch: bring-your-own-debugger + specs: + jazz_hands (0.5.2) + awesome_print (~> 1.2) + coolline (>= 0.4.2) + hirb (~> 0.7.1) + pry (~> 0.9.12) + pry-doc (~> 0.4.6) + pry-git (~> 0.2.3) + pry-rails (~> 0.3.2) + pry-remote (>= 0.1.7) + pry-stack_explorer (~> 0.4.9) + railties (>= 3.0, < 5.0) + +GEM + remote: https://rubygems.org/ + remote: https://rails-assets.org/ + specs: + actionmailer (4.2.3) + actionpack (= 4.2.3) + actionview (= 4.2.3) + activejob (= 4.2.3) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 1.0, >= 1.0.5) + actionpack (4.2.3) + actionview (= 4.2.3) + activesupport (= 4.2.3) + rack (~> 1.6) + rack-test (~> 0.6.2) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (4.2.3) + activesupport (= 4.2.3) + builder (~> 3.1) + erubis (~> 2.7.0) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + activejob (4.2.3) + activesupport (= 4.2.3) + globalid (>= 0.3.0) + activemodel (4.2.3) + activesupport (= 4.2.3) + builder (~> 3.1) + activerecord (4.2.3) + activemodel (= 4.2.3) + activesupport (= 4.2.3) + arel (~> 6.0) + activesupport (4.2.3) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + angular_rails_csrf (1.0.4) + rails (>= 3, < 5) + angularjs-rails (1.4.4) + arel (6.0.3) + awesome_print (1.6.1) + bcrypt (3.1.10) + better_errors (2.1.1) + coderay (>= 1.0.0) + erubis (>= 2.6.6) + rack (>= 0.9.0) + binding_of_caller (0.7.2) + debug_inspector (>= 0.0.1) + builder (3.2.2) + byebug (2.7.0) + columnize (~> 0.3) + debugger-linecache (~> 1.2) + coderay (1.1.0) + coffee-rails (4.1.0) + coffee-script (>= 2.2.0) + railties (>= 4.0.0, < 5.0) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.9.1.1) + columnize (0.9.0) + coolline (0.5.0) + unicode_utils (~> 1.4) + debug_inspector (0.0.2) + debugger-linecache (1.2.0) + devise (3.5.2) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 3.2.6, < 5) + responders + thread_safe (~> 0.1) + warden (~> 1.2.3) + diff-lcs (1.2.5) + diffy (3.0.7) + erubis (2.7.0) + execjs (2.6.0) + factory_girl (4.5.0) + activesupport (>= 3.0.0) + factory_girl_rails (4.5.0) + factory_girl (~> 4.5.0) + railties (>= 3.0.0) + faker (1.5.0) + i18n (~> 0.5) + ffi (1.9.10) + formatador (0.2.5) + globalid (0.3.6) + activesupport (>= 4.1.0) + grit (2.0.0) + diff-lcs (>= 1.1.2) + mime-types (>= 1.15) + guard (2.13.0) + formatador (>= 0.2.4) + listen (>= 2.7, <= 4.0) + lumberjack (~> 1.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.9.12) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-rspec (4.6.4) + guard (~> 2.1) + guard-compat (~> 1.1) + rspec (>= 2.99.0, < 4.0) + hirb (0.7.3) + i18n (0.7.0) + jbuilder (2.3.1) + activesupport (>= 3.0.0, < 5) + multi_json (~> 1.2) + jquery-rails (4.0.5) + rails-dom-testing (~> 1.0) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + json (1.8.3) + listen (3.0.3) + rb-fsevent (>= 0.9.3) + rb-inotify (>= 0.9) + loofah (2.0.3) + nokogiri (>= 1.5.9) + lumberjack (1.0.9) + mail (2.6.3) + mime-types (>= 1.16, < 3) + method_source (0.8.2) + mime-types (2.6.2) + mini_portile (0.6.2) + minitest (5.8.0) + multi_json (1.11.2) + nenv (0.2.0) + nokogiri (1.6.6.2) + mini_portile (~> 0.6.0) + notiffany (0.0.8) + nenv (~> 0.1) + shellany (~> 0.0) + orm_adapter (0.5.0) + pg (0.18.3) + pry (0.9.12.6) + coderay (~> 1.0) + method_source (~> 0.8) + slop (~> 3.4) + pry-byebug (1.3.2) + byebug (~> 2.7) + pry (~> 0.9.12) + pry-doc (0.4.6) + pry (>= 0.9) + yard (>= 0.8) + pry-git (0.2.3) + diffy + grit + pry (>= 0.9.8) + pry-rails (0.3.4) + pry (>= 0.9.10) + pry-remote (0.1.8) + pry (~> 0.9) + slop (~> 3.0) + pry-stack_explorer (0.4.9.2) + binding_of_caller (>= 0.7) + pry (>= 0.9.11) + rack (1.6.4) + rack-test (0.6.3) + rack (>= 1.0) + rails (4.2.3) + actionmailer (= 4.2.3) + actionpack (= 4.2.3) + actionview (= 4.2.3) + activejob (= 4.2.3) + activemodel (= 4.2.3) + activerecord (= 4.2.3) + activesupport (= 4.2.3) + bundler (>= 1.3.0, < 2.0) + railties (= 4.2.3) + sprockets-rails + rails-assets-angular (1.4.6) + rails-assets-angular-devise (1.1.0) + rails-assets-angular (>= 1.2.0, < 2) + rails-deprecated_sanitizer (1.0.3) + activesupport (>= 4.2.0.alpha) + rails-dom-testing (1.0.7) + activesupport (>= 4.2.0.beta, < 5.0) + nokogiri (~> 1.6.0) + rails-deprecated_sanitizer (>= 1.0.1) + rails-html-sanitizer (1.0.2) + loofah (~> 2.0) + railties (4.2.3) + actionpack (= 4.2.3) + activesupport (= 4.2.3) + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (10.4.2) + rb-fsevent (0.9.6) + rb-inotify (0.9.5) + ffi (>= 0.5.0) + rdoc (4.2.0) + responders (2.1.0) + railties (>= 4.2.0, < 5) + rspec (3.3.0) + rspec-core (~> 3.3.0) + rspec-expectations (~> 3.3.0) + rspec-mocks (~> 3.3.0) + rspec-core (3.3.2) + rspec-support (~> 3.3.0) + rspec-expectations (3.3.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.3.0) + rspec-mocks (3.3.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.3.0) + rspec-rails (3.3.3) + actionpack (>= 3.0, < 4.3) + activesupport (>= 3.0, < 4.3) + railties (>= 3.0, < 4.3) + rspec-core (~> 3.3.0) + rspec-expectations (~> 3.3.0) + rspec-mocks (~> 3.3.0) + rspec-support (~> 3.3.0) + rspec-support (3.3.0) + sass (3.4.18) + sass-rails (5.0.4) + railties (>= 4.0.0, < 5.0) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + sdoc (0.4.1) + json (~> 1.7, >= 1.7.7) + rdoc (~> 4.0) + shellany (0.0.1) + slop (3.6.0) + spring (1.4.0) + sprockets (3.3.4) + rack (~> 1.0) + sprockets-rails (2.3.3) + actionpack (>= 3.0) + activesupport (>= 3.0) + sprockets (>= 2.8, < 4.0) + thor (0.19.1) + thread_safe (0.3.5) + tilt (2.0.1) + turbolinks (2.5.3) + coffee-rails + tzinfo (1.2.2) + thread_safe (~> 0.1) + uglifier (2.7.2) + execjs (>= 0.3.0) + json (>= 1.8.0) + unicode_utils (1.4.0) + warden (1.2.3) + rack (>= 1.0) + web-console (2.2.1) + activemodel (>= 4.0) + binding_of_caller (>= 0.7.2) + railties (>= 4.0) + sprockets-rails (>= 2.0, < 4.0) + yard (0.8.7.6) + +PLATFORMS + ruby + +DEPENDENCIES + angular_rails_csrf + angularjs-rails + better_errors + binding_of_caller + coffee-rails (~> 4.1.0) + devise + factory_girl_rails + faker + guard-rspec + jazz_hands! + jbuilder (~> 2.0) + jquery-rails + pg + pry-byebug + rails (= 4.2.3) + rails-assets-angular-devise! + rspec-rails + sass-rails (~> 5.0) + sdoc (~> 0.4.0) + spring + turbolinks + uglifier (>= 1.3.0) + web-console (~> 2.0) diff --git a/djello/Rakefile b/djello/Rakefile new file mode 100644 index 00000000..ba6b733d --- /dev/null +++ b/djello/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require File.expand_path('../config/application', __FILE__) + +Rails.application.load_tasks diff --git a/djello/app/assets/images/.keep b/djello/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/djello/app/assets/javascripts/app.js b/djello/app/assets/javascripts/app.js new file mode 100644 index 00000000..5b7ecc19 --- /dev/null +++ b/djello/app/assets/javascripts/app.js @@ -0,0 +1,91 @@ +var djello = angular.module('djello', ['restangular', 'ui.router', 'Devise', 'angularModalService']); + +djello.config(['RestangularProvider', function(RestangularProvider){ + RestangularProvider.setBaseUrl('/api/v1'); + RestangularProvider.setRequestSuffix('.json'); + RestangularProvider.setDefaultHttpFields({ + "content-type": "application/json" + }); +}]); + +djello.config(function(AuthProvider) { + + AuthProvider.loginPath('api/v1/users/sign_in.json'); + + AuthProvider.logoutPath('api/v1/users/sign_out.json'); + }); + +djello.config(['$urlRouterProvider', '$stateProvider', + function($urlRouterProvider, $stateProvider){ + $stateProvider + .state('header', { + url: '/', + views: { + 'header': { + templateUrl: 'templates/headerLayout.html', + controller: 'headerCtrl' + }, + '' : { + templateUrl: 'templates/loginForm.html', + controller: 'headerCtrl', + } + } + }) + + .state('board', { + url: '/board', + controller: 'boardCtrl', + resolve:{ + 'boards': function($location, loginService, dataService){ + if(loginService.signedInUser.user){ + //get board data + return dataService.boards; + + }else{ + $location.path('/#/'); //redirect login + alert("You don't have access here"); + } + } + }, + + views: { + 'header': { + templateUrl: 'templates/loggedInHeader.html', + controller: 'headerCtrl' + }, + '' : { + templateUrl: 'templates/boardLayout.html', + controller: 'boardCtrl' + } + } + }) + + .state('board.show', { + url: '/:id', + templateUrl: 'templates/boardShow.html', + controller: 'boardShowCtrl', + resolve: { + 'showresponse': function(Restangular, $stateParams, loginService, dataService){ + + if(dataService.checkBoardOwner( $stateParams.id)){ + return Restangular.one('boards', $stateParams.id).get(); + } else { + $location.path('/#/'); //redirect login + alert("You don't have access to this board"); + } + }, + 'users': ['Restangular', function(Restangular){ + return Restangular.all('users').getList(); + }] + } + //server request to validate board owner ok + }); + + + }]); + +//for errors + +djello.run(function($rootScope){ + $rootScope.$on("$stateChangeError", console.log.bind(console)); +}); \ No newline at end of file diff --git a/djello/app/assets/javascripts/application.js b/djello/app/assets/javascripts/application.js new file mode 100644 index 00000000..fbf206d0 --- /dev/null +++ b/djello/app/assets/javascripts/application.js @@ -0,0 +1,24 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. +// +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details +// about supported directives. +// +//= require jquery +//= require jquery_ujs +//= require angular +//= require angular-modal-service.min.js +//= require angular-ui-router.min.js + +//= require devise-min.js + +//= require underscore + +//= require restangular +//= require_tree . diff --git a/djello/app/assets/javascripts/boards.coffee b/djello/app/assets/javascripts/boards.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/djello/app/assets/javascripts/boards.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/djello/app/assets/javascripts/cards.coffee b/djello/app/assets/javascripts/cards.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/djello/app/assets/javascripts/cards.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/djello/app/assets/javascripts/controllers/angularCtrl.js b/djello/app/assets/javascripts/controllers/angularCtrl.js new file mode 100644 index 00000000..fd055da1 --- /dev/null +++ b/djello/app/assets/javascripts/controllers/angularCtrl.js @@ -0,0 +1,3 @@ +djello.controller('angularCtrl', ['$scope', function($scope){ + $scope.testVar = 'testing'; +}]); diff --git a/djello/app/assets/javascripts/controllers/boardCtrl.js b/djello/app/assets/javascripts/controllers/boardCtrl.js new file mode 100644 index 00000000..ab93bfac --- /dev/null +++ b/djello/app/assets/javascripts/controllers/boardCtrl.js @@ -0,0 +1,28 @@ +djello.controller('boardCtrl', + ['$scope', '$location', 'loginService', 'boards', 'Restangular', 'dataService', + function($scope, $location, loginService, boards, Restangular, dataService){ + + $scope.user = loginService.signedInUser.user; + + // redirect back to root if not signed in + // if(!$scope.user.user) $location.path('/'); + + $scope.boards = boards.allBoards; + console.log("boards in ctrl", $scope.boards); + + + + // creates default board w/o title + + $scope.createBoard = function(){ + console.log('board create'); + Restangular.all('boards').post( + { board: { title: 'Default Board Title' , + user_id: $scope.user.id }}) + .then(function(createdBoard){ + // $scope.boards.push(createdBoard); + dataService.boards.allBoards.push(createdBoard); + }); + }; + +}]); diff --git a/djello/app/assets/javascripts/controllers/boardShowCtrl.js b/djello/app/assets/javascripts/controllers/boardShowCtrl.js new file mode 100644 index 00000000..051a32b3 --- /dev/null +++ b/djello/app/assets/javascripts/controllers/boardShowCtrl.js @@ -0,0 +1,132 @@ +djello.controller('boardShowCtrl', + ['$scope', '$location', '$stateParams','$document','loginService', 'showresponse', 'Restangular', 'dataService', 'ModalService', 'users', + function($scope, $location, $stateParams, $document, loginService, showresponse, Restangular, dataService, ModalService, users){ + + // ==============initial variable settings =============== + + console.log("boardShowCtrl initiated"); + $scope.user = loginService.signedInUser.user; + + $scope.board = JSON.parse(showresponse.board); + $scope.lists = JSON.parse(showresponse.lists); + $scope.users = users; + // ==============all board methods=============== + + $scope.deleteBoard = function(){ + Restangular.one('boards', $scope.board.id).remove(); + dataService.deleteBoard($scope.board); + $location.path('/board'); + }; + + var oldTitle = ""; + + $scope.editorBoardTitle = function(input){ + if (input == 'cancel' && $scope.BoardTitleEnabled) { + $scope.board.title = oldTitle; + } + else if (input == 'saved' && $scope.BoardTitleEnabled){ + var oldboard = Restangular.one('boards', $scope.board.id); + oldboard.title = $scope.board.title; + oldboard.put().then( + dataService.updateBoard(oldboard) + ); + } + oldTitle = $scope.board.title; + $scope.BoardTitleEnabled=!$scope.BoardTitleEnabled; + + }; + + // ==============all list methods=============== + var oldList = {}; + $scope.ListEditEnabled = {}; + + $scope.editorListTitle = function(index, input){ + if (input == 'cancel' && $scope.ListEditEnabled[index]) { + $scope.lists[index].title = oldList.title; + $scope.lists[index].description = oldList.description; + } + else if (input == 'saved' && $scope.ListEditEnabled[index]){ + oldList = Restangular.one('lists', $scope.lists[index].id); + oldList.title = $scope.lists[index].title; + oldList.description = $scope.lists[index].description; + oldList.put(); + } + if (Object.keys($scope.ListEditEnabled)[0] != index){ + $scope.ListEditEnabled = {}; + } + oldList = { id: $scope.lists[index].id, + title: $scope.lists[index].title, + description: $scope.lists[index].description}; + $scope.ListEditEnabled[index]=!$scope.ListEditEnabled[index]; + + }; + + $scope.deleteList= function(index){ + Restangular.one('lists', $scope.lists[index].id).remove(); + $scope.lists.splice(index, 1); + }; + + $scope.newList = function(){ + console.log('list create'); + Restangular.all('lists').post( + { list: { title: 'Blank List' , + description: 'insert description here', + board_id: $scope.board.id }}) + .then(function(createdList){ + createdList.cards = []; + $scope.lists.push(createdList); + }); + }; + + // $scope.escapeEvent = function(e) { + // debugger; + // if (e.which == 27) closeEdit(); + // }; + + // console.log("lists", $scope.lists); + // ==============all card methods=============== + + $scope.newCard = function(index){ + var listId = $scope.lists[index].id; + Restangular.one('lists', listId).all('cards').post( + { card: { title: 'Blank Card' , + description: 'card task details here', + list_id: listId }}) + .then(function(createdCard){ + $scope.lists[index].cards.push(createdCard); + }); + }; + + var findList = function (listId){ + for(var i =0; i < $scope.lists.length; i++){ + if($scope.lists[i].id == listId){ + return $scope.lists[i]; + } + } + }; + + $scope.editCard = function(card, idxInList){ + list = findList(card.list_id); + + ModalService.showModal({ + templateUrl: "/templates/cardModal.html", + controller: "cardModalCtrl", + inputs: { + card: card, + list: list, + idxInList: idxInList, + users: $scope.users + } + }).then(function(modal) { + //it's a bootstrap element, use 'modal' to show it + modal.element.modal(); + modal.close.then(function(result) { + console.log(result); + // no info sent back from close fn b/c data binding did it + }); + }); + }; + +}]); + + diff --git a/djello/app/assets/javascripts/controllers/cardModalCtrl.js b/djello/app/assets/javascripts/controllers/cardModalCtrl.js new file mode 100644 index 00000000..a2d6101f --- /dev/null +++ b/djello/app/assets/javascripts/controllers/cardModalCtrl.js @@ -0,0 +1,110 @@ + +djello.controller('cardModalCtrl', [ + '$scope', '$element', 'Restangular', 'card', 'list', 'users', 'idxInList', 'close', + function($scope, $element, Restangular, card, list, users, idxInList, close) { + + + $scope.card = card; + $scope.list = list; + $scope.editCardEnabled = {}; + + //get users for adding members + // Restangular.all('users').getList().then(function(result){ + // console.log(result); + // $scope.users = result; + // }); + $scope.users = users; + + + // This close function doesn't need to use jQuery or bootstrap, because + // the button has the 'data-dismiss' attribute. + $scope.close = function() { + close({ + // not needed to send info back b/c databinding + // card: $scope.card + }, 500); // close, but give 500ms for bootstrap to animate + console.log('close method ran'); + }; + + // This cancel function must use the bootstrap, 'modal' function because + // the doesn't have the 'data-dismiss' attribute. + $scope.cancel = function() { + + // Manually hide the modal. + $element.modal('hide'); + + // Now call close, returning control to the caller. + close({ + }, 500); // close, but give 500ms for bootstrap to animate + }; + + //=================card editing methods================== + + // to populate editable data in modal + var setEditData = function(){ + $scope.oldCard = { id: card.id, + title: card.title, + description: card.description, + list_id: card.list_id }; + }; + + setEditData(); // running fn when modal opens + + var getRectangularObj = function(){ + return Restangular.one('lists', card.list_id) + .one('cards', card.id); + }; + + $scope.editCard = function(field, save){ + if (save && $scope.editCardEnabled[field]){ + updateCard(field); + } else { + setEditData(); + } + if (Object.keys($scope.editCardEnabled)[0] != field){ + $scope.editCardEnabled = {}; + } + $scope.editCardEnabled[field] = !$scope.editCardEnabled[field]; + }; + + var updateCard = function(field){ + var updatingCard = getRectangularObj(); + // set restangular obj properties with updated data + updatingCard[field] = $scope.oldCard[field]; + updatingCard.put().then( function(result){ + $scope.card[field] = result[field]; + } ); + }; + + $scope.cardComplete = function(){ + getRectangularObj().remove().then(function(){ + $scope.list.cards.splice(idxInList, 1); + }); + $scope.cancel(); + }; + + //=============member methods============= + $scope.addMember = function(){ + var userId = $scope.memberID; + var idx; + for(var i =0; i<$scope.users.length; i++){ + if($scope.users[i].id == userId) return idx = i; + } + $scope.card.members.push($scope.users[idx]); + + //also use restangular to add into db + }; + + var findMemberIdx= function(userId){ + for(var i =0; i<$scope.card.members.length; i++){ + if($scope.card.members[i].id == userId) return i; + } + }; + + $scope.removeMember = function(index){ + // Restangular.one('card_memberships', ).remove().then(function(){ + // $scope.card.members.splice(idx, 1); + // }); + } + +}]); \ No newline at end of file diff --git a/djello/app/assets/javascripts/controllers/headerCtrl.js b/djello/app/assets/javascripts/controllers/headerCtrl.js new file mode 100644 index 00000000..ae93fd61 --- /dev/null +++ b/djello/app/assets/javascripts/controllers/headerCtrl.js @@ -0,0 +1,31 @@ +djello.controller('headerCtrl', ['$scope', '$location','Auth', 'loginService', function($scope, $location, Auth, loginService){ + + console.log('headerCtrl initiated'); + + + $scope.loginFormData = {}; + $scope.user = loginService.signedInUser.user; + + $scope.logout = function(){ + loginService.logout(); + } + + $scope.signInUser = function(){ + loginService.loginUser($scope.loginFormData); + + $scope.$on('devise:login', function(event, currentUser) { + // after a login, a hard refresh, a new tab + $scope.user = currentUser; + console.log('in headerCtrl', $scope.user); + $location.path('/board'); + }); + + $scope.$on('devise:new-session', function(event, currentUser) { + // user logged in by Auth.login({...}) + $location.path('/board'); + }); + + // console.log("in ctrl", $scope.user ); + }; + +}]); diff --git a/djello/app/assets/javascripts/lists.coffee b/djello/app/assets/javascripts/lists.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/djello/app/assets/javascripts/lists.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/djello/app/assets/javascripts/services/dataService.js b/djello/app/assets/javascripts/services/dataService.js new file mode 100644 index 00000000..39701b4f --- /dev/null +++ b/djello/app/assets/javascripts/services/dataService.js @@ -0,0 +1,46 @@ +djello.service('dataService',['Restangular', 'loginService', + function(Restangular,loginService){ + + var obj = {}; + + obj.boards = {}; + + + Restangular.all('boards').getList().then( + function(result){ + obj.boards.allBoards = result; + } + ); + + obj.checkBoardOwner = function(boardId){ + for(var i=0; i {id: 1, ect: '...'} + }, function(error) { + console.log("Authentication failed..."); + }); + + }; + + return obj; +}]); \ No newline at end of file diff --git a/djello/app/assets/javascripts/sessions.coffee b/djello/app/assets/javascripts/sessions.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/djello/app/assets/javascripts/sessions.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/djello/app/assets/javascripts/users.coffee b/djello/app/assets/javascripts/users.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/djello/app/assets/javascripts/users.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/djello/app/assets/stylesheets/angular.scss b/djello/app/assets/stylesheets/angular.scss new file mode 100644 index 00000000..df66101d --- /dev/null +++ b/djello/app/assets/stylesheets/angular.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the angular controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/djello/app/assets/stylesheets/application.css b/djello/app/assets/stylesheets/application.css new file mode 100644 index 00000000..f9cd5b34 --- /dev/null +++ b/djello/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any styles + * defined in the other CSS/SCSS files in this directory. It is generally better to create a new + * file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/djello/app/assets/stylesheets/boards.scss b/djello/app/assets/stylesheets/boards.scss new file mode 100644 index 00000000..8c12f8f0 --- /dev/null +++ b/djello/app/assets/stylesheets/boards.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Boards controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/djello/app/assets/stylesheets/cards.scss b/djello/app/assets/stylesheets/cards.scss new file mode 100644 index 00000000..648b0327 --- /dev/null +++ b/djello/app/assets/stylesheets/cards.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the cards controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/djello/app/assets/stylesheets/lists.scss b/djello/app/assets/stylesheets/lists.scss new file mode 100644 index 00000000..88af4ba9 --- /dev/null +++ b/djello/app/assets/stylesheets/lists.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the lists controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/djello/app/assets/stylesheets/sessions.scss b/djello/app/assets/stylesheets/sessions.scss new file mode 100644 index 00000000..7bef9cf8 --- /dev/null +++ b/djello/app/assets/stylesheets/sessions.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the sessions controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/djello/app/assets/stylesheets/users.scss b/djello/app/assets/stylesheets/users.scss new file mode 100644 index 00000000..1efc835c --- /dev/null +++ b/djello/app/assets/stylesheets/users.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the users controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/djello/app/controllers/angular_controller.rb b/djello/app/controllers/angular_controller.rb new file mode 100644 index 00000000..ff800787 --- /dev/null +++ b/djello/app/controllers/angular_controller.rb @@ -0,0 +1,7 @@ +class AngularController < ApplicationController + + def index + + end + +end diff --git a/djello/app/controllers/application_controller.rb b/djello/app/controllers/application_controller.rb new file mode 100644 index 00000000..d378a2ca --- /dev/null +++ b/djello/app/controllers/application_controller.rb @@ -0,0 +1,11 @@ +class ApplicationController < ActionController::Base + # Prevent CSRF attacks by raising an exception. + # For APIs, you may want to use :null_session instead. + protect_from_forgery with: :exception + + + respond_to :html, :json + + # respond_to :json, if: :devise_controller? + +end diff --git a/djello/app/controllers/boards_controller.rb b/djello/app/controllers/boards_controller.rb new file mode 100644 index 00000000..5f3a79f7 --- /dev/null +++ b/djello/app/controllers/boards_controller.rb @@ -0,0 +1,75 @@ +class BoardsController < ApplicationController + + before_action :authenticate_user! + + def index + puts "=================action index in board ctrl================" + puts current_user.boards + @boards = current_user.boards + respond_to do |format| + format.json {render json: @boards} + end + end + + def create + @board = Board.new(params_list) + @board.user_id = current_user.id + respond_to do |format| + if @board.save + format.json {render json: @board} + else + format.json {render status: :unprocessable_entity} + end + end + end + + def update + @board = Board.find(params["id"]) + respond_to do |format| + if @board.update(params_list) + format.json {render json: @board} + else + format.json {render status: :unprocessable_entity} + end + end + end + + def destroy + @board = Board.find(params["id"]) + respond_to do |format| + if @board.destroy + format.json {head :ok} + else + format.json {render status: :unprocessable_entity} + end + end + end + + def show + + @board = Board.find(params["id"]) + @lists = List.where(:board_id => @board.id) + puts "===================!!!!!!!!!!====================" + puts @lists + + respond_to do |format| + format.json {render json: + {:board => @board.to_json(:include => :lists), + :lists => @lists.to_json(include: [cards: { + include: :members} ]) } + } + + format.json { render json: @boards.to_json( include: [:user, lists: { include: [cards:{ include: :list }] } ] ) } + + # {render json: @board.to_json(:include => {:lists => :cards)} + + # # @venue.to_json(:include => {:published_events => {:method => :to_param}}) + end + end + + private + + def params_list + params.require(:board).permit(:title, :user_id, :id) + end +end diff --git a/djello/app/controllers/cards_controller.rb b/djello/app/controllers/cards_controller.rb new file mode 100644 index 00000000..ac725d5e --- /dev/null +++ b/djello/app/controllers/cards_controller.rb @@ -0,0 +1,50 @@ +class CardsController < ApplicationController + + def create + @card = Card.new(params_list) + respond_to do |format| + if @card.save + format.json {render json: @card} + else + format.json {render status: :unprocessable_entity} + end + end + end + + def show + @card = Card.find(param['id']) + respond_to do |format| + format.json {render json: @card } + end + end + + def update + @card = Card.find(params["id"]) + respond_to do |format| + if @card.update(params_list) + format.json {render json: @card} + else + format.json {render status: :unprocessable_entity} + end + end + end + + def destroy + @card = Card.find(params["id"]) + respond_to do |format| + if @card.destroy + format.json {head :ok} + else + format.json {render status: :unprocessable_entity} + end + end + end + + private + + def params_list + params.require(:card).permit(:title, :description, :list_id, :id) + end + + +end diff --git a/djello/app/controllers/concerns/.keep b/djello/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/djello/app/controllers/lists_controller.rb b/djello/app/controllers/lists_controller.rb new file mode 100644 index 00000000..f3c6df49 --- /dev/null +++ b/djello/app/controllers/lists_controller.rb @@ -0,0 +1,41 @@ +class ListsController < ApplicationController + + def create + @list = List.new(params_list) + respond_to do |format| + if @list.save + format.json {render json: @list} + else + format.json {render status: :unprocessable_entity} + end + end + end + + def update + @list = List.find(params["id"]) + respond_to do |format| + if @list.update(params_list) + format.json {render json: @list} + else + format.json {render status: :unprocessable_entity} + end + end + end + + def destroy + @list = List.find(params["id"]) + respond_to do |format| + if @list.destroy + format.json {head :ok} + else + format.json {render status: :unprocessable_entity} + end + end + end + + private + + def params_list + params.require(:list).permit(:title, :board_id, :id, :description) + end +end diff --git a/djello/app/controllers/sessions_controller.rb b/djello/app/controllers/sessions_controller.rb new file mode 100644 index 00000000..581ce734 --- /dev/null +++ b/djello/app/controllers/sessions_controller.rb @@ -0,0 +1,11 @@ +class SessionsController < Devise::SessionsController + # prepend_before_filter :require_no_authentication, :only => [ :new, :create, :cancel ] + # skip_before_filter :require_no_authentication, :only => [:create, :new, :destroy] + + # skip_before_filter :verify_authenticity_token, :only => [:destroy] + + clear_respond_to # won't accept html + respond_to :json +end + +#registration same, just inherit from diff. controller \ No newline at end of file diff --git a/djello/app/controllers/users_controller.rb b/djello/app/controllers/users_controller.rb new file mode 100644 index 00000000..6c9a8f88 --- /dev/null +++ b/djello/app/controllers/users_controller.rb @@ -0,0 +1,12 @@ +class UsersController < ApplicationController + + def index + @users = User.all + respond_to do |format| + format.json {render json: @users} + end + end + +end + + diff --git a/djello/app/helpers/angular_helper.rb b/djello/app/helpers/angular_helper.rb new file mode 100644 index 00000000..2ceb5e4f --- /dev/null +++ b/djello/app/helpers/angular_helper.rb @@ -0,0 +1,2 @@ +module AngularHelper +end diff --git a/djello/app/helpers/application_helper.rb b/djello/app/helpers/application_helper.rb new file mode 100644 index 00000000..cbda6ae1 --- /dev/null +++ b/djello/app/helpers/application_helper.rb @@ -0,0 +1,15 @@ +module ApplicationHelper + + def resource_name + :user + end + + def resource + @resource ||= User.new + end + + def devise_mapping + @devise_mapping ||= Devise.mappings[:user] + end + +end diff --git a/djello/app/helpers/boards_helper.rb b/djello/app/helpers/boards_helper.rb new file mode 100644 index 00000000..8b8af150 --- /dev/null +++ b/djello/app/helpers/boards_helper.rb @@ -0,0 +1,2 @@ +module BoardsHelper +end diff --git a/djello/app/helpers/cards_helper.rb b/djello/app/helpers/cards_helper.rb new file mode 100644 index 00000000..82dc1f45 --- /dev/null +++ b/djello/app/helpers/cards_helper.rb @@ -0,0 +1,2 @@ +module CardsHelper +end diff --git a/djello/app/helpers/lists_helper.rb b/djello/app/helpers/lists_helper.rb new file mode 100644 index 00000000..bf2e0db4 --- /dev/null +++ b/djello/app/helpers/lists_helper.rb @@ -0,0 +1,2 @@ +module ListsHelper +end diff --git a/djello/app/helpers/sessions_helper.rb b/djello/app/helpers/sessions_helper.rb new file mode 100644 index 00000000..309f8b2e --- /dev/null +++ b/djello/app/helpers/sessions_helper.rb @@ -0,0 +1,2 @@ +module SessionsHelper +end diff --git a/djello/app/helpers/users_helper.rb b/djello/app/helpers/users_helper.rb new file mode 100644 index 00000000..2310a240 --- /dev/null +++ b/djello/app/helpers/users_helper.rb @@ -0,0 +1,2 @@ +module UsersHelper +end diff --git a/djello/app/mailers/.keep b/djello/app/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/djello/app/models/.keep b/djello/app/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/djello/app/models/board.rb b/djello/app/models/board.rb new file mode 100644 index 00000000..dfc03155 --- /dev/null +++ b/djello/app/models/board.rb @@ -0,0 +1,12 @@ +class Board < ActiveRecord::Base + + belongs_to :owner, class_name: "User", foreign_key: :user_id + + has_many :lists + has_many :cards, through: :lists + has_many :card_memberships, through: :cards + + has_many :project_members, through: :card_memberships, source: :user + + default_scope { includes(:lists)} +end diff --git a/djello/app/models/card.rb b/djello/app/models/card.rb new file mode 100644 index 00000000..a6338824 --- /dev/null +++ b/djello/app/models/card.rb @@ -0,0 +1,10 @@ +class Card < ActiveRecord::Base + + belongs_to :list + delegate :board, to: :list + delegate :owner, to: :board + + has_many :card_memberships, :dependent: :destroy + has_many :members, through: :card_memberships, source: :user + +end diff --git a/djello/app/models/card_membership.rb b/djello/app/models/card_membership.rb new file mode 100644 index 00000000..c4d2dea2 --- /dev/null +++ b/djello/app/models/card_membership.rb @@ -0,0 +1,6 @@ +class CardMembership < ActiveRecord::Base + + belongs_to :task, class_name: "Card", foreign_key: :card_id + belongs_to :user + +end diff --git a/djello/app/models/concerns/.keep b/djello/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/djello/app/models/list.rb b/djello/app/models/list.rb new file mode 100644 index 00000000..10f8a4c6 --- /dev/null +++ b/djello/app/models/list.rb @@ -0,0 +1,8 @@ +class List < ActiveRecord::Base + + has_many :cards + belongs_to :board + delegate :owner, to: :board + + default_scope { includes(:cards)} +end diff --git a/djello/app/models/user.rb b/djello/app/models/user.rb new file mode 100644 index 00000000..787d4ac6 --- /dev/null +++ b/djello/app/models/user.rb @@ -0,0 +1,19 @@ +class User < ActiveRecord::Base + # Include default devise modules. Others available are: + # :confirmable, :lockable, :timeoutable and :omniauthable + devise :registerable, + :recoverable, :rememberable, :trackable, :validatable + devise :database_authenticatable, :authentication_keys => [:username] + + has_many :boards + has_many :lists, through: :boards + has_many :cards, through: :lists + + #===== card member associations below ==== + has_many :collaborations, class_name: "CardMembership" + has_many :tasks, through: :collaborations + has_many :task_lists, through: :tasks, source: :list + has_many :projects, through: :task_lists, source: :board + + +end diff --git a/djello/app/views/angular/index.html b/djello/app/views/angular/index.html new file mode 100644 index 00000000..e6cdf08b --- /dev/null +++ b/djello/app/views/angular/index.html @@ -0,0 +1,3 @@ + +
Header is not loaded
+
Main Area
\ No newline at end of file diff --git a/djello/app/views/devise/confirmations/new.html.erb b/djello/app/views/devise/confirmations/new.html.erb new file mode 100644 index 00000000..826672f7 --- /dev/null +++ b/djello/app/views/devise/confirmations/new.html.erb @@ -0,0 +1,16 @@ +

Resend confirmation instructions

+ +<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> + <%= devise_error_messages! %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> +
+ +
+ <%= f.submit "Resend confirmation instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/djello/app/views/devise/mailer/confirmation_instructions.html.erb b/djello/app/views/devise/mailer/confirmation_instructions.html.erb new file mode 100644 index 00000000..dc55f64f --- /dev/null +++ b/djello/app/views/devise/mailer/confirmation_instructions.html.erb @@ -0,0 +1,5 @@ +

Welcome <%= @email %>!

+ +

You can confirm your account email through the link below:

+ +

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

diff --git a/djello/app/views/devise/mailer/reset_password_instructions.html.erb b/djello/app/views/devise/mailer/reset_password_instructions.html.erb new file mode 100644 index 00000000..f667dc12 --- /dev/null +++ b/djello/app/views/devise/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +

Hello <%= @resource.email %>!

+ +

Someone has requested a link to change your password. You can do this through the link below.

+ +

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

+ +

If you didn't request this, please ignore this email.

+

Your password won't change until you access the link above and create a new one.

diff --git a/djello/app/views/devise/mailer/unlock_instructions.html.erb b/djello/app/views/devise/mailer/unlock_instructions.html.erb new file mode 100644 index 00000000..41e148bf --- /dev/null +++ b/djello/app/views/devise/mailer/unlock_instructions.html.erb @@ -0,0 +1,7 @@ +

Hello <%= @resource.email %>!

+ +

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

+ +

Click the link below to unlock your account:

+ +

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

diff --git a/djello/app/views/devise/passwords/edit.html.erb b/djello/app/views/devise/passwords/edit.html.erb new file mode 100644 index 00000000..6a796b05 --- /dev/null +++ b/djello/app/views/devise/passwords/edit.html.erb @@ -0,0 +1,25 @@ +

Change your password

+ +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> + <%= devise_error_messages! %> + <%= f.hidden_field :reset_password_token %> + +
+ <%= f.label :password, "New password" %>
+ <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum)
+ <% end %> + <%= f.password_field :password, autofocus: true, autocomplete: "off" %> +
+ +
+ <%= f.label :password_confirmation, "Confirm new password" %>
+ <%= f.password_field :password_confirmation, autocomplete: "off" %> +
+ +
+ <%= f.submit "Change my password" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/djello/app/views/devise/passwords/new.html.erb b/djello/app/views/devise/passwords/new.html.erb new file mode 100644 index 00000000..3d6d11aa --- /dev/null +++ b/djello/app/views/devise/passwords/new.html.erb @@ -0,0 +1,16 @@ +

Forgot your password?

+ +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> + <%= devise_error_messages! %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true %> +
+ +
+ <%= f.submit "Send me reset password instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/djello/app/views/devise/registrations/edit.html.erb b/djello/app/views/devise/registrations/edit.html.erb new file mode 100644 index 00000000..3ea40f01 --- /dev/null +++ b/djello/app/views/devise/registrations/edit.html.erb @@ -0,0 +1,39 @@ +

Edit <%= resource_name.to_s.humanize %>

+ +<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> + <%= devise_error_messages! %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true %> +
+ + <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> +
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
+ <% end %> + +
+ <%= f.label :password %> (leave blank if you don't want to change it)
+ <%= f.password_field :password, autocomplete: "off" %> +
+ +
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "off" %> +
+ +
+ <%= f.label :current_password %> (we need your current password to confirm your changes)
+ <%= f.password_field :current_password, autocomplete: "off" %> +
+ +
+ <%= f.submit "Update" %> +
+<% end %> + +

Cancel my account

+ +

Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>

+ +<%= link_to "Back", :back %> diff --git a/djello/app/views/devise/registrations/new.html.erb b/djello/app/views/devise/registrations/new.html.erb new file mode 100644 index 00000000..5a238ce6 --- /dev/null +++ b/djello/app/views/devise/registrations/new.html.erb @@ -0,0 +1,29 @@ +

Sign up

+ +<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> + <%= devise_error_messages! %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true %> +
+ +
+ <%= f.label :password %> + <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum) + <% end %>
+ <%= f.password_field :password, autocomplete: "off" %> +
+ +
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "off" %> +
+ +
+ <%= f.submit "Sign up" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/djello/app/views/devise/sessions/new.html.erb b/djello/app/views/devise/sessions/new.html.erb new file mode 100644 index 00000000..ab694d1b --- /dev/null +++ b/djello/app/views/devise/sessions/new.html.erb @@ -0,0 +1,28 @@ +

Log in

+ +<%= form_for(resource, as: resource_name, + url: session_path(resource_name), + :format => :json, :remote => true) do |f| %> +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true %> +
+ +
+ <%= f.label :password %>
+ <%= f.password_field :password, autocomplete: "off" %> +
+ + <% if devise_mapping.rememberable? -%> +
+ <%= f.check_box :remember_me %> + <%= f.label :remember_me %> +
+ <% end -%> + +
+ <%= f.submit "Log in" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/djello/app/views/devise/shared/_links.html.erb b/djello/app/views/devise/shared/_links.html.erb new file mode 100644 index 00000000..cd795adb --- /dev/null +++ b/djello/app/views/devise/shared/_links.html.erb @@ -0,0 +1,25 @@ +<%- if controller_name != 'sessions' %> + <%= link_to "Log in", new_session_path(resource_name) %>
+<% end -%> + +<%- if devise_mapping.registerable? && controller_name != 'registrations' %> + <%= link_to "Sign up", new_registration_path(resource_name) %>
+<% end -%> + +<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> + <%= link_to "Forgot your password?", new_password_path(resource_name) %>
+<% end -%> + +<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> + <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
+<% end -%> + +<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> + <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
+<% end -%> + +<%- if devise_mapping.omniauthable? %> + <%- resource_class.omniauth_providers.each do |provider| %> + <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %>
+ <% end -%> +<% end -%> diff --git a/djello/app/views/devise/unlocks/new.html.erb b/djello/app/views/devise/unlocks/new.html.erb new file mode 100644 index 00000000..16586bc7 --- /dev/null +++ b/djello/app/views/devise/unlocks/new.html.erb @@ -0,0 +1,16 @@ +

Resend unlock instructions

+ +<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> + <%= devise_error_messages! %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true %> +
+ +
+ <%= f.submit "Resend unlock instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/djello/app/views/layouts/application.html.erb b/djello/app/views/layouts/application.html.erb new file mode 100644 index 00000000..f3e1aa96 --- /dev/null +++ b/djello/app/views/layouts/application.html.erb @@ -0,0 +1,26 @@ + + + + Djello + <%= stylesheet_link_tag 'application', media: 'all'%> + <%= javascript_include_tag 'application' %> + <%= csrf_meta_tags %> + + + + + + + + + + + + +

<%= notice %>

+

<%= alert %>

+ + <%= yield %> + + + diff --git a/djello/bin/bundle b/djello/bin/bundle new file mode 100755 index 00000000..66e9889e --- /dev/null +++ b/djello/bin/bundle @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +load Gem.bin_path('bundler', 'bundle') diff --git a/djello/bin/rails b/djello/bin/rails new file mode 100755 index 00000000..4d608ede --- /dev/null +++ b/djello/bin/rails @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +begin + load File.expand_path("../spring", __FILE__) +rescue LoadError +end +APP_PATH = File.expand_path('../../config/application', __FILE__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/djello/bin/rake b/djello/bin/rake new file mode 100755 index 00000000..8017a027 --- /dev/null +++ b/djello/bin/rake @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +begin + load File.expand_path("../spring", __FILE__) +rescue LoadError +end +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/djello/bin/setup b/djello/bin/setup new file mode 100755 index 00000000..acdb2c13 --- /dev/null +++ b/djello/bin/setup @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +require 'pathname' + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +Dir.chdir APP_ROOT do + # This script is a starting point to setup your application. + # Add necessary setup steps to this file: + + puts "== Installing dependencies ==" + system "gem install bundler --conservative" + system "bundle check || bundle install" + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # system "cp config/database.yml.sample config/database.yml" + # end + + puts "\n== Preparing database ==" + system "bin/rake db:setup" + + puts "\n== Removing old logs and tempfiles ==" + system "rm -f log/*" + system "rm -rf tmp/cache" + + puts "\n== Restarting application server ==" + system "touch tmp/restart.txt" +end diff --git a/djello/bin/spring b/djello/bin/spring new file mode 100755 index 00000000..5e7ecc47 --- /dev/null +++ b/djello/bin/spring @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby + +# This file loads spring without using Bundler, in order to be fast. +# It gets overwritten when you run the `spring binstub` command. + +unless defined?(Spring) + require "rubygems" + require "bundler" + + if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)) + Gem.paths = { "GEM_PATH" => [Bundler.bundle_path.to_s, *Gem.path].uniq } + gem "spring", match[1] + require "spring/binstub" + end +end diff --git a/djello/config.ru b/djello/config.ru new file mode 100644 index 00000000..bd83b254 --- /dev/null +++ b/djello/config.ru @@ -0,0 +1,4 @@ +# This file is used by Rack-based servers to start the application. + +require ::File.expand_path('../config/environment', __FILE__) +run Rails.application diff --git a/djello/config/application.rb b/djello/config/application.rb new file mode 100644 index 00000000..097071ba --- /dev/null +++ b/djello/config/application.rb @@ -0,0 +1,36 @@ +require File.expand_path('../boot', __FILE__) + +require 'rails/all' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module Djello + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. + # config.time_zone = 'Central Time (US & Canada)' + + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # config.i18n.default_locale = :de + + # Do not swallow errors in after_commit/after_rollback callbacks. + config.active_record.raise_in_transactional_callbacks = true + end +end + +# module RailsApp +# class Application < Rails::Application +# # ... + +# config.to_prepare do +# DeviseController.respond_to :html, :json +# end +# end +# end diff --git a/djello/config/boot.rb b/djello/config/boot.rb new file mode 100644 index 00000000..6b750f00 --- /dev/null +++ b/djello/config/boot.rb @@ -0,0 +1,3 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/djello/config/database.yml b/djello/config/database.yml new file mode 100644 index 00000000..465e8a1a --- /dev/null +++ b/djello/config/database.yml @@ -0,0 +1,28 @@ +# SQLite version 3.x +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem 'sqlite3' +# +default: &default + adapter: postgresql + pool: 5 + timeout: 5000 + +development: + adapter: postgresql + database: db/djello_development + + pool: 5 + timeout: 5000 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/djello_test + +production: + <<: *default + database: db/djello_production diff --git a/djello/config/environment.rb b/djello/config/environment.rb new file mode 100644 index 00000000..ee8d90dc --- /dev/null +++ b/djello/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require File.expand_path('../application', __FILE__) + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/djello/config/environments/development.rb b/djello/config/environments/development.rb new file mode 100644 index 00000000..80dc0500 --- /dev/null +++ b/djello/config/environments/development.rb @@ -0,0 +1,44 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Asset digests allow you to set far-future HTTP expiration dates on all assets, + # yet still be able to expire them through the digest params. + config.assets.digest = true + + # Adds additional error checking when serving assets at runtime. + # Checks for improperly declared sprockets dependencies. + # Raises helpful error messages. + config.assets.raise_runtime_errors = true + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + #devise config + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } +end diff --git a/djello/config/environments/production.rb b/djello/config/environments/production.rb new file mode 100644 index 00000000..ba202be3 --- /dev/null +++ b/djello/config/environments/production.rb @@ -0,0 +1,82 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Enable Rack::Cache to put a simple HTTP cache in front of your application + # Add `rack-cache` to your Gemfile before enabling this. + # For large-scale production use, consider using a caching reverse proxy like + # NGINX, varnish or squid. + # config.action_dispatch.rack_cache = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # Asset digests allow you to set far-future HTTP expiration dates on all assets, + # yet still be able to expire them through the digest params. + config.assets.digest = true + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + # config.log_tags = [ :subdomain, :uuid ] + + # Use a different logger for distributed setups. + # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + #for devise, rename host later + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } +end diff --git a/djello/config/environments/test.rb b/djello/config/environments/test.rb new file mode 100644 index 00000000..1c19f08b --- /dev/null +++ b/djello/config/environments/test.rb @@ -0,0 +1,42 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure static file server for tests with Cache-Control for performance. + config.serve_static_files = true + config.static_cache_control = 'public, max-age=3600' + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Randomize the order test cases are executed. + config.active_support.test_order = :random + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/djello/config/initializers/assets.rb b/djello/config/initializers/assets.rb new file mode 100644 index 00000000..01ef3e66 --- /dev/null +++ b/djello/config/initializers/assets.rb @@ -0,0 +1,11 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path +# Rails.application.config.assets.paths << Emoji.images_path + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. +# Rails.application.config.assets.precompile += %w( search.js ) diff --git a/djello/config/initializers/backtrace_silencers.rb b/djello/config/initializers/backtrace_silencers.rb new file mode 100644 index 00000000..59385cdf --- /dev/null +++ b/djello/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/djello/config/initializers/cookies_serializer.rb b/djello/config/initializers/cookies_serializer.rb new file mode 100644 index 00000000..7f70458d --- /dev/null +++ b/djello/config/initializers/cookies_serializer.rb @@ -0,0 +1,3 @@ +# Be sure to restart your server when you modify this file. + +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/djello/config/initializers/devise.rb b/djello/config/initializers/devise.rb new file mode 100644 index 00000000..9e593930 --- /dev/null +++ b/djello/config/initializers/devise.rb @@ -0,0 +1,262 @@ +# Use this hook to configure devise mailer, warden hooks and so forth. +# Many of these configuration options can be set straight in your model. +Devise.setup do |config| + # The secret key used by Devise. Devise uses this key to generate + # random tokens. Changing this key will render invalid all existing + # confirmation, reset password and unlock tokens in the database. + # Devise will use the `secret_key_base` on Rails 4+ applications as its `secret_key` + # by default. You can change it below and use your own secret key. + # config.secret_key = '2c4d602083c9c8259146b859e6c0da3616fb9a474dcdeb4ea45cea4b2be8f757660d3d056a6505db967300d5c7680f2439dc53a85c898d17629e7a9290b4881a' + + # ==> Mailer Configuration + # Configure the e-mail address which will be shown in Devise::Mailer, + # note that it will be overwritten if you use your own mailer class + # with default "from" parameter. + config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' + + # Configure the class responsible to send e-mails. + # config.mailer = 'Devise::Mailer' + + # ==> ORM configuration + # Load and configure the ORM. Supports :active_record (default) and + # :mongoid (bson_ext recommended) by default. Other ORMs may be + # available as additional gems. + require 'devise/orm/active_record' + + # ==> Configuration for any authentication mechanism + # Configure which keys are used when authenticating a user. The default is + # just :email. You can configure it to use [:username, :subdomain], so for + # authenticating a user, both parameters are required. Remember that those + # parameters are used only when authenticating and not when retrieving from + # session. If you need permissions, you should implement that in a before filter. + # You can also supply a hash where the value is a boolean determining whether + # or not authentication should be aborted when the value is not present. + # config.authentication_keys = [:email] + + # Configure parameters from the request object used for authentication. Each entry + # given should be a request method and it will automatically be passed to the + # find_for_authentication method and considered in your model lookup. For instance, + # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. + # The same considerations mentioned for authentication_keys also apply to request_keys. + # config.request_keys = [] + + # Configure which authentication keys should be case-insensitive. + # These keys will be downcased upon creating or modifying a user and when used + # to authenticate or find a user. Default is :email. + config.case_insensitive_keys = [:email] + + # Configure which authentication keys should have whitespace stripped. + # These keys will have whitespace before and after removed upon creating or + # modifying a user and when used to authenticate or find a user. Default is :email. + config.strip_whitespace_keys = [:email] + + # Tell if authentication through request.params is enabled. True by default. + # It can be set to an array that will enable params authentication only for the + # given strategies, for example, `config.params_authenticatable = [:database]` will + # enable it only for database (email + password) authentication. + # config.params_authenticatable = true + + # Tell if authentication through HTTP Auth is enabled. False by default. + # It can be set to an array that will enable http authentication only for the + # given strategies, for example, `config.http_authenticatable = [:database]` will + # enable it only for database authentication. The supported strategies are: + # :database = Support basic authentication with authentication key + password + # config.http_authenticatable = false + + # If 401 status code should be returned for AJAX requests. True by default. + # config.http_authenticatable_on_xhr = true + + # The realm used in Http Basic Authentication. 'Application' by default. + # config.http_authentication_realm = 'Application' + + # It will change confirmation, password recovery and other workflows + # to behave the same regardless if the e-mail provided was right or wrong. + # Does not affect registerable. + # config.paranoid = true + + # By default Devise will store the user in session. You can skip storage for + # particular strategies by setting this option. + # Notice that if you are skipping storage for all authentication paths, you + # may want to disable generating routes to Devise's sessions controller by + # passing skip: :sessions to `devise_for` in your config/routes.rb + config.skip_session_storage = [:http_auth] + + # By default, Devise cleans up the CSRF token on authentication to + # avoid CSRF token fixation attacks. This means that, when using AJAX + # requests for sign in and sign up, you need to get a new CSRF token + # from the server. You can disable this option at your own risk. + # config.clean_up_csrf_token_on_authentication = true + + # ==> Configuration for :database_authenticatable + # For bcrypt, this is the cost for hashing the password and defaults to 10. If + # using other encryptors, it sets how many times you want the password re-encrypted. + # + # Limiting the stretches to just one in testing will increase the performance of + # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use + # a value less than 10 in other environments. Note that, for bcrypt (the default + # encryptor), the cost increases exponentially with the number of stretches (e.g. + # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). + config.stretches = Rails.env.test? ? 1 : 10 + + # Setup a pepper to generate the encrypted password. + # config.pepper = '53eaa905082a94776d798c27d9e828f144d68e2c70559c5f5a5118f93d3470ea48ba1ebadb349f1208dcd1a874cba345deaad7298e2b453432515752ca214087' + + # ==> Configuration for :confirmable + # A period that the user is allowed to access the website even without + # confirming their account. For instance, if set to 2.days, the user will be + # able to access the website for two days without confirming their account, + # access will be blocked just in the third day. Default is 0.days, meaning + # the user cannot access the website without confirming their account. + # config.allow_unconfirmed_access_for = 2.days + + # A period that the user is allowed to confirm their account before their + # token becomes invalid. For example, if set to 3.days, the user can confirm + # their account within 3 days after the mail was sent, but on the fourth day + # their account can't be confirmed with the token any more. + # Default is nil, meaning there is no restriction on how long a user can take + # before confirming their account. + # config.confirm_within = 3.days + + # If true, requires any email changes to be confirmed (exactly the same way as + # initial account confirmation) to be applied. Requires additional unconfirmed_email + # db field (see migrations). Until confirmed, new email is stored in + # unconfirmed_email column, and copied to email column on successful confirmation. + config.reconfirmable = true + + # Defines which key will be used when confirming an account + # config.confirmation_keys = [:email] + + # ==> Configuration for :rememberable + # The time the user will be remembered without asking for credentials again. + # config.remember_for = 2.weeks + + # Invalidates all the remember me tokens when the user signs out. + config.expire_all_remember_me_on_sign_out = true + + # If true, extends the user's remember period when remembered via cookie. + # config.extend_remember_period = false + + # Options to be passed to the created cookie. For instance, you can set + # secure: true in order to force SSL only cookies. + # config.rememberable_options = {} + + # ==> Configuration for :validatable + # Range for password length. + config.password_length = 8..72 + + # Email regex used to validate email formats. It simply asserts that + # one (and only one) @ exists in the given string. This is mainly + # to give user feedback and not to assert the e-mail validity. + # config.email_regexp = /\A[^@]+@[^@]+\z/ + + # ==> Configuration for :timeoutable + # The time you want to timeout the user session without activity. After this + # time the user will be asked for credentials again. Default is 30 minutes. + # config.timeout_in = 30.minutes + + # ==> Configuration for :lockable + # Defines which strategy will be used to lock an account. + # :failed_attempts = Locks an account after a number of failed attempts to sign in. + # :none = No lock strategy. You should handle locking by yourself. + # config.lock_strategy = :failed_attempts + + # Defines which key will be used when locking and unlocking an account + # config.unlock_keys = [:email] + + # Defines which strategy will be used to unlock an account. + # :email = Sends an unlock link to the user email + # :time = Re-enables login after a certain amount of time (see :unlock_in below) + # :both = Enables both strategies + # :none = No unlock strategy. You should handle unlocking by yourself. + # config.unlock_strategy = :both + + # Number of authentication tries before locking an account if lock_strategy + # is failed attempts. + # config.maximum_attempts = 20 + + # Time interval to unlock the account if :time is enabled as unlock_strategy. + # config.unlock_in = 1.hour + + # Warn on the last attempt before the account is locked. + # config.last_attempt_warning = true + + # ==> Configuration for :recoverable + # + # Defines which key will be used when recovering the password for an account + # config.reset_password_keys = [:email] + + # Time interval you can reset your password with a reset password key. + # Don't put a too small interval or your users won't have the time to + # change their passwords. + config.reset_password_within = 6.hours + + # When set to false, does not sign a user in automatically after their password is + # reset. Defaults to true, so a user is signed in automatically after a reset. + # config.sign_in_after_reset_password = true + + # ==> Configuration for :encryptable + # Allow you to use another encryption algorithm besides bcrypt (default). You can use + # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1, + # :authlogic_sha512 (then you should set stretches above to 20 for default behavior) + # and :restful_authentication_sha1 (then you should set stretches to 10, and copy + # REST_AUTH_SITE_KEY to pepper). + # + # Require the `devise-encryptable` gem when using anything other than bcrypt + # config.encryptor = :sha512 + + # ==> Scopes configuration + # Turn scoped views on. Before rendering "sessions/new", it will first check for + # "users/sessions/new". It's turned off by default because it's slower if you + # are using only default views. + # config.scoped_views = false + + # Configure the default scope given to Warden. By default it's the first + # devise role declared in your routes (usually :user). + # config.default_scope = :user + + # Set this configuration to false if you want /users/sign_out to sign out + # only the current scope. By default, Devise signs out all scopes. + # config.sign_out_all_scopes = true + + # ==> Navigation configuration + # Lists the formats that should be treated as navigational. Formats like + # :html, should redirect to the sign in page when the user does not have + # access, but formats like :xml or :json, should return 401. + # + # If you have any extra navigational formats, like :iphone or :mobile, you + # should add them to the navigational formats lists. + # + # The "*/*" below is required to match Internet Explorer requests. + # config.navigational_formats = ['*/*', :html] + + # The default HTTP method used to sign out a resource. Default is :delete. + config.sign_out_via = :delete + + # ==> OmniAuth + # Add a new OmniAuth provider. Check the wiki for more information on setting + # up on your models and hooks. + # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' + + # ==> Warden configuration + # If you want to use other strategies, that are not supported by Devise, or + # change the failure app, you can configure them inside the config.warden block. + # + # config.warden do |manager| + # manager.intercept_401 = false + # manager.default_strategies(scope: :user).unshift :some_external_strategy + # end + + # ==> Mountable engine configurations + # When using Devise inside an engine, let's call it `MyEngine`, and this engine + # is mountable, there are some extra configurations to be taken into account. + # The following options are available, assuming the engine is mounted as: + # + # mount MyEngine, at: '/my_engine' + # + # The router that invoked `devise_for`, in the example above, would be: + # config.router_name = :my_engine + # + # When using OmniAuth, Devise cannot automatically set OmniAuth path, + # so you need to do it manually. For the users scope, it would be: + # config.omniauth_path_prefix = '/my_engine/users/auth' +end diff --git a/djello/config/initializers/filter_parameter_logging.rb b/djello/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..4a994e1e --- /dev/null +++ b/djello/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/djello/config/initializers/inflections.rb b/djello/config/initializers/inflections.rb new file mode 100644 index 00000000..ac033bf9 --- /dev/null +++ b/djello/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/djello/config/initializers/mime_types.rb b/djello/config/initializers/mime_types.rb new file mode 100644 index 00000000..dc189968 --- /dev/null +++ b/djello/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/djello/config/initializers/session_store.rb b/djello/config/initializers/session_store.rb new file mode 100644 index 00000000..cceae75c --- /dev/null +++ b/djello/config/initializers/session_store.rb @@ -0,0 +1,3 @@ +# Be sure to restart your server when you modify this file. + +Rails.application.config.session_store :cookie_store, key: '_djello_session' diff --git a/djello/config/initializers/wrap_parameters.rb b/djello/config/initializers/wrap_parameters.rb new file mode 100644 index 00000000..33725e95 --- /dev/null +++ b/djello/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] if respond_to?(:wrap_parameters) +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/djello/config/locales/devise.en.yml b/djello/config/locales/devise.en.yml new file mode 100644 index 00000000..26a10f29 --- /dev/null +++ b/djello/config/locales/devise.en.yml @@ -0,0 +1,60 @@ +# Additional translations at https://github.com/plataformatec/devise/wiki/I18n + +en: + devise: + confirmations: + confirmed: "Your email address has been successfully confirmed." + send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." + failure: + already_authenticated: "You are already signed in." + inactive: "Your account is not activated yet." + invalid: "Invalid %{authentication_keys} or password." + locked: "Your account is locked." + last_attempt: "You have one more attempt before your account is locked." + not_found_in_database: "Invalid %{authentication_keys} or password." + timeout: "Your session expired. Please sign in again to continue." + unauthenticated: "You need to sign in or sign up before continuing." + unconfirmed: "You have to confirm your email address before continuing." + mailer: + confirmation_instructions: + subject: "Confirmation instructions" + reset_password_instructions: + subject: "Reset password instructions" + unlock_instructions: + subject: "Unlock instructions" + omniauth_callbacks: + failure: "Could not authenticate you from %{kind} because \"%{reason}\"." + success: "Successfully authenticated from %{kind} account." + passwords: + no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." + send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." + updated: "Your password has been changed successfully. You are now signed in." + updated_not_active: "Your password has been changed successfully." + registrations: + destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." + signed_up: "Welcome! You have signed up successfully." + signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." + signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." + signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." + update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." + updated: "Your account has been updated successfully." + sessions: + signed_in: "Signed in successfully." + signed_out: "Signed out successfully." + already_signed_out: "Signed out successfully." + unlocks: + send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." + send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." + unlocked: "Your account has been unlocked successfully. Please sign in to continue." + errors: + messages: + already_confirmed: "was already confirmed, please try signing in" + confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" + expired: "has expired, please request a new one" + not_found: "not found" + not_locked: "was not locked" + not_saved: + one: "1 error prohibited this %{resource} from being saved:" + other: "%{count} errors prohibited this %{resource} from being saved:" diff --git a/djello/config/locales/en.yml b/djello/config/locales/en.yml new file mode 100644 index 00000000..06539571 --- /dev/null +++ b/djello/config/locales/en.yml @@ -0,0 +1,23 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/djello/config/routes.rb b/djello/config/routes.rb new file mode 100644 index 00000000..864d0a5e --- /dev/null +++ b/djello/config/routes.rb @@ -0,0 +1,73 @@ +Rails.application.routes.draw do + + root 'angular#index' + + scope 'api' do + scope 'v1' do + devise_for :users, :controllers => {sessions: 'sessions'} + resources :boards, except: [:new, :edit] + resources :users, only: [:index] + + resources :lists, only: [:create, :update, :destroy] do + resources :cards, except: [:new, :edit] + end + resources :card_memberships, only: [:create, :destroy] + + end + end + + # The priority is based upon order of creation: first created -> highest priority. + # See how all your routes lay out with "rake routes". + + # You can have the root of your site routed with "root" + # root 'welcome#index' + + # Example of regular route: + # get 'products/:id' => 'catalog#view' + + # Example of named route that can be invoked with purchase_url(id: product.id) + # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase + + # Example resource route (maps HTTP verbs to controller actions automatically): + # resources :products + + # Example resource route with options: + # resources :products do + # member do + # get 'short' + # post 'toggle' + # end + # + # collection do + # get 'sold' + # end + # end + + # Example resource route with sub-resources: + # resources :products do + # resources :comments, :sales + # resource :seller + # end + + # Example resource route with more complex sub-resources: + # resources :products do + # resources :comments + # resources :sales do + # get 'recent', on: :collection + # end + # end + + # Example resource route with concerns: + # concern :toggleable do + # post 'toggle' + # end + # resources :posts, concerns: :toggleable + # resources :photos, concerns: :toggleable + + # Example resource route within a namespace: + # namespace :admin do + # # Directs /admin/products/* to Admin::ProductsController + # # (app/controllers/admin/products_controller.rb) + # resources :products + # end +end diff --git a/djello/config/secrets.yml b/djello/config/secrets.yml new file mode 100644 index 00000000..bccf9a7e --- /dev/null +++ b/djello/config/secrets.yml @@ -0,0 +1,22 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rake secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +development: + secret_key_base: f299fe63ecb2d79e2d674a6984ffbd3b31f98dcb5ee3f7d41f20e6eca52a367f32bcf4908f8cd2fc40b2058a975fdbe6d36878cd71dd8252cc0b61cbeb2bed8d + +test: + secret_key_base: aa30431fe6988fe1c6c402bc8afd97d07c21b085f8f3f249aab355b78d1f5a80896c374adb5693dae61a9be299309116855c406f8141fd0d3e257fcf537a4cb0 + +# Do not keep production secrets in the repository, +# instead read values from the environment. +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/djello/db/migrate/20150922154355_create_users.rb b/djello/db/migrate/20150922154355_create_users.rb new file mode 100644 index 00000000..3aa2c9e0 --- /dev/null +++ b/djello/db/migrate/20150922154355_create_users.rb @@ -0,0 +1,9 @@ +class CreateUsers < ActiveRecord::Migration + def change + create_table :users do |t| + t.string :username, null: false + t.string :password, null: false + t.timestamps null: false + end + end +end diff --git a/djello/db/migrate/20150922161600_add_devise_to_users.rb b/djello/db/migrate/20150922161600_add_devise_to_users.rb new file mode 100644 index 00000000..1b43f96f --- /dev/null +++ b/djello/db/migrate/20150922161600_add_devise_to_users.rb @@ -0,0 +1,49 @@ +class AddDeviseToUsers < ActiveRecord::Migration + def self.up + change_table(:users) do |t| + ## Database authenticatable + t.string :email, null: false, default: "" + t.string :encrypted_password, null: false, default: "" + + ## Recoverable + t.string :reset_password_token + t.datetime :reset_password_sent_at + + ## Rememberable + t.datetime :remember_created_at + + ## Trackable + t.integer :sign_in_count, default: 0, null: false + t.datetime :current_sign_in_at + t.datetime :last_sign_in_at + t.inet :current_sign_in_ip + t.inet :last_sign_in_ip + + ## Confirmable + # t.string :confirmation_token + # t.datetime :confirmed_at + # t.datetime :confirmation_sent_at + # t.string :unconfirmed_email # Only if using reconfirmable + + ## Lockable + # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts + # t.string :unlock_token # Only if unlock strategy is :email or :both + # t.datetime :locked_at + + + # Uncomment below if timestamps were not included in your original model. + # t.timestamps null: false + end + + add_index :users, :email, unique: true + add_index :users, :reset_password_token, unique: true + # add_index :users, :confirmation_token, unique: true + # add_index :users, :unlock_token, unique: true + end + + def self.down + # By default, we don't want to make any assumption about how to roll back a migration when your + # model already existed. Please edit below which fields you would like to remove in this migration. + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/djello/db/migrate/20150922162018_remove_password_col.rb b/djello/db/migrate/20150922162018_remove_password_col.rb new file mode 100644 index 00000000..021c840a --- /dev/null +++ b/djello/db/migrate/20150922162018_remove_password_col.rb @@ -0,0 +1,5 @@ +class RemovePasswordCol < ActiveRecord::Migration + def change + remove_column :users, :password, :string + end +end diff --git a/djello/db/migrate/20150922184454_create_boards.rb b/djello/db/migrate/20150922184454_create_boards.rb new file mode 100644 index 00000000..94dcaf52 --- /dev/null +++ b/djello/db/migrate/20150922184454_create_boards.rb @@ -0,0 +1,9 @@ +class CreateBoards < ActiveRecord::Migration + def change + create_table :boards do |t| + t.string :title, null: false + t.references :user, null: false + t.timestamps null: false + end + end +end diff --git a/djello/db/migrate/20150922184503_create_lists.rb b/djello/db/migrate/20150922184503_create_lists.rb new file mode 100644 index 00000000..a9acb04a --- /dev/null +++ b/djello/db/migrate/20150922184503_create_lists.rb @@ -0,0 +1,10 @@ +class CreateLists < ActiveRecord::Migration + def change + create_table :lists do |t| + t.string :title, null: false + t.string :description, null: false + t.references :board, null: false + t.timestamps null: false + end + end +end diff --git a/djello/db/migrate/20150922184511_create_cards.rb b/djello/db/migrate/20150922184511_create_cards.rb new file mode 100644 index 00000000..0df8a5e8 --- /dev/null +++ b/djello/db/migrate/20150922184511_create_cards.rb @@ -0,0 +1,10 @@ +class CreateCards < ActiveRecord::Migration + def change + create_table :cards do |t| + t.string :title, null: false + t.string :description, null: false + t.references :list, null: false + t.timestamps null: false + end + end +end diff --git a/djello/db/migrate/20150924222309_create_card_memberships.rb b/djello/db/migrate/20150924222309_create_card_memberships.rb new file mode 100644 index 00000000..69ebc74d --- /dev/null +++ b/djello/db/migrate/20150924222309_create_card_memberships.rb @@ -0,0 +1,9 @@ +class CreateCardMemberships < ActiveRecord::Migration + def change + create_table :card_memberships do |t| + t.references :user + t.references :card + t.timestamps null: false + end + end +end diff --git a/djello/db/schema.rb b/djello/db/schema.rb new file mode 100644 index 00000000..8b5f5296 --- /dev/null +++ b/djello/db/schema.rb @@ -0,0 +1,68 @@ +# encoding: UTF-8 +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 20150924222309) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "boards", force: :cascade do |t| + t.string "title", null: false + t.integer "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "card_memberships", force: :cascade do |t| + t.integer "user_id" + t.integer "card_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "cards", force: :cascade do |t| + t.string "title", null: false + t.string "description", null: false + t.integer "list_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "lists", force: :cascade do |t| + t.string "title", null: false + t.string "description", null: false + t.integer "board_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "users", force: :cascade do |t| + t.string "username", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.integer "sign_in_count", default: 0, null: false + t.datetime "current_sign_in_at" + t.datetime "last_sign_in_at" + t.inet "current_sign_in_ip" + t.inet "last_sign_in_ip" + end + + add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree + add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree + +end diff --git a/djello/db/seeds.rb b/djello/db/seeds.rb new file mode 100644 index 00000000..de6b97c2 --- /dev/null +++ b/djello/db/seeds.rb @@ -0,0 +1,63 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). +# +# Examples: +# +# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) +# Mayor.create(name: 'Emanuel', city: cities.first) + + +User.destroy_all +Board.destroy_all +List.destroy_all +Card.destroy_all +CardMembership.destroy_all + +#create +2.times do + + user = User.create(username: Faker::Internet.user_name, + email: Faker::Internet.email, + password: 'foobar_1234', + password_confirmation: 'foobar_1234') + 3.times do |i| + board = user.boards.create(title: "Board Title #{i}") + + 2.times do + list = board.lists.create(title: Faker::Commerce.department, + description: Faker::Lorem.sentence) + + 4.times do + list.cards.create(title: Faker::SlackEmoji.activity, + description: Faker::Lorem.sentence ) + end + end + end +end + +testuser = User.create(username: 'tester', + email: Faker::Internet.email, + password: 'password', + password_confirmation: 'password') + +3.times do |i| + board = testuser.boards.create(title: "Random Board Title #{i}") + 2.times do + list = board.lists.create(title: Faker::Commerce.department, + description: Faker::Lorem.sentence ) + 4.times do + list.cards.create(title: Faker::SlackEmoji.activity, + description: Faker::Lorem.sentence ) + end + end +end + +users = User.all +cards = Card.all + +25.times do + + CardMembership.create(user_id: users.sample.id, + card_id: cards.sample.id) +end + diff --git a/djello/lib/assets/.keep b/djello/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/djello/lib/tasks/.keep b/djello/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/djello/log/.keep b/djello/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/djello/public/404.html b/djello/public/404.html new file mode 100644 index 00000000..b612547f --- /dev/null +++ b/djello/public/404.html @@ -0,0 +1,67 @@ + + + + The page you were looking for doesn't exist (404) + + + + + + +
+
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/djello/public/422.html b/djello/public/422.html new file mode 100644 index 00000000..a21f82b3 --- /dev/null +++ b/djello/public/422.html @@ -0,0 +1,67 @@ + + + + The change you wanted was rejected (422) + + + + + + +
+
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/djello/public/500.html b/djello/public/500.html new file mode 100644 index 00000000..061abc58 --- /dev/null +++ b/djello/public/500.html @@ -0,0 +1,66 @@ + + + + We're sorry, but something went wrong (500) + + + + + + +
+
+

We're sorry, but something went wrong.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/djello/public/favicon.ico b/djello/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/djello/public/robots.txt b/djello/public/robots.txt new file mode 100644 index 00000000..3c9c7c01 --- /dev/null +++ b/djello/public/robots.txt @@ -0,0 +1,5 @@ +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-agent: * +# Disallow: / diff --git a/djello/public/templates/boardLayout.html b/djello/public/templates/boardLayout.html new file mode 100644 index 00000000..76d6cdc6 --- /dev/null +++ b/djello/public/templates/boardLayout.html @@ -0,0 +1,19 @@ +
+ Select board: + + + + + New board + + +
Board is not shown
+ +
\ No newline at end of file diff --git a/djello/public/templates/boardShow.html b/djello/public/templates/boardShow.html new file mode 100644 index 00000000..ea320f49 --- /dev/null +++ b/djello/public/templates/boardShow.html @@ -0,0 +1,50 @@ +
+ +
+
+

{{board.title}}

+ Delete board +
+
+ + Done editing? + Cancel +
+ + + +
+
+ + +
+

{{list.title}}

+

{{list.description}}

+ Delete list +
+ + +
+

{{card.title}}

+

{{card.description}}

+ Edit Card +
+
+ +
+ Add a Card +
+
+ +
+
+
diff --git a/djello/public/templates/cardModal.html b/djello/public/templates/cardModal.html new file mode 100644 index 00000000..364e5c23 --- /dev/null +++ b/djello/public/templates/cardModal.html @@ -0,0 +1,83 @@ + \ No newline at end of file diff --git a/djello/public/templates/headerLayout.html b/djello/public/templates/headerLayout.html new file mode 100644 index 00000000..c2ebadab --- /dev/null +++ b/djello/public/templates/headerLayout.html @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/djello/public/templates/loggedInHeader.html b/djello/public/templates/loggedInHeader.html new file mode 100644 index 00000000..b97e7115 --- /dev/null +++ b/djello/public/templates/loggedInHeader.html @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/djello/public/templates/loginForm.html b/djello/public/templates/loginForm.html new file mode 100644 index 00000000..ac2e3687 --- /dev/null +++ b/djello/public/templates/loginForm.html @@ -0,0 +1,7 @@ +Form is here: + +
+ + + +
\ No newline at end of file diff --git a/djello/spec/controllers/angular_controller_spec.rb b/djello/spec/controllers/angular_controller_spec.rb new file mode 100644 index 00000000..be6e1107 --- /dev/null +++ b/djello/spec/controllers/angular_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe AngularController, type: :controller do + +end diff --git a/djello/spec/controllers/boards_controller_spec.rb b/djello/spec/controllers/boards_controller_spec.rb new file mode 100644 index 00000000..6ba914cf --- /dev/null +++ b/djello/spec/controllers/boards_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe BoardsController, type: :controller do + +end diff --git a/djello/spec/controllers/cards_controller_spec.rb b/djello/spec/controllers/cards_controller_spec.rb new file mode 100644 index 00000000..deda938d --- /dev/null +++ b/djello/spec/controllers/cards_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe CardsController, type: :controller do + +end diff --git a/djello/spec/controllers/lists_controller_spec.rb b/djello/spec/controllers/lists_controller_spec.rb new file mode 100644 index 00000000..d8072588 --- /dev/null +++ b/djello/spec/controllers/lists_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe ListsController, type: :controller do + +end diff --git a/djello/spec/controllers/sessions_controller_spec.rb b/djello/spec/controllers/sessions_controller_spec.rb new file mode 100644 index 00000000..003beded --- /dev/null +++ b/djello/spec/controllers/sessions_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe SessionsController, type: :controller do + +end diff --git a/djello/spec/controllers/users_controller_spec.rb b/djello/spec/controllers/users_controller_spec.rb new file mode 100644 index 00000000..e2c3d3b5 --- /dev/null +++ b/djello/spec/controllers/users_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe UsersController, type: :controller do + +end diff --git a/djello/spec/factories/boards.rb b/djello/spec/factories/boards.rb new file mode 100644 index 00000000..4f1feb5c --- /dev/null +++ b/djello/spec/factories/boards.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :board do + + end + +end diff --git a/djello/spec/factories/card_memberships.rb b/djello/spec/factories/card_memberships.rb new file mode 100644 index 00000000..71f4ae54 --- /dev/null +++ b/djello/spec/factories/card_memberships.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :card_membership do + + end + +end diff --git a/djello/spec/factories/cards.rb b/djello/spec/factories/cards.rb new file mode 100644 index 00000000..a520ec78 --- /dev/null +++ b/djello/spec/factories/cards.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :card do + + end + +end diff --git a/djello/spec/factories/lists.rb b/djello/spec/factories/lists.rb new file mode 100644 index 00000000..f8d806da --- /dev/null +++ b/djello/spec/factories/lists.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :list do + + end + +end diff --git a/djello/spec/factories/users.rb b/djello/spec/factories/users.rb new file mode 100644 index 00000000..209e6b1b --- /dev/null +++ b/djello/spec/factories/users.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :user do + + end + +end diff --git a/djello/spec/helpers/angular_helper_spec.rb b/djello/spec/helpers/angular_helper_spec.rb new file mode 100644 index 00000000..a215cb17 --- /dev/null +++ b/djello/spec/helpers/angular_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the AngularHelper. For example: +# +# describe AngularHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe AngularHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/djello/spec/helpers/boards_helper_spec.rb b/djello/spec/helpers/boards_helper_spec.rb new file mode 100644 index 00000000..e5639ad2 --- /dev/null +++ b/djello/spec/helpers/boards_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the BoardsHelper. For example: +# +# describe BoardsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe BoardsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/djello/spec/helpers/cards_helper_spec.rb b/djello/spec/helpers/cards_helper_spec.rb new file mode 100644 index 00000000..cd756fa9 --- /dev/null +++ b/djello/spec/helpers/cards_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the CardsHelper. For example: +# +# describe CardsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe CardsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/djello/spec/helpers/lists_helper_spec.rb b/djello/spec/helpers/lists_helper_spec.rb new file mode 100644 index 00000000..022e789b --- /dev/null +++ b/djello/spec/helpers/lists_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the ListsHelper. For example: +# +# describe ListsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe ListsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/djello/spec/helpers/sessions_helper_spec.rb b/djello/spec/helpers/sessions_helper_spec.rb new file mode 100644 index 00000000..94841983 --- /dev/null +++ b/djello/spec/helpers/sessions_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the SessionsHelper. For example: +# +# describe SessionsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe SessionsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/djello/spec/helpers/users_helper_spec.rb b/djello/spec/helpers/users_helper_spec.rb new file mode 100644 index 00000000..b2e34440 --- /dev/null +++ b/djello/spec/helpers/users_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the UsersHelper. For example: +# +# describe UsersHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe UsersHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/djello/spec/models/board_spec.rb b/djello/spec/models/board_spec.rb new file mode 100644 index 00000000..77f3fe83 --- /dev/null +++ b/djello/spec/models/board_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Board, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/djello/spec/models/card_membership_spec.rb b/djello/spec/models/card_membership_spec.rb new file mode 100644 index 00000000..d69b6582 --- /dev/null +++ b/djello/spec/models/card_membership_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe CardMembership, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/djello/spec/models/card_spec.rb b/djello/spec/models/card_spec.rb new file mode 100644 index 00000000..6f49b4ac --- /dev/null +++ b/djello/spec/models/card_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Card, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/djello/spec/models/list_spec.rb b/djello/spec/models/list_spec.rb new file mode 100644 index 00000000..47d98f6e --- /dev/null +++ b/djello/spec/models/list_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe List, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/djello/spec/models/user_spec.rb b/djello/spec/models/user_spec.rb new file mode 100644 index 00000000..47a31bb4 --- /dev/null +++ b/djello/spec/models/user_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe User, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/djello/vendor/assets/javascripts/.keep b/djello/vendor/assets/javascripts/.keep new file mode 100644 index 00000000..e69de29b diff --git a/djello/vendor/assets/javascripts/angular-modal-service.min.js b/djello/vendor/assets/javascripts/angular-modal-service.min.js new file mode 100644 index 00000000..4b2d4b0e --- /dev/null +++ b/djello/vendor/assets/javascripts/angular-modal-service.min.js @@ -0,0 +1,3 @@ +/*angular-modal-service v0.6.6 - https://github.com/dwmkerr/angular-modal-service */ +!function(){"use strict";var e=angular.module("angularModalService",[]);e.factory("ModalService",["$document","$compile","$controller","$http","$rootScope","$q","$templateCache",function(e,n,l,t,o,r,a){function c(){var e=this,c=function(e,n){var l=r.defer();if(e)l.resolve(e);else if(n){var o=a.get(n);void 0!==o?l.resolve(o):t({method:"GET",url:n,cache:!0}).then(function(e){a.put(n,e.data),l.resolve(e.data)},function(e){l.reject(e)})}else l.reject("No template or templateUrl has been specified.");return l.promise};e.showModal=function(e){var t=r.defer(),a=e.controller;return a?(e.controllerAs&&(a=a+" as "+e.controllerAs),c(e.template,e.templateUrl).then(function(c){var u=o.$new(),s=r.defer(),p={$scope:u,close:function(e,n){(void 0===n||null===n)&&(n=0),window.setTimeout(function(){s.resolve(e),u.$destroy(),m.remove(),p.close=null,t=null,s=null,$=null,p=null,m=null,u=null},n)}};if(e.inputs)for(var d in e.inputs)p[d]=e.inputs[d];var f=angular.element(c),v=n(f),m=v(u);p.$element=m;var h=l(a,p);e.appendElement?e.appendElement.append(m):i.append(m);var $={controller:h,scope:u,element:m,close:s.promise};t.resolve($)}).then(null,function(e){t.reject(e)}),t.promise):(t.reject("No controller has been specified."),t.promise)}}var i=e.find("body");return new c}])}(); +//# sourceMappingURL=angular-modal-service.min.js.map \ No newline at end of file diff --git a/djello/vendor/assets/javascripts/angular-ui-router.min.js b/djello/vendor/assets/javascripts/angular-ui-router.min.js new file mode 100644 index 00000000..18d8307f --- /dev/null +++ b/djello/vendor/assets/javascripts/angular-ui-router.min.js @@ -0,0 +1,7 @@ +/** + * State-based routing for AngularJS + * @version v0.2.15 + * @link http://angular-ui.github.com/ + * @license MIT License, http://www.opensource.org/licenses/MIT + */ +"undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="ui.router"),function(a,b,c){"use strict";function d(a,b){return N(new(N(function(){},{prototype:a})),b)}function e(a){return M(arguments,function(b){b!==a&&M(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)})}),a}function f(a,b){var c=[];for(var d in a.path){if(a.path[d]!==b.path[d])break;c.push(a.path[d])}return c}function g(a){if(Object.keys)return Object.keys(a);var b=[];return M(a,function(a,c){b.push(c)}),b}function h(a,b){if(Array.prototype.indexOf)return a.indexOf(b,Number(arguments[2])||0);var c=a.length>>>0,d=Number(arguments[2])||0;for(d=0>d?Math.ceil(d):Math.floor(d),0>d&&(d+=c);c>d;d++)if(d in a&&a[d]===b)return d;return-1}function i(a,b,c,d){var e,i=f(c,d),j={},k=[];for(var l in i)if(i[l].params&&(e=g(i[l].params),e.length))for(var m in e)h(k,e[m])>=0||(k.push(e[m]),j[e[m]]=a[e[m]]);return N({},j,b)}function j(a,b,c){if(!c){c=[];for(var d in a)c.push(d)}for(var e=0;e "));if(s[c]=d,J(a))q.push(c,[function(){return b.get(a)}],j);else{var e=b.annotate(a);M(e,function(a){a!==c&&i.hasOwnProperty(a)&&n(i[a],a)}),q.push(c,a,e)}r.pop(),s[c]=f}}function o(a){return K(a)&&a.then&&a.$$promises}if(!K(i))throw new Error("'invocables' must be an object");var p=g(i||{}),q=[],r=[],s={};return M(i,n),i=r=s=null,function(d,f,g){function h(){--u||(v||e(t,f.$$values),r.$$values=t,r.$$promises=r.$$promises||!0,delete r.$$inheritedValues,n.resolve(t))}function i(a){r.$$failure=a,n.reject(a)}function j(c,e,f){function j(a){l.reject(a),i(a)}function k(){if(!H(r.$$failure))try{l.resolve(b.invoke(e,g,t)),l.promise.then(function(a){t[c]=a,h()},j)}catch(a){j(a)}}var l=a.defer(),m=0;M(f,function(a){s.hasOwnProperty(a)&&!d.hasOwnProperty(a)&&(m++,s[a].then(function(b){t[a]=b,--m||k()},j))}),m||k(),s[c]=l.promise}if(o(d)&&g===c&&(g=f,f=d,d=null),d){if(!K(d))throw new Error("'locals' must be an object")}else d=k;if(f){if(!o(f))throw new Error("'parent' must be a promise returned by $resolve.resolve()")}else f=l;var n=a.defer(),r=n.promise,s=r.$$promises={},t=N({},d),u=1+q.length/3,v=!1;if(H(f.$$failure))return i(f.$$failure),r;f.$$inheritedValues&&e(t,m(f.$$inheritedValues,p)),N(s,f.$$promises),f.$$values?(v=e(t,m(f.$$values,p)),r.$$inheritedValues=m(f.$$values,p),h()):(f.$$inheritedValues&&(r.$$inheritedValues=m(f.$$inheritedValues,p)),f.then(h,i));for(var w=0,x=q.length;x>w;w+=3)d.hasOwnProperty(q[w])?h():j(q[w],q[w+1],q[w+2]);return r}},this.resolve=function(a,b,c,d){return this.study(a)(b,c,d)}}function q(a,b,c){this.fromConfig=function(a,b,c){return H(a.template)?this.fromString(a.template,b):H(a.templateUrl)?this.fromUrl(a.templateUrl,b):H(a.templateProvider)?this.fromProvider(a.templateProvider,b,c):null},this.fromString=function(a,b){return I(a)?a(b):a},this.fromUrl=function(c,d){return I(c)&&(c=c(d)),null==c?null:a.get(c,{cache:b,headers:{Accept:"text/html"}}).then(function(a){return a.data})},this.fromProvider=function(a,b,d){return c.invoke(a,null,d||{params:b})}}function r(a,b,e){function f(b,c,d,e){if(q.push(b),o[b])return o[b];if(!/^\w+(-+\w+)*(?:\[\])?$/.test(b))throw new Error("Invalid parameter name '"+b+"' in pattern '"+a+"'");if(p[b])throw new Error("Duplicate parameter name '"+b+"' in pattern '"+a+"'");return p[b]=new P.Param(b,c,d,e),p[b]}function g(a,b,c,d){var e=["",""],f=a.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&");if(!b)return f;switch(c){case!1:e=["(",")"+(d?"?":"")];break;case!0:e=["?(",")?"];break;default:e=["("+c+"|",")?"]}return f+e[0]+b+e[1]}function h(e,f){var g,h,i,j,k;return g=e[2]||e[3],k=b.params[g],i=a.substring(m,e.index),h=f?e[4]:e[4]||("*"==e[1]?".*":null),j=P.type(h||"string")||d(P.type("string"),{pattern:new RegExp(h,b.caseInsensitive?"i":c)}),{id:g,regexp:h,segment:i,type:j,cfg:k}}b=N({params:{}},K(b)?b:{});var i,j=/([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,k=/([:]?)([\w\[\]-]+)|\{([\w\[\]-]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,l="^",m=0,n=this.segments=[],o=e?e.params:{},p=this.params=e?e.params.$$new():new P.ParamSet,q=[];this.source=a;for(var r,s,t;(i=j.exec(a))&&(r=h(i,!1),!(r.segment.indexOf("?")>=0));)s=f(r.id,r.type,r.cfg,"path"),l+=g(r.segment,s.type.pattern.source,s.squash,s.isOptional),n.push(r.segment),m=j.lastIndex;t=a.substring(m);var u=t.indexOf("?");if(u>=0){var v=this.sourceSearch=t.substring(u);if(t=t.substring(0,u),this.sourcePath=a.substring(0,m+u),v.length>0)for(m=0;i=k.exec(v);)r=h(i,!0),s=f(r.id,r.type,r.cfg,"search"),m=j.lastIndex}else this.sourcePath=a,this.sourceSearch="";l+=g(t)+(b.strict===!1?"/?":"")+"$",n.push(t),this.regexp=new RegExp(l,b.caseInsensitive?"i":c),this.prefix=n[0],this.$$paramNames=q}function s(a){N(this,a)}function t(){function a(a){return null!=a?a.toString().replace(/\//g,"%2F"):a}function e(a){return null!=a?a.toString().replace(/%2F/g,"/"):a}function f(){return{strict:p,caseInsensitive:m}}function i(a){return I(a)||L(a)&&I(a[a.length-1])}function j(){for(;w.length;){var a=w.shift();if(a.pattern)throw new Error("You cannot override a type's .pattern at runtime.");b.extend(u[a.name],l.invoke(a.def))}}function k(a){N(this,a||{})}P=this;var l,m=!1,p=!0,q=!1,u={},v=!0,w=[],x={string:{encode:a,decode:e,is:function(a){return null==a||!H(a)||"string"==typeof a},pattern:/[^/]*/},"int":{encode:a,decode:function(a){return parseInt(a,10)},is:function(a){return H(a)&&this.decode(a.toString())===a},pattern:/\d+/},bool:{encode:function(a){return a?1:0},decode:function(a){return 0!==parseInt(a,10)},is:function(a){return a===!0||a===!1},pattern:/0|1/},date:{encode:function(a){return this.is(a)?[a.getFullYear(),("0"+(a.getMonth()+1)).slice(-2),("0"+a.getDate()).slice(-2)].join("-"):c},decode:function(a){if(this.is(a))return a;var b=this.capture.exec(a);return b?new Date(b[1],b[2]-1,b[3]):c},is:function(a){return a instanceof Date&&!isNaN(a.valueOf())},equals:function(a,b){return this.is(a)&&this.is(b)&&a.toISOString()===b.toISOString()},pattern:/[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,capture:/([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/},json:{encode:b.toJson,decode:b.fromJson,is:b.isObject,equals:b.equals,pattern:/[^/]*/},any:{encode:b.identity,decode:b.identity,equals:b.equals,pattern:/.*/}};t.$$getDefaultValue=function(a){if(!i(a.value))return a.value;if(!l)throw new Error("Injectable functions cannot be called at configuration time");return l.invoke(a.value)},this.caseInsensitive=function(a){return H(a)&&(m=a),m},this.strictMode=function(a){return H(a)&&(p=a),p},this.defaultSquashPolicy=function(a){if(!H(a))return q;if(a!==!0&&a!==!1&&!J(a))throw new Error("Invalid squash policy: "+a+". Valid policies: false, true, arbitrary-string");return q=a,a},this.compile=function(a,b){return new r(a,N(f(),b))},this.isMatcher=function(a){if(!K(a))return!1;var b=!0;return M(r.prototype,function(c,d){I(c)&&(b=b&&H(a[d])&&I(a[d]))}),b},this.type=function(a,b,c){if(!H(b))return u[a];if(u.hasOwnProperty(a))throw new Error("A type named '"+a+"' has already been defined.");return u[a]=new s(N({name:a},b)),c&&(w.push({name:a,def:c}),v||j()),this},M(x,function(a,b){u[b]=new s(N({name:b},a))}),u=d(u,{}),this.$get=["$injector",function(a){return l=a,v=!1,j(),M(x,function(a,b){u[b]||(u[b]=new s(a))}),this}],this.Param=function(a,b,d,e){function f(a){var b=K(a)?g(a):[],c=-1===h(b,"value")&&-1===h(b,"type")&&-1===h(b,"squash")&&-1===h(b,"array");return c&&(a={value:a}),a.$$fn=i(a.value)?a.value:function(){return a.value},a}function j(b,c,d){if(b.type&&c)throw new Error("Param '"+a+"' has two type configurations.");return c?c:b.type?b.type instanceof s?b.type:new s(b.type):"config"===d?u.any:u.string}function k(){var b={array:"search"===e?"auto":!1},c=a.match(/\[\]$/)?{array:!0}:{};return N(b,c,d).array}function m(a,b){var c=a.squash;if(!b||c===!1)return!1;if(!H(c)||null==c)return q;if(c===!0||J(c))return c;throw new Error("Invalid squash policy: '"+c+"'. Valid policies: false, true, or arbitrary string")}function p(a,b,d,e){var f,g,i=[{from:"",to:d||b?c:""},{from:null,to:d||b?c:""}];return f=L(a.replace)?a.replace:[],J(e)&&f.push({from:e,to:c}),g=o(f,function(a){return a.from}),n(i,function(a){return-1===h(g,a.from)}).concat(f)}function r(){if(!l)throw new Error("Injectable functions cannot be called at configuration time");var a=l.invoke(d.$$fn);if(null!==a&&a!==c&&!w.type.is(a))throw new Error("Default value ("+a+") for parameter '"+w.id+"' is not an instance of Type ("+w.type.name+")");return a}function t(a){function b(a){return function(b){return b.from===a}}function c(a){var c=o(n(w.replace,b(a)),function(a){return a.to});return c.length?c[0]:a}return a=c(a),H(a)?w.type.$normalize(a):r()}function v(){return"{Param:"+a+" "+b+" squash: '"+z+"' optional: "+y+"}"}var w=this;d=f(d),b=j(d,b,e);var x=k();b=x?b.$asArray(x,"search"===e):b,"string"!==b.name||x||"path"!==e||d.value!==c||(d.value="");var y=d.value!==c,z=m(d,y),A=p(d,x,y,z);N(this,{id:a,type:b,location:e,array:x,squash:z,replace:A,isOptional:y,value:t,dynamic:c,config:d,toString:v})},k.prototype={$$new:function(){return d(this,N(new k,{$$parent:this}))},$$keys:function(){for(var a=[],b=[],c=this,d=g(k.prototype);c;)b.push(c),c=c.$$parent;return b.reverse(),M(b,function(b){M(g(b),function(b){-1===h(a,b)&&-1===h(d,b)&&a.push(b)})}),a},$$values:function(a){var b={},c=this;return M(c.$$keys(),function(d){b[d]=c[d].value(a&&a[d])}),b},$$equals:function(a,b){var c=!0,d=this;return M(d.$$keys(),function(e){var f=a&&a[e],g=b&&b[e];d[e].type.equals(f,g)||(c=!1)}),c},$$validates:function(a){var d,e,f,g,h,i=this.$$keys();for(d=0;de;e++)if(b(j[e]))return;k&&b(k)}}function n(){return i=i||e.$on("$locationChangeSuccess",m)}var o,p=g.baseHref(),q=d.url();return l||n(),{sync:function(){m()},listen:function(){return n()},update:function(a){return a?void(q=d.url()):void(d.url()!==q&&(d.url(q),d.replace()))},push:function(a,b,e){var f=a.format(b||{});null!==f&&b&&b["#"]&&(f+="#"+b["#"]),d.url(f),o=e&&e.$$avoidResync?d.url():c,e&&e.replace&&d.replace()},href:function(c,e,f){if(!c.validates(e))return null;var g=a.html5Mode();b.isObject(g)&&(g=g.enabled);var i=c.format(e);if(f=f||{},g||null===i||(i="#"+a.hashPrefix()+i),null!==i&&e&&e["#"]&&(i+="#"+e["#"]),i=h(i,g,f.absolute),!f.absolute||!i)return i;var j=!g&&i?"/":"",k=d.port();return k=80===k||443===k?"":":"+k,[d.protocol(),"://",d.host(),k,j,i].join("")}}}var i,j=[],k=null,l=!1;this.rule=function(a){if(!I(a))throw new Error("'rule' must be a function");return j.push(a),this},this.otherwise=function(a){if(J(a)){var b=a;a=function(){return b}}else if(!I(a))throw new Error("'rule' must be a function");return k=a,this},this.when=function(a,b){var c,h=J(b);if(J(a)&&(a=d.compile(a)),!h&&!I(b)&&!L(b))throw new Error("invalid 'handler' in when()");var i={matcher:function(a,b){return h&&(c=d.compile(b),b=["$match",function(a){return c.format(a)}]),N(function(c,d){return g(c,b,a.exec(d.path(),d.search()))},{prefix:J(a.prefix)?a.prefix:""})},regex:function(a,b){if(a.global||a.sticky)throw new Error("when() RegExp must not be global or sticky");return h&&(c=b,b=["$match",function(a){return f(c,a)}]),N(function(c,d){return g(c,b,a.exec(d.path()))},{prefix:e(a)})}},j={matcher:d.isMatcher(a),regex:a instanceof RegExp};for(var k in j)if(j[k])return this.rule(i[k](a,b));throw new Error("invalid 'what' in when()")},this.deferIntercept=function(a){a===c&&(a=!0),l=a},this.$get=h,h.$inject=["$location","$rootScope","$injector","$browser"]}function v(a,e){function f(a){return 0===a.indexOf(".")||0===a.indexOf("^")}function m(a,b){if(!a)return c;var d=J(a),e=d?a:a.name,g=f(e);if(g){if(!b)throw new Error("No reference point given for path '"+e+"'");b=m(b);for(var h=e.split("."),i=0,j=h.length,k=b;j>i;i++)if(""!==h[i]||0!==i){if("^"!==h[i])break;if(!k.parent)throw new Error("Path '"+e+"' not valid for state '"+b.name+"'");k=k.parent}else k=b;h=h.slice(i).join("."),e=k.name+(k.name&&h?".":"")+h}var l=z[e];return!l||!d&&(d||l!==a&&l.self!==a)?c:l}function n(a,b){A[a]||(A[a]=[]),A[a].push(b)}function p(a){for(var b=A[a]||[];b.length;)q(b.shift())}function q(b){b=d(b,{self:b,resolve:b.resolve||{},toString:function(){return this.name}});var c=b.name;if(!J(c)||c.indexOf("@")>=0)throw new Error("State must have a valid name");if(z.hasOwnProperty(c))throw new Error("State '"+c+"'' is already defined");var e=-1!==c.indexOf(".")?c.substring(0,c.lastIndexOf(".")):J(b.parent)?b.parent:K(b.parent)&&J(b.parent.name)?b.parent.name:"";if(e&&!z[e])return n(e,b.self);for(var f in C)I(C[f])&&(b[f]=C[f](b,C.$delegates[f]));return z[c]=b,!b[B]&&b.url&&a.when(b.url,["$match","$stateParams",function(a,c){y.$current.navigable==b&&j(a,c)||y.transitionTo(b,a,{inherit:!0,location:!1})}]),p(c),b}function r(a){return a.indexOf("*")>-1}function s(a){for(var b=a.split("."),c=y.$current.name.split("."),d=0,e=b.length;e>d;d++)"*"===b[d]&&(c[d]="*");return"**"===b[0]&&(c=c.slice(h(c,b[1])),c.unshift("**")),"**"===b[b.length-1]&&(c.splice(h(c,b[b.length-2])+1,Number.MAX_VALUE),c.push("**")),b.length!=c.length?!1:c.join("")===b.join("")}function t(a,b){return J(a)&&!H(b)?C[a]:I(b)&&J(a)?(C[a]&&!C.$delegates[a]&&(C.$delegates[a]=C[a]),C[a]=b,this):this}function u(a,b){return K(a)?b=a:b.name=a,q(b),this}function v(a,e,f,h,l,n,p,q,t){function u(b,c,d,f){var g=a.$broadcast("$stateNotFound",b,c,d);if(g.defaultPrevented)return p.update(),D;if(!g.retry)return null;if(f.$retry)return p.update(),E;var h=y.transition=e.when(g.retry);return h.then(function(){return h!==y.transition?A:(b.options.$retry=!0,y.transitionTo(b.to,b.toParams,b.options))},function(){return D}),p.update(),h}function v(a,c,d,g,i,j){function m(){var c=[];return M(a.views,function(d,e){var g=d.resolve&&d.resolve!==a.resolve?d.resolve:{};g.$template=[function(){return f.load(e,{view:d,locals:i.globals,params:n,notify:j.notify})||""}],c.push(l.resolve(g,i.globals,i.resolve,a).then(function(c){if(I(d.controllerProvider)||L(d.controllerProvider)){var f=b.extend({},g,i.globals);c.$$controller=h.invoke(d.controllerProvider,null,f)}else c.$$controller=d.controller;c.$$state=a,c.$$controllerAs=d.controllerAs,i[e]=c}))}),e.all(c).then(function(){return i.globals})}var n=d?c:k(a.params.$$keys(),c),o={$stateParams:n};i.resolve=l.resolve(a.resolve,o,i.resolve,a);var p=[i.resolve.then(function(a){i.globals=a})];return g&&p.push(g),e.all(p).then(m).then(function(a){return i})}var A=e.reject(new Error("transition superseded")),C=e.reject(new Error("transition prevented")),D=e.reject(new Error("transition aborted")),E=e.reject(new Error("transition failed"));return x.locals={resolve:null,globals:{$stateParams:{}}},y={params:{},current:x.self,$current:x,transition:null},y.reload=function(a){return y.transitionTo(y.current,n,{reload:a||!0,inherit:!1,notify:!0})},y.go=function(a,b,c){return y.transitionTo(a,b,N({inherit:!0,relative:y.$current},c))},y.transitionTo=function(b,c,f){c=c||{},f=N({location:!0,inherit:!1,relative:null,notify:!0,reload:!1,$retry:!1},f||{});var g,j=y.$current,l=y.params,o=j.path,q=m(b,f.relative),r=c["#"];if(!H(q)){var s={to:b,toParams:c,options:f},t=u(s,j.self,l,f);if(t)return t;if(b=s.to,c=s.toParams,f=s.options,q=m(b,f.relative),!H(q)){if(!f.relative)throw new Error("No such state '"+b+"'");throw new Error("Could not resolve '"+b+"' from state '"+f.relative+"'")}}if(q[B])throw new Error("Cannot transition to abstract state '"+b+"'");if(f.inherit&&(c=i(n,c||{},y.$current,q)),!q.params.$$validates(c))return E;c=q.params.$$values(c),b=q;var z=b.path,D=0,F=z[D],G=x.locals,I=[];if(f.reload){if(J(f.reload)||K(f.reload)){if(K(f.reload)&&!f.reload.name)throw new Error("Invalid reload state object");var L=f.reload===!0?o[0]:m(f.reload);if(f.reload&&!L)throw new Error("No such reload state '"+(J(f.reload)?f.reload:f.reload.name)+"'");for(;F&&F===o[D]&&F!==L;)G=I[D]=F.locals,D++,F=z[D]}}else for(;F&&F===o[D]&&F.ownParams.$$equals(c,l);)G=I[D]=F.locals,D++,F=z[D];if(w(b,c,j,l,G,f))return r&&(c["#"]=r),y.params=c,O(y.params,n),f.location&&b.navigable&&b.navigable.url&&(p.push(b.navigable.url,c,{$$avoidResync:!0,replace:"replace"===f.location}),p.update(!0)),y.transition=null,e.when(y.current);if(c=k(b.params.$$keys(),c||{}),f.notify&&a.$broadcast("$stateChangeStart",b.self,c,j.self,l).defaultPrevented)return a.$broadcast("$stateChangeCancel",b.self,c,j.self,l),p.update(),C;for(var M=e.when(G),P=D;P=D;d--)g=o[d],g.self.onExit&&h.invoke(g.self.onExit,g.self,g.locals.globals),g.locals=null;for(d=D;d=0?e:e+"@"+(f?f.state.name:"")}function B(a,b){var c,d=a.match(/^\s*({[^}]*})\s*$/);if(d&&(a=b+"("+d[1]+")"),c=a.replace(/\n/g," ").match(/^([^(]+?)\s*(\((.*)\))?$/),!c||4!==c.length)throw new Error("Invalid state ref '"+a+"'");return{state:c[1],paramExpr:c[3]||null}}function C(a){var b=a.parent().inheritedData("$uiView");return b&&b.state&&b.state.name?b.state:void 0}function D(a,c){var d=["location","inherit","reload","absolute"];return{restrict:"A",require:["?^uiSrefActive","?^uiSrefActiveEq"],link:function(e,f,g,h){var i=B(g.uiSref,a.current.name),j=null,k=C(f)||a.$current,l="[object SVGAnimatedString]"===Object.prototype.toString.call(f.prop("href"))?"xlink:href":"href",m=null,n="A"===f.prop("tagName").toUpperCase(),o="FORM"===f[0].nodeName,p=o?"action":l,q=!0,r={relative:k,inherit:!0},s=e.$eval(g.uiSrefOpts)||{};b.forEach(d,function(a){a in s&&(r[a]=s[a])});var t=function(c){if(c&&(j=b.copy(c)),q){m=a.href(i.state,j,r);var d=h[1]||h[0];return d&&d.$$addStateInfo(i.state,j),null===m?(q=!1,!1):void g.$set(p,m)}};i.paramExpr&&(e.$watch(i.paramExpr,function(a,b){a!==j&&t(a)},!0),j=b.copy(e.$eval(i.paramExpr))),t(),o||f.bind("click",function(b){var d=b.which||b.button;if(!(d>1||b.ctrlKey||b.metaKey||b.shiftKey||f.attr("target"))){var e=c(function(){a.go(i.state,j,r)});b.preventDefault();var g=n&&!m?1:0;b.preventDefault=function(){g--<=0&&c.cancel(e)}}})}}}function E(a,b,c){return{restrict:"A",controller:["$scope","$element","$attrs",function(b,d,e){function f(){g()?d.addClass(i):d.removeClass(i)}function g(){for(var a=0;ae;e++){g=h[e];var l=this.params[g],m=d[e+1];for(f=0;fe;e++)g=h[e],k[g]=this.params[g].value(b[g]);return k},r.prototype.parameters=function(a){return H(a)?this.params[a]||null:this.$$paramNames},r.prototype.validates=function(a){return this.params.$$validates(a)},r.prototype.format=function(a){function b(a){return encodeURIComponent(a).replace(/-/g,function(a){return"%5C%"+a.charCodeAt(0).toString(16).toUpperCase()})}a=a||{};var c=this.segments,d=this.parameters(),e=this.params;if(!this.validates(a))return null;var f,g=!1,h=c.length-1,i=d.length,j=c[0];for(f=0;i>f;f++){var k=h>f,l=d[f],m=e[l],n=m.value(a[l]),p=m.isOptional&&m.type.equals(m.value(),n),q=p?m.squash:!1,r=m.type.encode(n);if(k){var s=c[f+1];if(q===!1)null!=r&&(j+=L(r)?o(r,b).join("-"):encodeURIComponent(r)),j+=s;else if(q===!0){var t=j.match(/\/$/)?/\/?(.*)/:/(.*)/;j+=s.match(t)[1]}else J(q)&&(j+=q+s)}else{if(null==r||p&&q!==!1)continue;L(r)||(r=[r]),r=o(r,encodeURIComponent).join("&"+l+"="),j+=(g?"&":"?")+(l+"="+r),g=!0}}return j},s.prototype.is=function(a,b){return!0},s.prototype.encode=function(a,b){return a},s.prototype.decode=function(a,b){return a},s.prototype.equals=function(a,b){return a==b},s.prototype.$subPattern=function(){var a=this.pattern.toString();return a.substr(1,a.length-2)},s.prototype.pattern=/.*/,s.prototype.toString=function(){return"{Type:"+this.name+"}"},s.prototype.$normalize=function(a){return this.is(a)?a:this.decode(a)},s.prototype.$asArray=function(a,b){function d(a,b){function d(a,b){return function(){return a[b].apply(a,arguments)}}function e(a){return L(a)?a:H(a)?[a]:[]}function f(a){switch(a.length){case 0:return c;case 1:return"auto"===b?a[0]:a;default:return a}}function g(a){return!a}function h(a,b){return function(c){c=e(c);var d=o(c,a);return b===!0?0===n(d,g).length:f(d)}}function i(a){return function(b,c){var d=e(b),f=e(c);if(d.length!==f.length)return!1;for(var g=0;g0,g=j.isAuthenticated();return a=a||{},c(b("login",a,d)).then(j.parse).then(f).then(function(a){return e&&!g?i("new-session")(a):a}).then(i("login"))},logout:function(a){var e=d(j._currentUser);return c(b("logout",void 0,a)).then(g).then(e).then(i("logout"))},register:function(a,d){return a=a||{},c(b("register",a,d)).then(j.parse).then(f).then(i("new-registration"))},currentUser:function(){return j.isAuthenticated()?a.when(j._currentUser):j.login()},isAuthenticated:function(){return!!j._currentUser}};return j}]})}(angular); \ No newline at end of file diff --git a/djello/vendor/assets/javascripts/restangular.js b/djello/vendor/assets/javascripts/restangular.js new file mode 100644 index 00000000..0230b64f --- /dev/null +++ b/djello/vendor/assets/javascripts/restangular.js @@ -0,0 +1,1355 @@ +/** + * Restful Resources service for AngularJS apps + * @version v1.4.0 - 2015-04-03 * @link https://github.com/mgonto/restangular + * @author Martin Gontovnikas + * @license MIT License, http://www.opensource.org/licenses/MIT + */(function() { + +var restangular = angular.module('restangular', []); + +restangular.provider('Restangular', function() { + // Configuration + var Configurer = {}; + Configurer.init = function(object, config) { + object.configuration = config; + + /** + * Those are HTTP safe methods for which there is no need to pass any data with the request. + */ + var safeMethods= ['get', 'head', 'options', 'trace', 'getlist']; + config.isSafe = function(operation) { + return _.contains(safeMethods, operation.toLowerCase()); + }; + + var absolutePattern = /^https?:\/\//i; + config.isAbsoluteUrl = function(string) { + return _.isUndefined(config.absoluteUrl) || _.isNull(config.absoluteUrl) ? + string && absolutePattern.test(string) : + config.absoluteUrl; + }; + + config.absoluteUrl = _.isUndefined(config.absoluteUrl) ? true : config.absoluteUrl; + object.setSelfLinkAbsoluteUrl = function(value) { + config.absoluteUrl = value; + }; + /** + * This is the BaseURL to be used with Restangular + */ + config.baseUrl = _.isUndefined(config.baseUrl) ? '' : config.baseUrl; + object.setBaseUrl = function(newBaseUrl) { + config.baseUrl = /\/$/.test(newBaseUrl) ? + newBaseUrl.substring(0, newBaseUrl.length-1) : + newBaseUrl; + return this; + }; + + /** + * Sets the extra fields to keep from the parents + */ + config.extraFields = config.extraFields || []; + object.setExtraFields = function(newExtraFields) { + config.extraFields = newExtraFields; + return this; + }; + + /** + * Some default $http parameter to be used in EVERY call + **/ + config.defaultHttpFields = config.defaultHttpFields || {}; + object.setDefaultHttpFields = function(values) { + config.defaultHttpFields = values; + return this; + }; + + config.withHttpValues = function(httpLocalConfig, obj) { + return _.defaults(obj, httpLocalConfig, config.defaultHttpFields); + }; + + config.encodeIds = _.isUndefined(config.encodeIds) ? true : config.encodeIds; + object.setEncodeIds = function(encode) { + config.encodeIds = encode; + }; + + config.defaultRequestParams = config.defaultRequestParams || { + get: {}, + post: {}, + put: {}, + remove: {}, + common: {} + }; + + object.setDefaultRequestParams = function(param1, param2) { + var methods = [], + params = param2 || param1; + if (!_.isUndefined(param2)) { + if (_.isArray(param1)) { + methods = param1; + } else { + methods.push(param1); + } + } else { + methods.push('common'); + } + + _.each(methods, function (method) { + config.defaultRequestParams[method] = params; + }); + return this; + }; + + object.requestParams = config.defaultRequestParams; + + config.defaultHeaders = config.defaultHeaders || {}; + object.setDefaultHeaders = function(headers) { + config.defaultHeaders = headers; + object.defaultHeaders = config.defaultHeaders; + return this; + }; + + object.defaultHeaders = config.defaultHeaders; + + /** + * Method overriders will set which methods are sent via POST with an X-HTTP-Method-Override + **/ + config.methodOverriders = config.methodOverriders || []; + object.setMethodOverriders = function(values) { + var overriders = _.extend([], values); + if (config.isOverridenMethod('delete', overriders)) { + overriders.push('remove'); + } + config.methodOverriders = overriders; + return this; + }; + + config.jsonp = _.isUndefined(config.jsonp) ? false : config.jsonp; + object.setJsonp = function(active) { + config.jsonp = active; + }; + + config.isOverridenMethod = function(method, values) { + var search = values || config.methodOverriders; + return !_.isUndefined(_.find(search, function(one) { + return one.toLowerCase() === method.toLowerCase(); + })); + }; + + /** + * Sets the URL creator type. For now, only Path is created. In the future we'll have queryParams + **/ + config.urlCreator = config.urlCreator || 'path'; + object.setUrlCreator = function(name) { + if (!_.has(config.urlCreatorFactory, name)) { + throw new Error('URL Path selected isn\'t valid'); + } + + config.urlCreator = name; + return this; + }; + + /** + * You can set the restangular fields here. The 3 required fields for Restangular are: + * + * id: Id of the element + * route: name of the route of this element + * parentResource: the reference to the parent resource + * + * All of this fields except for id, are handled (and created) by Restangular. By default, + * the field values will be id, route and parentResource respectively + */ + config.restangularFields = config.restangularFields || { + id: 'id', + route: 'route', + parentResource: 'parentResource', + restangularCollection: 'restangularCollection', + cannonicalId: '__cannonicalId', + etag: 'restangularEtag', + selfLink: 'href', + get: 'get', + getList: 'getList', + put: 'put', + post: 'post', + remove: 'remove', + head: 'head', + trace: 'trace', + options: 'options', + patch: 'patch', + getRestangularUrl: 'getRestangularUrl', + getRequestedUrl: 'getRequestedUrl', + putElement: 'putElement', + addRestangularMethod: 'addRestangularMethod', + getParentList: 'getParentList', + clone: 'clone', + ids: 'ids', + httpConfig: '_$httpConfig', + reqParams: 'reqParams', + one: 'one', + all: 'all', + several: 'several', + oneUrl: 'oneUrl', + allUrl: 'allUrl', + customPUT: 'customPUT', + customPOST: 'customPOST', + customDELETE: 'customDELETE', + customGET: 'customGET', + customGETLIST: 'customGETLIST', + customOperation: 'customOperation', + doPUT: 'doPUT', + doPOST: 'doPOST', + doDELETE: 'doDELETE', + doGET: 'doGET', + doGETLIST: 'doGETLIST', + fromServer: 'fromServer', + withConfig: 'withConfig', + withHttpConfig: 'withHttpConfig', + singleOne: 'singleOne', + plain: 'plain', + save: 'save', + restangularized: 'restangularized' + }; + object.setRestangularFields = function(resFields) { + config.restangularFields = + _.extend(config.restangularFields, resFields); + return this; + }; + + config.isRestangularized = function(obj) { + return !!obj[config.restangularFields.restangularized]; + }; + + config.setFieldToElem = function(field, elem, value) { + var properties = field.split('.'); + var idValue = elem; + _.each(_.initial(properties), function(prop) { + idValue[prop] = {}; + idValue = idValue[prop]; + }); + idValue[_.last(properties)] = value; + return this; + }; + + config.getFieldFromElem = function(field, elem) { + var properties = field.split('.'); + var idValue = elem; + _.each(properties, function(prop) { + if (idValue) { + idValue = idValue[prop]; + } + }); + return angular.copy(idValue); + }; + + config.setIdToElem = function(elem, id /*, route */) { + config.setFieldToElem(config.restangularFields.id, elem, id); + return this; + }; + + config.getIdFromElem = function(elem) { + return config.getFieldFromElem(config.restangularFields.id, elem); + }; + + config.isValidId = function(elemId) { + return '' !== elemId && !_.isUndefined(elemId) && !_.isNull(elemId); + }; + + config.setUrlToElem = function(elem, url /*, route */) { + config.setFieldToElem(config.restangularFields.selfLink, elem, url); + return this; + }; + + config.getUrlFromElem = function(elem) { + return config.getFieldFromElem(config.restangularFields.selfLink, elem); + }; + + config.useCannonicalId = _.isUndefined(config.useCannonicalId) ? false : config.useCannonicalId; + object.setUseCannonicalId = function(value) { + config.useCannonicalId = value; + return this; + }; + + config.getCannonicalIdFromElem = function(elem) { + var cannonicalId = elem[config.restangularFields.cannonicalId]; + var actualId = config.isValidId(cannonicalId) ? cannonicalId : config.getIdFromElem(elem); + return actualId; + }; + + /** + * Sets the Response parser. This is used in case your response isn't directly the data. + * For example if you have a response like {meta: {'meta'}, data: {name: 'Gonto'}} + * you can extract this data which is the one that needs wrapping + * + * The ResponseExtractor is a function that receives the response and the method executed. + */ + + config.responseInterceptors = config.responseInterceptors || []; + + config.defaultResponseInterceptor = function(data /*, operation, what, url, response, deferred */) { + return data; + }; + + config.responseExtractor = function(data, operation, what, url, response, deferred) { + var interceptors = angular.copy(config.responseInterceptors); + interceptors.push(config.defaultResponseInterceptor); + var theData = data; + _.each(interceptors, function(interceptor) { + theData = interceptor(theData, operation, + what, url, response, deferred); + }); + return theData; + }; + + object.addResponseInterceptor = function(extractor) { + config.responseInterceptors.push(extractor); + return this; + }; + + config.errorInterceptors = config.errorInterceptors || []; + object.addErrorInterceptor = function(interceptor) { + config.errorInterceptors.push(interceptor); + return this; + }; + + object.setResponseInterceptor = object.addResponseInterceptor; + object.setResponseExtractor = object.addResponseInterceptor; + object.setErrorInterceptor = object.addErrorInterceptor; + + /** + * Response interceptor is called just before resolving promises. + */ + + + /** + * Request interceptor is called before sending an object to the server. + */ + config.requestInterceptors = config.requestInterceptors || []; + + config.defaultInterceptor = function(element, operation, path, url, headers, params, httpConfig) { + return { + element: element, + headers: headers, + params: params, + httpConfig: httpConfig + }; + }; + + config.fullRequestInterceptor = function(element, operation, path, url, headers, params, httpConfig) { + var interceptors = angular.copy(config.requestInterceptors); + var defaultRequest = config.defaultInterceptor(element, operation, path, url, headers, params, httpConfig); + return _.reduce(interceptors, function(request, interceptor) { + return _.extend(request, interceptor(request.element, operation, + path, url, request.headers, request.params, request.httpConfig)); + }, defaultRequest); + }; + + object.addRequestInterceptor = function(interceptor) { + config.requestInterceptors.push(function(elem, operation, path, url, headers, params, httpConfig) { + return { + headers: headers, + params: params, + element: interceptor(elem, operation, path, url), + httpConfig: httpConfig + }; + }); + return this; + }; + + object.setRequestInterceptor = object.addRequestInterceptor; + + object.addFullRequestInterceptor = function(interceptor) { + config.requestInterceptors.push(interceptor); + return this; + }; + + object.setFullRequestInterceptor = object.addFullRequestInterceptor; + + config.onBeforeElemRestangularized = config.onBeforeElemRestangularized || function(elem) { + return elem; + }; + object.setOnBeforeElemRestangularized = function(post) { + config.onBeforeElemRestangularized = post; + return this; + }; + + object.setRestangularizePromiseInterceptor = function(interceptor) { + config.restangularizePromiseInterceptor = interceptor; + return this; + }; + + /** + * This method is called after an element has been "Restangularized". + * + * It receives the element, a boolean indicating if it's an element or a collection + * and the name of the model + * + */ + config.onElemRestangularized = config.onElemRestangularized || function(elem) { + return elem; + }; + object.setOnElemRestangularized = function(post) { + config.onElemRestangularized = post; + return this; + }; + + config.shouldSaveParent = config.shouldSaveParent || function() { + return true; + }; + object.setParentless = function(values) { + if (_.isArray(values)) { + config.shouldSaveParent = function(route) { + return !_.contains(values, route); + }; + } else if (_.isBoolean(values)) { + config.shouldSaveParent = function() { + return !values; + }; + } + return this; + }; + + /** + * This lets you set a suffix to every request. + * + * For example, if your api requires that for JSon requests you do /users/123.json, you can set that + * in here. + * + * + * By default, the suffix is null + */ + config.suffix = _.isUndefined(config.suffix) ? null : config.suffix; + object.setRequestSuffix = function(newSuffix) { + config.suffix = newSuffix; + return this; + }; + + /** + * Add element transformers for certain routes. + */ + config.transformers = config.transformers || {}; + object.addElementTransformer = function(type, secondArg, thirdArg) { + var isCollection = null; + var transformer = null; + if (arguments.length === 2) { + transformer = secondArg; + } else { + transformer = thirdArg; + isCollection = secondArg; + } + + var typeTransformers = config.transformers[type]; + if (!typeTransformers) { + typeTransformers = config.transformers[type] = []; + } + + typeTransformers.push(function(coll, elem) { + if (_.isNull(isCollection) || (coll === isCollection)) { + return transformer(elem); + } + return elem; + }); + + return object; + }; + + object.extendCollection = function(route, fn) { + return object.addElementTransformer(route, true, fn); + }; + + object.extendModel = function(route, fn) { + return object.addElementTransformer(route, false, fn); + }; + + config.transformElem = function(elem, isCollection, route, Restangular, force) { + if (!force && !config.transformLocalElements && !elem[config.restangularFields.fromServer]) { + return elem; + } + var typeTransformers = config.transformers[route]; + var changedElem = elem; + if (typeTransformers) { + _.each(typeTransformers, function(transformer) { + changedElem = transformer(isCollection, changedElem); + }); + } + return config.onElemRestangularized(changedElem, isCollection, route, Restangular); + }; + + config.transformLocalElements = _.isUndefined(config.transformLocalElements) ? + false : + config.transformLocalElements; + + object.setTransformOnlyServerElements = function(active) { + config.transformLocalElements = !active; + }; + + config.fullResponse = _.isUndefined(config.fullResponse) ? false : config.fullResponse; + object.setFullResponse = function(full) { + config.fullResponse = full; + return this; + }; + + + //Internal values and functions + config.urlCreatorFactory = {}; + + /** + * Base URL Creator. Base prototype for everything related to it + **/ + + var BaseCreator = function() { + }; + + BaseCreator.prototype.setConfig = function(config) { + this.config = config; + return this; + }; + + BaseCreator.prototype.parentsArray = function(current) { + var parents = []; + while(current) { + parents.push(current); + current = current[this.config.restangularFields.parentResource]; + } + return parents.reverse(); + }; + + function RestangularResource(config, $http, url, configurer) { + var resource = {}; + _.each(_.keys(configurer), function(key) { + var value = configurer[key]; + + // Add default parameters + value.params = _.extend({}, value.params, config.defaultRequestParams[value.method.toLowerCase()]); + // We don't want the ? if no params are there + if (_.isEmpty(value.params)) { + delete value.params; + } + + if (config.isSafe(value.method)) { + + resource[key] = function() { + return $http(_.extend(value, { + url: url + })); + }; + + } else { + + resource[key] = function(data) { + return $http(_.extend(value, { + url: url, + data: data + })); + }; + + } + }); + + return resource; + } + + BaseCreator.prototype.resource = function(current, $http, localHttpConfig, callHeaders, callParams, what, etag,operation) { + + var params = _.defaults(callParams || {}, this.config.defaultRequestParams.common); + var headers = _.defaults(callHeaders || {}, this.config.defaultHeaders); + + if (etag) { + if (!config.isSafe(operation)) { + headers['If-Match'] = etag; + } else { + headers['If-None-Match'] = etag; + } + } + + var url = this.base(current); + + if (what) { + var add = ''; + if (!/\/$/.test(url)) { + add += '/'; + } + add += what; + url += add; + } + + if (this.config.suffix && + url.indexOf(this.config.suffix, url.length - this.config.suffix.length) === -1 && + !this.config.getUrlFromElem(current)) { + url += this.config.suffix; + } + + current[this.config.restangularFields.httpConfig] = undefined; + + return RestangularResource(this.config, $http, url, { + getList: this.config.withHttpValues(localHttpConfig, + {method: 'GET', + params: params, + headers: headers}), + + get: this.config.withHttpValues(localHttpConfig, + {method: 'GET', + params: params, + headers: headers}), + + jsonp: this.config.withHttpValues(localHttpConfig, + {method: 'jsonp', + params: params, + headers: headers}), + + put: this.config.withHttpValues(localHttpConfig, + {method: 'PUT', + params: params, + headers: headers}), + + post: this.config.withHttpValues(localHttpConfig, + {method: 'POST', + params: params, + headers: headers}), + + remove: this.config.withHttpValues(localHttpConfig, + {method: 'DELETE', + params: params, + headers: headers}), + + head: this.config.withHttpValues(localHttpConfig, + {method: 'HEAD', + params: params, + headers: headers}), + + trace: this.config.withHttpValues(localHttpConfig, + {method: 'TRACE', + params: params, + headers: headers}), + + options: this.config.withHttpValues(localHttpConfig, + {method: 'OPTIONS', + params: params, + headers: headers}), + + patch: this.config.withHttpValues(localHttpConfig, + {method: 'PATCH', + params: params, + headers: headers}) + }); + }; + + /** + * This is the Path URL creator. It uses Path to show Hierarchy in the Rest API. + * This means that if you have an Account that then has a set of Buildings, a URL to a building + * would be /accounts/123/buildings/456 + **/ + var Path = function() { + }; + + Path.prototype = new BaseCreator(); + + Path.prototype.normalizeUrl = function (url){ + var parts = /(http[s]?:\/\/)?(.*)?/.exec(url); + parts[2] = parts[2].replace(/[\\\/]+/g, '/'); + return (typeof parts[1] !== 'undefined')? parts[1] + parts[2] : parts[2]; + }; + + Path.prototype.base = function(current) { + var __this = this; + return _.reduce(this.parentsArray(current), function(acum, elem) { + var elemUrl; + var elemSelfLink = __this.config.getUrlFromElem(elem); + if (elemSelfLink) { + if (__this.config.isAbsoluteUrl(elemSelfLink)) { + return elemSelfLink; + } else { + elemUrl = elemSelfLink; + } + } else { + elemUrl = elem[__this.config.restangularFields.route]; + + if (elem[__this.config.restangularFields.restangularCollection]) { + var ids = elem[__this.config.restangularFields.ids]; + if (ids) { + elemUrl += '/' + ids.join(','); + } + } else { + var elemId; + if (__this.config.useCannonicalId) { + elemId = __this.config.getCannonicalIdFromElem(elem); + } else { + elemId = __this.config.getIdFromElem(elem); + } + + if (config.isValidId(elemId) && !elem.singleOne) { + elemUrl += '/' + (__this.config.encodeIds ? encodeURIComponent(elemId) : elemId); + } + } + } + acum = acum.replace(/\/$/, '') + '/' + elemUrl; + return __this.normalizeUrl(acum); + + }, this.config.baseUrl); + }; + + + + Path.prototype.fetchUrl = function(current, what) { + var baseUrl = this.base(current); + if (what) { + baseUrl += '/' + what; + } + return baseUrl; + }; + + Path.prototype.fetchRequestedUrl = function(current, what) { + var url = this.fetchUrl(current, what); + var params = current[config.restangularFields.reqParams]; + + // From here on and until the end of fetchRequestedUrl, + // the code has been kindly borrowed from angular.js + // The reason for such code bloating is coherence: + // If the user were to use this for cache management, the + // serialization of parameters would need to be identical + // to the one done by angular for cache keys to match. + function sortedKeys(obj) { + var keys = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + keys.push(key); + } + } + return keys.sort(); + } + + function forEachSorted(obj, iterator, context) { + var keys = sortedKeys(obj); + for ( var i = 0; i < keys.length; i++) { + iterator.call(context, obj[keys[i]], keys[i]); + } + return keys; + } + + function encodeUriQuery(val, pctEncodeSpaces) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); + } + + if (!params) { return url + (this.config.suffix || ''); } + + var parts = []; + forEachSorted(params, function(value, key) { + if (value === null || value === undefined) { return; } + if (!angular.isArray(value)) { value = [value]; } + + angular.forEach(value, function(v) { + if (angular.isObject(v)) { + v = angular.toJson(v); + } + parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(v)); + }); + }); + + return url + (this.config.suffix || '') + ((url.indexOf('?') === -1) ? '?' : '&') + parts.join('&'); + }; + + config.urlCreatorFactory.path = Path; + }; + + var globalConfiguration = {}; + + Configurer.init(this, globalConfiguration); + + + + this.$get = ['$http', '$q', function($http, $q) { + + function createServiceForConfiguration(config) { + var service = {}; + + var urlHandler = new config.urlCreatorFactory[config.urlCreator](); + urlHandler.setConfig(config); + + function restangularizeBase(parent, elem, route, reqParams, fromServer) { + elem[config.restangularFields.route] = route; + elem[config.restangularFields.getRestangularUrl] = _.bind(urlHandler.fetchUrl, urlHandler, elem); + elem[config.restangularFields.getRequestedUrl] = _.bind(urlHandler.fetchRequestedUrl, urlHandler, elem); + elem[config.restangularFields.addRestangularMethod] = _.bind(addRestangularMethodFunction, elem); + elem[config.restangularFields.clone] = _.bind(copyRestangularizedElement, elem, elem); + elem[config.restangularFields.reqParams] = _.isEmpty(reqParams) ? null : reqParams; + elem[config.restangularFields.withHttpConfig] = _.bind(withHttpConfig, elem); + elem[config.restangularFields.plain] = _.bind(stripRestangular, elem, elem); + + // Tag element as restangularized + elem[config.restangularFields.restangularized] = true; + + // RequestLess connection + elem[config.restangularFields.one] = _.bind(one, elem, elem); + elem[config.restangularFields.all] = _.bind(all, elem, elem); + elem[config.restangularFields.several] = _.bind(several, elem, elem); + elem[config.restangularFields.oneUrl] = _.bind(oneUrl, elem, elem); + elem[config.restangularFields.allUrl] = _.bind(allUrl, elem, elem); + + elem[config.restangularFields.fromServer] = !!fromServer; + + if (parent && config.shouldSaveParent(route)) { + var parentId = config.getIdFromElem(parent); + var parentUrl = config.getUrlFromElem(parent); + + var restangularFieldsForParent = _.union( + _.values( _.pick(config.restangularFields, ['route', 'singleOne', 'parentResource']) ), + config.extraFields + ); + var parentResource = _.pick(parent, restangularFieldsForParent); + + if (config.isValidId(parentId)) { + config.setIdToElem(parentResource, parentId, route); + } + if (config.isValidId(parentUrl)) { + config.setUrlToElem(parentResource, parentUrl, route); + } + + elem[config.restangularFields.parentResource] = parentResource; + } else { + elem[config.restangularFields.parentResource] = null; + } + return elem; + } + + function one(parent, route, id, singleOne) { + var error; + if (_.isNumber(route) || _.isNumber(parent)) { + error = 'You\'re creating a Restangular entity with the number '; + error += 'instead of the route or the parent. For example, you can\'t call .one(12).'; + throw new Error(error); + } + if (_.isUndefined(route)) { + error = 'You\'re creating a Restangular entity either without the path. '; + error += 'For example you can\'t call .one(). Please check if your arguments are valid.'; + throw new Error(error); + } + var elem = {}; + config.setIdToElem(elem, id, route); + config.setFieldToElem(config.restangularFields.singleOne, elem, singleOne); + return restangularizeElem(parent, elem , route, false); + } + + + function all(parent, route) { + return restangularizeCollection(parent, [] , route, false); + } + + function several(parent, route /*, ids */) { + var collection = []; + collection[config.restangularFields.ids] = Array.prototype.splice.call(arguments, 2); + return restangularizeCollection(parent, collection , route, false); + } + + function oneUrl(parent, route, url) { + if (!route) { + throw new Error('Route is mandatory when creating new Restangular objects.'); + } + var elem = {}; + config.setUrlToElem(elem, url, route); + return restangularizeElem(parent, elem , route, false); + } + + + function allUrl(parent, route, url) { + if (!route) { + throw new Error('Route is mandatory when creating new Restangular objects.'); + } + var elem = {}; + config.setUrlToElem(elem, url, route); + return restangularizeCollection(parent, elem , route, false); + } + // Promises + function restangularizePromise(promise, isCollection, valueToFill) { + promise.call = _.bind(promiseCall, promise); + promise.get = _.bind(promiseGet, promise); + promise[config.restangularFields.restangularCollection] = isCollection; + if (isCollection) { + promise.push = _.bind(promiseCall, promise, 'push'); + } + promise.$object = valueToFill; + if (config.restangularizePromiseInterceptor) { + config.restangularizePromiseInterceptor(promise); + } + return promise; + } + + function promiseCall(method) { + var deferred = $q.defer(); + var callArgs = arguments; + var filledValue = {}; + this.then(function(val) { + var params = Array.prototype.slice.call(callArgs, 1); + var func = val[method]; + func.apply(val, params); + filledValue = val; + deferred.resolve(val); + }); + return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue); + } + + function promiseGet(what) { + var deferred = $q.defer(); + var filledValue = {}; + this.then(function(val) { + filledValue = val[what]; + deferred.resolve(filledValue); + }); + return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue); + } + + function resolvePromise(deferred, response, data, filledValue) { + _.extend(filledValue, data); + + // Trigger the full response interceptor. + if (config.fullResponse) { + return deferred.resolve(_.extend(response, { + data: data + })); + } else { + deferred.resolve(data); + } + } + + + // Elements + function stripRestangular(elem) { + if (_.isArray(elem)) { + var array = []; + _.each(elem, function(value) { + array.push(config.isRestangularized(value) ? stripRestangular(value) : value); + }); + return array; + } else { + return _.omit(elem, _.values(_.omit(config.restangularFields, 'id'))); + } + } + + function addCustomOperation(elem) { + elem[config.restangularFields.customOperation] = _.bind(customFunction, elem); + _.each(['put', 'post', 'get', 'delete'], function(oper) { + _.each(['do', 'custom'], function(alias) { + var callOperation = oper === 'delete' ? 'remove' : oper; + var name = alias + oper.toUpperCase(); + var callFunction; + + if (callOperation !== 'put' && callOperation !== 'post') { + callFunction = customFunction; + } else { + callFunction = function(operation, elem, path, params, headers) { + return _.bind(customFunction, this)(operation, path, params, headers, elem); + }; + } + elem[name] = _.bind(callFunction, elem, callOperation); + }); + }); + elem[config.restangularFields.customGETLIST] = _.bind(fetchFunction, elem); + elem[config.restangularFields.doGETLIST] = elem[config.restangularFields.customGETLIST]; + } + + function copyRestangularizedElement(fromElement, toElement) { + var copiedElement = angular.copy(fromElement, toElement); + return restangularizeElem(copiedElement[config.restangularFields.parentResource], + copiedElement, copiedElement[config.restangularFields.route], true); + } + + function restangularizeElem(parent, element, route, fromServer, collection, reqParams) { + var elem = config.onBeforeElemRestangularized(element, false, route); + + var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer); + + if (config.useCannonicalId) { + localElem[config.restangularFields.cannonicalId] = config.getIdFromElem(localElem); + } + + if (collection) { + localElem[config.restangularFields.getParentList] = function() { + return collection; + }; + } + + localElem[config.restangularFields.restangularCollection] = false; + localElem[config.restangularFields.get] = _.bind(getFunction, localElem); + localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem); + localElem[config.restangularFields.put] = _.bind(putFunction, localElem); + localElem[config.restangularFields.post] = _.bind(postFunction, localElem); + localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem); + localElem[config.restangularFields.head] = _.bind(headFunction, localElem); + localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem); + localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem); + localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem); + localElem[config.restangularFields.save] = _.bind(save, localElem); + + addCustomOperation(localElem); + return config.transformElem(localElem, false, route, service, true); + } + + function restangularizeCollection(parent, element, route, fromServer, reqParams) { + var elem = config.onBeforeElemRestangularized(element, true, route); + + var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer); + localElem[config.restangularFields.restangularCollection] = true; + localElem[config.restangularFields.post] = _.bind(postFunction, localElem, null); + localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem); + localElem[config.restangularFields.head] = _.bind(headFunction, localElem); + localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem); + localElem[config.restangularFields.putElement] = _.bind(putElementFunction, localElem); + localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem); + localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem); + localElem[config.restangularFields.get] = _.bind(getById, localElem); + localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem, null); + + addCustomOperation(localElem); + return config.transformElem(localElem, true, route, service, true); + } + + function restangularizeCollectionAndElements(parent, element, route) { + var collection = restangularizeCollection(parent, element, route, false); + _.each(collection, function(elem) { + restangularizeElem(parent, elem, route, false); + }); + return collection; + } + + function getById(id, reqParams, headers){ + return this.customGET(id.toString(), reqParams, headers); + } + + function putElementFunction(idx, params, headers) { + var __this = this; + var elemToPut = this[idx]; + var deferred = $q.defer(); + var filledArray = []; + filledArray = config.transformElem(filledArray, true, elemToPut[config.restangularFields.route], service); + elemToPut.put(params, headers).then(function(serverElem) { + var newArray = copyRestangularizedElement(__this); + newArray[idx] = serverElem; + filledArray = newArray; + deferred.resolve(newArray); + }, function(response) { + deferred.reject(response); + }); + + return restangularizePromise(deferred.promise, true, filledArray); + } + + function parseResponse(resData, operation, route, fetchUrl, response, deferred) { + var data = config.responseExtractor(resData, operation, route, fetchUrl, response, deferred); + var etag = response.headers('ETag'); + if (data && etag) { + data[config.restangularFields.etag] = etag; + } + return data; + } + + + function fetchFunction(what, reqParams, headers) { + var __this = this; + var deferred = $q.defer(); + var operation = 'getList'; + var url = urlHandler.fetchUrl(this, what); + var whatFetched = what || __this[config.restangularFields.route]; + + var request = config.fullRequestInterceptor(null, operation, + whatFetched, url, headers || {}, reqParams || {}, this[config.restangularFields.httpConfig] || {}); + + var filledArray = []; + filledArray = config.transformElem(filledArray, true, whatFetched, service); + + var method = 'getList'; + + if (config.jsonp) { + method = 'jsonp'; + } + + var okCallback = function(response) { + var resData = response.data; + var fullParams = response.config.params; + var data = parseResponse(resData, operation, whatFetched, url, response, deferred); + + // support empty response for getList() calls (some APIs respond with 204 and empty body) + if (_.isUndefined(data) || '' === data) { + data = []; + } + if (!_.isArray(data)) { + throw new Error('Response for getList SHOULD be an array and not an object or something else'); + } + var processedData = _.map(data, function(elem) { + if (!__this[config.restangularFields.restangularCollection]) { + return restangularizeElem(__this, elem, what, true, data); + } else { + return restangularizeElem(__this[config.restangularFields.parentResource], + elem, __this[config.restangularFields.route], true, data); + } + }); + + processedData = _.extend(data, processedData); + + if (!__this[config.restangularFields.restangularCollection]) { + resolvePromise( + deferred, + response, + restangularizeCollection( + __this, + processedData, + what, + true, + fullParams + ), + filledArray + ); + } else { + resolvePromise( + deferred, + response, + restangularizeCollection( + __this[config.restangularFields.parentResource], + processedData, + __this[config.restangularFields.route], + true, + fullParams + ), + filledArray + ); + } + }; + + urlHandler.resource(this, $http, request.httpConfig, request.headers, request.params, what, + this[config.restangularFields.etag], operation)[method]().then(okCallback, function error(response) { + if (response.status === 304 && __this[config.restangularFields.restangularCollection]) { + resolvePromise(deferred, response, __this, filledArray); + } else if ( _.every(config.errorInterceptors, function(cb) { return cb(response, deferred, okCallback) !== false; }) ) { + // triggered if no callback returns false + deferred.reject(response); + } + }); + + return restangularizePromise(deferred.promise, true, filledArray); + } + + function withHttpConfig(httpConfig) { + this[config.restangularFields.httpConfig] = httpConfig; + return this; + } + + function save(params, headers) { + if (this[config.restangularFields.fromServer]) { + return this[config.restangularFields.put](params, headers); + } else { + return _.bind(elemFunction, this)('post', undefined, params, undefined, headers); + } + } + + function elemFunction(operation, what, params, obj, headers) { + var __this = this; + var deferred = $q.defer(); + var resParams = params || {}; + var route = what || this[config.restangularFields.route]; + var fetchUrl = urlHandler.fetchUrl(this, what); + + var callObj = obj || this; + // fallback to etag on restangular object (since for custom methods we probably don't explicitly specify the etag field) + var etag = callObj[config.restangularFields.etag] || (operation !== 'post' ? this[config.restangularFields.etag] : null); + + if (_.isObject(callObj) && config.isRestangularized(callObj)) { + callObj = stripRestangular(callObj); + } + var request = config.fullRequestInterceptor(callObj, operation, route, fetchUrl, + headers || {}, resParams || {}, this[config.restangularFields.httpConfig] || {}); + + var filledObject = {}; + filledObject = config.transformElem(filledObject, false, route, service); + + var okCallback = function(response) { + var resData = response.data; + var fullParams = response.config.params; + var elem = parseResponse(resData, operation, route, fetchUrl, response, deferred); + if (elem) { + + if (operation === 'post' && !__this[config.restangularFields.restangularCollection]) { + var data = restangularizeElem( + __this[config.restangularFields.parentResource], + elem, + route, + true, + null, + fullParams + ); + resolvePromise(deferred, response, data, filledObject); + } else { + var data = restangularizeElem( + __this[config.restangularFields.parentResource], + elem, + __this[config.restangularFields.route], + true, + null, + fullParams + ); + + data[config.restangularFields.singleOne] = __this[config.restangularFields.singleOne]; + resolvePromise(deferred, response, data, filledObject); + } + + } else { + resolvePromise(deferred, response, undefined, filledObject); + } + }; + + var errorCallback = function(response) { + if (response.status === 304 && config.isSafe(operation)) { + resolvePromise(deferred, response, __this, filledObject); + } else if ( _.every(config.errorInterceptors, function(cb) { return cb(response, deferred, okCallback) !== false; }) ) { + // triggered if no callback returns false + deferred.reject(response); + } + }; + // Overriding HTTP Method + var callOperation = operation; + var callHeaders = _.extend({}, request.headers); + var isOverrideOperation = config.isOverridenMethod(operation); + if (isOverrideOperation) { + callOperation = 'post'; + callHeaders = _.extend(callHeaders, {'X-HTTP-Method-Override': operation === 'remove' ? 'DELETE' : operation.toUpperCase()}); + } else if (config.jsonp && callOperation === 'get') { + callOperation = 'jsonp'; + } + + if (config.isSafe(operation)) { + if (isOverrideOperation) { + urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, + what, etag, callOperation)[callOperation]({}).then(okCallback, errorCallback); + } else { + urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, + what, etag, callOperation)[callOperation]().then(okCallback, errorCallback); + } + } else { + urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, + what, etag, callOperation)[callOperation](request.element).then(okCallback, errorCallback); + } + + return restangularizePromise(deferred.promise, false, filledObject); + } + + function getFunction(params, headers) { + return _.bind(elemFunction, this)('get', undefined, params, undefined, headers); + } + + function deleteFunction(params, headers) { + return _.bind(elemFunction, this)('remove', undefined, params, undefined, headers); + } + + function putFunction(params, headers) { + return _.bind(elemFunction, this)('put', undefined, params, undefined, headers); + } + + function postFunction(what, elem, params, headers) { + return _.bind(elemFunction, this)('post', what, params, elem, headers); + } + + function headFunction(params, headers) { + return _.bind(elemFunction, this)('head', undefined, params, undefined, headers); + } + + function traceFunction(params, headers) { + return _.bind(elemFunction, this)('trace', undefined, params, undefined, headers); + } + + function optionsFunction(params, headers) { + return _.bind(elemFunction, this)('options', undefined, params, undefined, headers); + } + + function patchFunction(elem, params, headers) { + return _.bind(elemFunction, this)('patch', undefined, params, elem, headers); + } + + function customFunction(operation, path, params, headers, elem) { + return _.bind(elemFunction, this)(operation, path, params, elem, headers); + } + + function addRestangularMethodFunction(name, operation, path, defaultParams, defaultHeaders, defaultElem) { + var bindedFunction; + if (operation === 'getList') { + bindedFunction = _.bind(fetchFunction, this, path); + } else { + bindedFunction = _.bind(customFunction, this, operation, path); + } + + var createdFunction = function(params, headers, elem) { + var callParams = _.defaults({ + params: params, + headers: headers, + elem: elem + }, { + params: defaultParams, + headers: defaultHeaders, + elem: defaultElem + }); + return bindedFunction(callParams.params, callParams.headers, callParams.elem); + }; + + if (config.isSafe(operation)) { + this[name] = createdFunction; + } else { + this[name] = function(elem, params, headers) { + return createdFunction(params, headers, elem); + }; + } + } + + function withConfigurationFunction(configurer) { + var newConfig = angular.copy(_.omit(config, 'configuration')); + Configurer.init(newConfig, newConfig); + configurer(newConfig); + return createServiceForConfiguration(newConfig); + } + + function toService(route, parent) { + var knownCollectionMethods = _.values(config.restangularFields); + var serv = {}; + var collection = (parent || service).all(route); + serv.one = _.bind(one, (parent || service), parent, route); + serv.post = _.bind(collection.post, collection); + serv.getList = _.bind(collection.getList, collection); + + for (var prop in collection) { + if (collection.hasOwnProperty(prop) && _.isFunction(collection[prop]) && !_.contains(knownCollectionMethods, prop)) { + serv[prop] = _.bind(collection[prop], collection); + } + } + + return serv; + } + + + Configurer.init(service, config); + + service.copy = _.bind(copyRestangularizedElement, service); + + service.service = _.bind(toService, service); + + service.withConfig = _.bind(withConfigurationFunction, service); + + service.one = _.bind(one, service, null); + + service.all = _.bind(all, service, null); + + service.several = _.bind(several, service, null); + + service.oneUrl = _.bind(oneUrl, service, null); + + service.allUrl = _.bind(allUrl, service, null); + + service.stripRestangular = _.bind(stripRestangular, service); + + service.restangularizeElement = _.bind(restangularizeElem, service); + + service.restangularizeCollection = _.bind(restangularizeCollectionAndElements, service); + + return service; + } + + return createServiceForConfiguration(globalConfiguration); + }]; +}); + +})(); diff --git a/djello/vendor/assets/javascripts/underscore.js b/djello/vendor/assets/javascripts/underscore.js new file mode 100644 index 00000000..b29332f9 --- /dev/null +++ b/djello/vendor/assets/javascripts/underscore.js @@ -0,0 +1,1548 @@ +// Underscore.js 1.8.3 +// http://underscorejs.org +// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Underscore may be freely distributed under the MIT license. + +(function() { + + // Baseline setup + // -------------- + + // Establish the root object, `window` in the browser, or `exports` on the server. + var root = this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; + + // Create quick reference variables for speed access to core prototypes. + var + push = ArrayProto.push, + slice = ArrayProto.slice, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var + nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeBind = FuncProto.bind, + nativeCreate = Object.create; + + // Naked function reference for surrogate-prototype-swapping. + var Ctor = function(){}; + + // Create a safe reference to the Underscore object for use below. + var _ = function(obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + }; + + // Export the Underscore object for **Node.js**, with + // backwards-compatibility for the old `require()` API. If we're in + // the browser, add `_` as a global object. + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = _; + } + exports._ = _; + } else { + root._ = _; + } + + // Current version. + _.VERSION = '1.8.3'; + + // Internal function that returns an efficient (for current engines) version + // of the passed-in callback, to be repeatedly applied in other Underscore + // functions. + var optimizeCb = function(func, context, argCount) { + if (context === void 0) return func; + switch (argCount == null ? 3 : argCount) { + case 1: return function(value) { + return func.call(context, value); + }; + case 2: return function(value, other) { + return func.call(context, value, other); + }; + case 3: return function(value, index, collection) { + return func.call(context, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(context, accumulator, value, index, collection); + }; + } + return function() { + return func.apply(context, arguments); + }; + }; + + // A mostly-internal function to generate callbacks that can be applied + // to each element in a collection, returning the desired result — either + // identity, an arbitrary callback, a property matcher, or a property accessor. + var cb = function(value, context, argCount) { + if (value == null) return _.identity; + if (_.isFunction(value)) return optimizeCb(value, context, argCount); + if (_.isObject(value)) return _.matcher(value); + return _.property(value); + }; + _.iteratee = function(value, context) { + return cb(value, context, Infinity); + }; + + // An internal function for creating assigner functions. + var createAssigner = function(keysFunc, undefinedOnly) { + return function(obj) { + var length = arguments.length; + if (length < 2 || obj == null) return obj; + for (var index = 1; index < length; index++) { + var source = arguments[index], + keys = keysFunc(source), + l = keys.length; + for (var i = 0; i < l; i++) { + var key = keys[i]; + if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key]; + } + } + return obj; + }; + }; + + // An internal function for creating a new object that inherits from another. + var baseCreate = function(prototype) { + if (!_.isObject(prototype)) return {}; + if (nativeCreate) return nativeCreate(prototype); + Ctor.prototype = prototype; + var result = new Ctor; + Ctor.prototype = null; + return result; + }; + + var property = function(key) { + return function(obj) { + return obj == null ? void 0 : obj[key]; + }; + }; + + // Helper for collection methods to determine whether a collection + // should be iterated as an array or as an object + // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength + // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 + var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; + var getLength = property('length'); + var isArrayLike = function(collection) { + var length = getLength(collection); + return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; + }; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles raw objects in addition to array-likes. Treats all + // sparse array-likes as if they were dense. + _.each = _.forEach = function(obj, iteratee, context) { + iteratee = optimizeCb(iteratee, context); + var i, length; + if (isArrayLike(obj)) { + for (i = 0, length = obj.length; i < length; i++) { + iteratee(obj[i], i, obj); + } + } else { + var keys = _.keys(obj); + for (i = 0, length = keys.length; i < length; i++) { + iteratee(obj[keys[i]], keys[i], obj); + } + } + return obj; + }; + + // Return the results of applying the iteratee to each element. + _.map = _.collect = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length, + results = Array(length); + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + results[index] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + }; + + // Create a reducing function iterating left or right. + function createReduce(dir) { + // Optimized iterator function as using arguments.length + // in the main function will deoptimize the, see #1991. + function iterator(obj, iteratee, memo, keys, index, length) { + for (; index >= 0 && index < length; index += dir) { + var currentKey = keys ? keys[index] : index; + memo = iteratee(memo, obj[currentKey], currentKey, obj); + } + return memo; + } + + return function(obj, iteratee, memo, context) { + iteratee = optimizeCb(iteratee, context, 4); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length, + index = dir > 0 ? 0 : length - 1; + // Determine the initial value if none is provided. + if (arguments.length < 3) { + memo = obj[keys ? keys[index] : index]; + index += dir; + } + return iterator(obj, iteratee, memo, keys, index, length); + }; + } + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. + _.reduce = _.foldl = _.inject = createReduce(1); + + // The right-associative version of reduce, also known as `foldr`. + _.reduceRight = _.foldr = createReduce(-1); + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function(obj, predicate, context) { + var key; + if (isArrayLike(obj)) { + key = _.findIndex(obj, predicate, context); + } else { + key = _.findKey(obj, predicate, context); + } + if (key !== void 0 && key !== -1) return obj[key]; + }; + + // Return all the elements that pass a truth test. + // Aliased as `select`. + _.filter = _.select = function(obj, predicate, context) { + var results = []; + predicate = cb(predicate, context); + _.each(obj, function(value, index, list) { + if (predicate(value, index, list)) results.push(value); + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function(obj, predicate, context) { + return _.filter(obj, _.negate(cb(predicate)), context); + }; + + // Determine whether all of the elements match a truth test. + // Aliased as `all`. + _.every = _.all = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + if (!predicate(obj[currentKey], currentKey, obj)) return false; + } + return true; + }; + + // Determine if at least one element in the object matches a truth test. + // Aliased as `any`. + _.some = _.any = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + if (predicate(obj[currentKey], currentKey, obj)) return true; + } + return false; + }; + + // Determine if the array or object contains a given item (using `===`). + // Aliased as `includes` and `include`. + _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { + if (!isArrayLike(obj)) obj = _.values(obj); + if (typeof fromIndex != 'number' || guard) fromIndex = 0; + return _.indexOf(obj, item, fromIndex) >= 0; + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = function(obj, method) { + var args = slice.call(arguments, 2); + var isFunc = _.isFunction(method); + return _.map(obj, function(value) { + var func = isFunc ? method : value[method]; + return func == null ? func : func.apply(value, args); + }); + }; + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function(obj, key) { + return _.map(obj, _.property(key)); + }; + + // Convenience version of a common use case of `filter`: selecting only objects + // containing specific `key:value` pairs. + _.where = function(obj, attrs) { + return _.filter(obj, _.matcher(attrs)); + }; + + // Convenience version of a common use case of `find`: getting the first object + // containing specific `key:value` pairs. + _.findWhere = function(obj, attrs) { + return _.find(obj, _.matcher(attrs)); + }; + + // Return the maximum element (or element-based computation). + _.max = function(obj, iteratee, context) { + var result = -Infinity, lastComputed = -Infinity, + value, computed; + if (iteratee == null && obj != null) { + obj = isArrayLike(obj) ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value > result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(value, index, list) { + computed = iteratee(value, index, list); + if (computed > lastComputed || computed === -Infinity && result === -Infinity) { + result = value; + lastComputed = computed; + } + }); + } + return result; + }; + + // Return the minimum element (or element-based computation). + _.min = function(obj, iteratee, context) { + var result = Infinity, lastComputed = Infinity, + value, computed; + if (iteratee == null && obj != null) { + obj = isArrayLike(obj) ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value < result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(value, index, list) { + computed = iteratee(value, index, list); + if (computed < lastComputed || computed === Infinity && result === Infinity) { + result = value; + lastComputed = computed; + } + }); + } + return result; + }; + + // Shuffle a collection, using the modern version of the + // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). + _.shuffle = function(obj) { + var set = isArrayLike(obj) ? obj : _.values(obj); + var length = set.length; + var shuffled = Array(length); + for (var index = 0, rand; index < length; index++) { + rand = _.random(0, index); + if (rand !== index) shuffled[index] = shuffled[rand]; + shuffled[rand] = set[index]; + } + return shuffled; + }; + + // Sample **n** random values from a collection. + // If **n** is not specified, returns a single random element. + // The internal `guard` argument allows it to work with `map`. + _.sample = function(obj, n, guard) { + if (n == null || guard) { + if (!isArrayLike(obj)) obj = _.values(obj); + return obj[_.random(obj.length - 1)]; + } + return _.shuffle(obj).slice(0, Math.max(0, n)); + }; + + // Sort the object's values by a criterion produced by an iteratee. + _.sortBy = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + return _.pluck(_.map(obj, function(value, index, list) { + return { + value: value, + index: index, + criteria: iteratee(value, index, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index - right.index; + }), 'value'); + }; + + // An internal function used for aggregate "group by" operations. + var group = function(behavior) { + return function(obj, iteratee, context) { + var result = {}; + iteratee = cb(iteratee, context); + _.each(obj, function(value, index) { + var key = iteratee(value, index, obj); + behavior(result, value, key); + }); + return result; + }; + }; + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = group(function(result, value, key) { + if (_.has(result, key)) result[key].push(value); else result[key] = [value]; + }); + + // Indexes the object's values by a criterion, similar to `groupBy`, but for + // when you know that your index values will be unique. + _.indexBy = group(function(result, value, key) { + result[key] = value; + }); + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = group(function(result, value, key) { + if (_.has(result, key)) result[key]++; else result[key] = 1; + }); + + // Safely create a real, live array from anything iterable. + _.toArray = function(obj) { + if (!obj) return []; + if (_.isArray(obj)) return slice.call(obj); + if (isArrayLike(obj)) return _.map(obj, _.identity); + return _.values(obj); + }; + + // Return the number of elements in an object. + _.size = function(obj) { + if (obj == null) return 0; + return isArrayLike(obj) ? obj.length : _.keys(obj).length; + }; + + // Split a collection into two arrays: one whose elements all satisfy the given + // predicate, and one whose elements all do not satisfy the predicate. + _.partition = function(obj, predicate, context) { + predicate = cb(predicate, context); + var pass = [], fail = []; + _.each(obj, function(value, key, obj) { + (predicate(value, key, obj) ? pass : fail).push(value); + }); + return [pass, fail]; + }; + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head` and `take`. The **guard** check + // allows it to work with `_.map`. + _.first = _.head = _.take = function(array, n, guard) { + if (array == null) return void 0; + if (n == null || guard) return array[0]; + return _.initial(array, array.length - n); + }; + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. + _.initial = function(array, n, guard) { + return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. + _.last = function(array, n, guard) { + if (array == null) return void 0; + if (n == null || guard) return array[array.length - 1]; + return _.rest(array, Math.max(0, array.length - n)); + }; + + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. + _.rest = _.tail = _.drop = function(array, n, guard) { + return slice.call(array, n == null || guard ? 1 : n); + }; + + // Trim out all falsy values from an array. + _.compact = function(array) { + return _.filter(array, _.identity); + }; + + // Internal implementation of a recursive `flatten` function. + var flatten = function(input, shallow, strict, startIndex) { + var output = [], idx = 0; + for (var i = startIndex || 0, length = getLength(input); i < length; i++) { + var value = input[i]; + if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { + //flatten current level of array or arguments object + if (!shallow) value = flatten(value, shallow, strict); + var j = 0, len = value.length; + output.length += len; + while (j < len) { + output[idx++] = value[j++]; + } + } else if (!strict) { + output[idx++] = value; + } + } + return output; + }; + + // Flatten out an array, either recursively (by default), or just one level. + _.flatten = function(array, shallow) { + return flatten(array, shallow, false); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function(array) { + return _.difference(array, slice.call(arguments, 1)); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function(array, isSorted, iteratee, context) { + if (!_.isBoolean(isSorted)) { + context = iteratee; + iteratee = isSorted; + isSorted = false; + } + if (iteratee != null) iteratee = cb(iteratee, context); + var result = []; + var seen = []; + for (var i = 0, length = getLength(array); i < length; i++) { + var value = array[i], + computed = iteratee ? iteratee(value, i, array) : value; + if (isSorted) { + if (!i || seen !== computed) result.push(value); + seen = computed; + } else if (iteratee) { + if (!_.contains(seen, computed)) { + seen.push(computed); + result.push(value); + } + } else if (!_.contains(result, value)) { + result.push(value); + } + } + return result; + }; + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + _.union = function() { + return _.uniq(flatten(arguments, true, true)); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersection = function(array) { + var result = []; + var argsLength = arguments.length; + for (var i = 0, length = getLength(array); i < length; i++) { + var item = array[i]; + if (_.contains(result, item)) continue; + for (var j = 1; j < argsLength; j++) { + if (!_.contains(arguments[j], item)) break; + } + if (j === argsLength) result.push(item); + } + return result; + }; + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + _.difference = function(array) { + var rest = flatten(arguments, true, true, 1); + return _.filter(array, function(value){ + return !_.contains(rest, value); + }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function() { + return _.unzip(arguments); + }; + + // Complement of _.zip. Unzip accepts an array of arrays and groups + // each array's elements on shared indices + _.unzip = function(array) { + var length = array && _.max(array, getLength).length || 0; + var result = Array(length); + + for (var index = 0; index < length; index++) { + result[index] = _.pluck(array, index); + } + return result; + }; + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function(list, values) { + var result = {}; + for (var i = 0, length = getLength(list); i < length; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + }; + + // Generator function to create the findIndex and findLastIndex functions + function createPredicateIndexFinder(dir) { + return function(array, predicate, context) { + predicate = cb(predicate, context); + var length = getLength(array); + var index = dir > 0 ? 0 : length - 1; + for (; index >= 0 && index < length; index += dir) { + if (predicate(array[index], index, array)) return index; + } + return -1; + }; + } + + // Returns the first index on an array-like that passes a predicate test + _.findIndex = createPredicateIndexFinder(1); + _.findLastIndex = createPredicateIndexFinder(-1); + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iteratee, context) { + iteratee = cb(iteratee, context, 1); + var value = iteratee(obj); + var low = 0, high = getLength(array); + while (low < high) { + var mid = Math.floor((low + high) / 2); + if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; + } + return low; + }; + + // Generator function to create the indexOf and lastIndexOf functions + function createIndexFinder(dir, predicateFind, sortedIndex) { + return function(array, item, idx) { + var i = 0, length = getLength(array); + if (typeof idx == 'number') { + if (dir > 0) { + i = idx >= 0 ? idx : Math.max(idx + length, i); + } else { + length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; + } + } else if (sortedIndex && idx && length) { + idx = sortedIndex(array, item); + return array[idx] === item ? idx : -1; + } + if (item !== item) { + idx = predicateFind(slice.call(array, i, length), _.isNaN); + return idx >= 0 ? idx + i : -1; + } + for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { + if (array[idx] === item) return idx; + } + return -1; + }; + } + + // Return the position of the first occurrence of an item in an array, + // or -1 if the item is not included in the array. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex); + _.lastIndexOf = createIndexFinder(-1, _.findLastIndex); + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function(start, stop, step) { + if (stop == null) { + stop = start || 0; + start = 0; + } + step = step || 1; + + var length = Math.max(Math.ceil((stop - start) / step), 0); + var range = Array(length); + + for (var idx = 0; idx < length; idx++, start += step) { + range[idx] = start; + } + + return range; + }; + + // Function (ahem) Functions + // ------------------ + + // Determines whether to execute a function as a constructor + // or a normal function with the provided arguments + var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { + if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); + var self = baseCreate(sourceFunc.prototype); + var result = sourceFunc.apply(self, args); + if (_.isObject(result)) return result; + return self; + }; + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if + // available. + _.bind = function(func, context) { + if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); + var args = slice.call(arguments, 2); + var bound = function() { + return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); + }; + return bound; + }; + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. _ acts + // as a placeholder, allowing any combination of arguments to be pre-filled. + _.partial = function(func) { + var boundArgs = slice.call(arguments, 1); + var bound = function() { + var position = 0, length = boundArgs.length; + var args = Array(length); + for (var i = 0; i < length; i++) { + args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; + } + while (position < arguments.length) args.push(arguments[position++]); + return executeBound(func, bound, this, this, args); + }; + return bound; + }; + + // Bind a number of an object's methods to that object. Remaining arguments + // are the method names to be bound. Useful for ensuring that all callbacks + // defined on an object belong to it. + _.bindAll = function(obj) { + var i, length = arguments.length, key; + if (length <= 1) throw new Error('bindAll must be passed function names'); + for (i = 1; i < length; i++) { + key = arguments[i]; + obj[key] = _.bind(obj[key], obj); + } + return obj; + }; + + // Memoize an expensive function by storing its results. + _.memoize = function(func, hasher) { + var memoize = function(key) { + var cache = memoize.cache; + var address = '' + (hasher ? hasher.apply(this, arguments) : key); + if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); + return cache[address]; + }; + memoize.cache = {}; + return memoize; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function(){ + return func.apply(null, args); + }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = _.partial(_.delay, _, 1); + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. Normally, the throttled function will run + // as much as it can, without ever going more than once per `wait` duration; + // but if you'd like to disable the execution on the leading edge, pass + // `{leading: false}`. To disable execution on the trailing edge, ditto. + _.throttle = function(func, wait, options) { + var context, args, result; + var timeout = null; + var previous = 0; + if (!options) options = {}; + var later = function() { + previous = options.leading === false ? 0 : _.now(); + timeout = null; + result = func.apply(context, args); + if (!timeout) context = args = null; + }; + return function() { + var now = _.now(); + if (!previous && options.leading === false) previous = now; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = now; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If `immediate` is passed, trigger the function on the + // leading edge, instead of the trailing. + _.debounce = function(func, wait, immediate) { + var timeout, args, context, timestamp, result; + + var later = function() { + var last = _.now() - timestamp; + + if (last < wait && last >= 0) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + if (!timeout) context = args = null; + } + } + }; + + return function() { + context = this; + args = arguments; + timestamp = _.now(); + var callNow = immediate && !timeout; + if (!timeout) timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + context = args = null; + } + + return result; + }; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function(func, wrapper) { + return _.partial(wrapper, func); + }; + + // Returns a negated version of the passed-in predicate. + _.negate = function(predicate) { + return function() { + return !predicate.apply(this, arguments); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var args = arguments; + var start = args.length - 1; + return function() { + var i = start; + var result = args[start].apply(this, arguments); + while (i--) result = args[i].call(this, result); + return result; + }; + }; + + // Returns a function that will only be executed on and after the Nth call. + _.after = function(times, func) { + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + }; + + // Returns a function that will only be executed up to (but not including) the Nth call. + _.before = function(times, func) { + var memo; + return function() { + if (--times > 0) { + memo = func.apply(this, arguments); + } + if (times <= 1) func = null; + return memo; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = _.partial(_.before, 2); + + // Object Functions + // ---------------- + + // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. + var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); + var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', + 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; + + function collectNonEnumProps(obj, keys) { + var nonEnumIdx = nonEnumerableProps.length; + var constructor = obj.constructor; + var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; + + // Constructor is a special case. + var prop = 'constructor'; + if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); + + while (nonEnumIdx--) { + prop = nonEnumerableProps[nonEnumIdx]; + if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { + keys.push(prop); + } + } + } + + // Retrieve the names of an object's own properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = function(obj) { + if (!_.isObject(obj)) return []; + if (nativeKeys) return nativeKeys(obj); + var keys = []; + for (var key in obj) if (_.has(obj, key)) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + }; + + // Retrieve all the property names of an object. + _.allKeys = function(obj) { + if (!_.isObject(obj)) return []; + var keys = []; + for (var key in obj) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function(obj) { + var keys = _.keys(obj); + var length = keys.length; + var values = Array(length); + for (var i = 0; i < length; i++) { + values[i] = obj[keys[i]]; + } + return values; + }; + + // Returns the results of applying the iteratee to each element of the object + // In contrast to _.map it returns an object + _.mapObject = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var keys = _.keys(obj), + length = keys.length, + results = {}, + currentKey; + for (var index = 0; index < length; index++) { + currentKey = keys[index]; + results[currentKey] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + }; + + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function(obj) { + var keys = _.keys(obj); + var length = keys.length; + var pairs = Array(length); + for (var i = 0; i < length; i++) { + pairs[i] = [keys[i], obj[keys[i]]]; + } + return pairs; + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function(obj) { + var result = {}; + var keys = _.keys(obj); + for (var i = 0, length = keys.length; i < length; i++) { + result[obj[keys[i]]] = keys[i]; + } + return result; + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods` + _.functions = _.methods = function(obj) { + var names = []; + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } + return names.sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = createAssigner(_.allKeys); + + // Assigns a given object with all the own properties in the passed-in object(s) + // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) + _.extendOwn = _.assign = createAssigner(_.keys); + + // Returns the first key on an object that passes a predicate test + _.findKey = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = _.keys(obj), key; + for (var i = 0, length = keys.length; i < length; i++) { + key = keys[i]; + if (predicate(obj[key], key, obj)) return key; + } + }; + + // Return a copy of the object only containing the whitelisted properties. + _.pick = function(object, oiteratee, context) { + var result = {}, obj = object, iteratee, keys; + if (obj == null) return result; + if (_.isFunction(oiteratee)) { + keys = _.allKeys(obj); + iteratee = optimizeCb(oiteratee, context); + } else { + keys = flatten(arguments, false, false, 1); + iteratee = function(value, key, obj) { return key in obj; }; + obj = Object(obj); + } + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i]; + var value = obj[key]; + if (iteratee(value, key, obj)) result[key] = value; + } + return result; + }; + + // Return a copy of the object without the blacklisted properties. + _.omit = function(obj, iteratee, context) { + if (_.isFunction(iteratee)) { + iteratee = _.negate(iteratee); + } else { + var keys = _.map(flatten(arguments, false, false, 1), String); + iteratee = function(value, key) { + return !_.contains(keys, key); + }; + } + return _.pick(obj, iteratee, context); + }; + + // Fill in a given object with default properties. + _.defaults = createAssigner(_.allKeys, true); + + // Creates an object that inherits from the given prototype object. + // If additional properties are provided then they will be added to the + // created object. + _.create = function(prototype, props) { + var result = baseCreate(prototype); + if (props) _.extendOwn(result, props); + return result; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function(obj) { + if (!_.isObject(obj)) return obj; + return _.isArray(obj) ? obj.slice() : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function(obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Returns whether an object has a given set of `key:value` pairs. + _.isMatch = function(object, attrs) { + var keys = _.keys(attrs), length = keys.length; + if (object == null) return !length; + var obj = Object(object); + for (var i = 0; i < length; i++) { + var key = keys[i]; + if (attrs[key] !== obj[key] || !(key in obj)) return false; + } + return true; + }; + + + // Internal recursive comparison function for `isEqual`. + var eq = function(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) return a !== 0 || 1 / a === 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; + // Unwrap any wrapped objects. + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className !== toString.call(b)) return false; + switch (className) { + // Strings, numbers, regular expressions, dates, and booleans are compared by value. + case '[object RegExp]': + // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return '' + a === '' + b; + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. + // Object(NaN) is equivalent to NaN + if (+a !== +a) return +b !== +b; + // An `egal` comparison is performed for other numeric values. + return +a === 0 ? 1 / +a === 1 / b : +a === +b; + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a === +b; + } + + var areArrays = className === '[object Array]'; + if (!areArrays) { + if (typeof a != 'object' || typeof b != 'object') return false; + + // Objects with different constructors are not equivalent, but `Object`s or `Array`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && + _.isFunction(bCtor) && bCtor instanceof bCtor) + && ('constructor' in a && 'constructor' in b)) { + return false; + } + } + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + + // Initializing stack of traversed objects. + // It's done here since we only need them for objects and arrays comparison. + aStack = aStack || []; + bStack = bStack || []; + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] === a) return bStack[length] === b; + } + + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + + // Recursively compare objects and arrays. + if (areArrays) { + // Compare array lengths to determine if a deep comparison is necessary. + length = a.length; + if (length !== b.length) return false; + // Deep compare the contents, ignoring non-numeric properties. + while (length--) { + if (!eq(a[length], b[length], aStack, bStack)) return false; + } + } else { + // Deep compare objects. + var keys = _.keys(a), key; + length = keys.length; + // Ensure that both objects contain the same number of properties before comparing deep equality. + if (_.keys(b).length !== length) return false; + while (length--) { + // Deep compare each member + key = keys[length]; + if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return true; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function(a, b) { + return eq(a, b); + }; + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + _.isEmpty = function(obj) { + if (obj == null) return true; + if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; + return _.keys(obj).length === 0; + }; + + // Is a given value a DOM element? + _.isElement = function(obj) { + return !!(obj && obj.nodeType === 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function(obj) { + return toString.call(obj) === '[object Array]'; + }; + + // Is a given variable an object? + _.isObject = function(obj) { + var type = typeof obj; + return type === 'function' || type === 'object' && !!obj; + }; + + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. + _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { + _['is' + name] = function(obj) { + return toString.call(obj) === '[object ' + name + ']'; + }; + }); + + // Define a fallback version of the method in browsers (ahem, IE < 9), where + // there isn't any inspectable "Arguments" type. + if (!_.isArguments(arguments)) { + _.isArguments = function(obj) { + return _.has(obj, 'callee'); + }; + } + + // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, + // IE 11 (#1621), and in Safari 8 (#1929). + if (typeof /./ != 'function' && typeof Int8Array != 'object') { + _.isFunction = function(obj) { + return typeof obj == 'function' || false; + }; + } + + // Is a given object a finite number? + _.isFinite = function(obj) { + return isFinite(obj) && !isNaN(parseFloat(obj)); + }; + + // Is the given value `NaN`? (NaN is the only number which does not equal itself). + _.isNaN = function(obj) { + return _.isNumber(obj) && obj !== +obj; + }; + + // Is a given value a boolean? + _.isBoolean = function(obj) { + return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; + }; + + // Is a given value equal to null? + _.isNull = function(obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function(obj) { + return obj === void 0; + }; + + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). + _.has = function(obj, key) { + return obj != null && hasOwnProperty.call(obj, key); + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function() { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iteratees. + _.identity = function(value) { + return value; + }; + + // Predicate-generating functions. Often useful outside of Underscore. + _.constant = function(value) { + return function() { + return value; + }; + }; + + _.noop = function(){}; + + _.property = property; + + // Generates a function for a given object that returns a given property. + _.propertyOf = function(obj) { + return obj == null ? function(){} : function(key) { + return obj[key]; + }; + }; + + // Returns a predicate for checking whether an object has a given set of + // `key:value` pairs. + _.matcher = _.matches = function(attrs) { + attrs = _.extendOwn({}, attrs); + return function(obj) { + return _.isMatch(obj, attrs); + }; + }; + + // Run a function **n** times. + _.times = function(n, iteratee, context) { + var accum = Array(Math.max(0, n)); + iteratee = optimizeCb(iteratee, context, 1); + for (var i = 0; i < n; i++) accum[i] = iteratee(i); + return accum; + }; + + // Return a random integer between min and max (inclusive). + _.random = function(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + }; + + // A (possibly faster) way to get the current timestamp as an integer. + _.now = Date.now || function() { + return new Date().getTime(); + }; + + // List of HTML entities for escaping. + var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`' + }; + var unescapeMap = _.invert(escapeMap); + + // Functions for escaping and unescaping strings to/from HTML interpolation. + var createEscaper = function(map) { + var escaper = function(match) { + return map[match]; + }; + // Regexes for identifying a key that needs to be escaped + var source = '(?:' + _.keys(map).join('|') + ')'; + var testRegexp = RegExp(source); + var replaceRegexp = RegExp(source, 'g'); + return function(string) { + string = string == null ? '' : '' + string; + return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; + }; + }; + _.escape = createEscaper(escapeMap); + _.unescape = createEscaper(unescapeMap); + + // If the value of the named `property` is a function then invoke it with the + // `object` as context; otherwise, return it. + _.result = function(object, property, fallback) { + var value = object == null ? void 0 : object[property]; + if (value === void 0) { + value = fallback; + } + return _.isFunction(value) ? value.call(object) : value; + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escaper = /\\|'|\r|\n|\u2028|\u2029/g; + + var escapeChar = function(match) { + return '\\' + escapes[match]; + }; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + // NB: `oldSettings` only exists for backwards compatibility. + _.template = function(text, settings, oldSettings) { + if (!settings && oldSettings) settings = oldSettings; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset).replace(escaper, escapeChar); + index = offset + match.length; + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } else if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } else if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + + // Adobe VMs need the match returned to produce the correct offest. + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + 'return __p;\n'; + + try { + var render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled source as a convenience for precompilation. + var argument = settings.variable || 'obj'; + template.source = 'function(' + argument + '){\n' + source + '}'; + + return template; + }; + + // Add a "chain" function. Start chaining a wrapped Underscore object. + _.chain = function(obj) { + var instance = _(obj); + instance._chain = true; + return instance; + }; + + // OOP + // --------------- + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + + // Helper function to continue chaining intermediate results. + var result = function(instance, obj) { + return instance._chain ? _(obj).chain() : obj; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + _.each(_.functions(obj), function(name) { + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result(this, func.apply(_, args)); + }; + }); + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + var obj = this._wrapped; + method.apply(obj, arguments); + if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; + return result(this, obj); + }; + }); + + // Add all accessor Array functions to the wrapper. + _.each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + return result(this, method.apply(this._wrapped, arguments)); + }; + }); + + // Extracts the result from a wrapped and chained object. + _.prototype.value = function() { + return this._wrapped; + }; + + // Provide unwrapping proxy for some methods used in engine operations + // such as arithmetic and JSON stringification. + _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; + + _.prototype.toString = function() { + return '' + this._wrapped; + }; + + // AMD registration happens at the end for compatibility with AMD loaders + // that may not enforce next-turn semantics on modules. Even though general + // practice for AMD registration is to be anonymous, underscore registers + // as a named module because, like jQuery, it is a base library that is + // popular enough to be bundled in a third party lib, but not be part of + // an AMD load request. Those cases could generate an error when an + // anonymous define() is called outside of a loader request. + if (typeof define === 'function' && define.amd) { + define('underscore', [], function() { + return _; + }); + } +}.call(this)); diff --git a/djello/vendor/assets/stylesheets/.keep b/djello/vendor/assets/stylesheets/.keep new file mode 100644 index 00000000..e69de29b