From 614d80e38bc50d4ab1448f9bdaec6f346665408d Mon Sep 17 00:00:00 2001 From: anubhav8421 Date: Wed, 7 Aug 2019 18:25:18 +0530 Subject: [PATCH 01/35] #8 - Add configuration for sentry --- Gemfile | 1 + Gemfile.lock | 7 ++++++- app/controllers/application_controller.rb | 7 +++++++ config/initializers/sentry.rb | 4 ++++ 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 config/initializers/sentry.rb diff --git a/Gemfile b/Gemfile index 36491c7..dab0632 100644 --- a/Gemfile +++ b/Gemfile @@ -27,6 +27,7 @@ gem 'uglifier', '>= 1.0.3' gem 'byebug', '~>11.0.1' gem 'puma', '~> 4.0.1' gem 'twitter', '~> 6.2.0' +gem 'sentry-raven', '~> 2.11' #gem 'jquery-rails' gem "devise", "~> 4.6.2" gem "omniauth", "~>1.9.0" diff --git a/Gemfile.lock b/Gemfile.lock index 27fde3d..90ee532 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -88,13 +88,15 @@ GEM erubi (1.8.0) erubis (2.7.0) execjs (2.7.0) + faraday (0.15.4) + multipart-post (>= 1.2, < 3) ffi (1.11.1) formtastic (3.1.5) actionpack (>= 3.2.13) formtastic_i18n (0.6.0) globalid (0.4.2) activesupport (>= 4.2.0) - haml (5.1.1) + haml (5.1.2) temple (>= 0.8.0) tilt haml-rails (2.0.1) @@ -244,6 +246,8 @@ GEM sprockets (> 3.0) sprockets-rails tilt + sentry-raven (2.11.0) + faraday (>= 0.7.6, < 1.0) sexp_processor (4.12.1) simple_oauth (0.3.1) sprockets (3.7.2) @@ -310,6 +314,7 @@ DEPENDENCIES pygmentize (~> 0.0.3) rails (= 5.2.3) redcarpet (~> 1.17.2) + sentry-raven (~> 2.11) twitter (~> 6.2.0) twitter-bootstrap-rails (~> 4.0.0) uglifier (>= 1.0.3) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 45eef3a..79ecd4b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,11 @@ class ApplicationController < ActionController::Base protect_from_forgery + before_action :set_raven_context + + private + def set_raven_context + Raven.user_context(id: session[:current_user_id]) + Raven.extra_context(params: params.to_unsafe_h, url: request.url) + end end diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb new file mode 100644 index 0000000..b504df8 --- /dev/null +++ b/config/initializers/sentry.rb @@ -0,0 +1,4 @@ +Raven.configure do |config| + config.dsn = ENV['SENTRY_DSN'] + config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s) +end From cdd980b3eaf6a5b2927a4f4391cd3549f201be32 Mon Sep 17 00:00:00 2001 From: anubhav8421 Date: Thu, 8 Aug 2019 12:50:11 +0530 Subject: [PATCH 02/35] Add validation to check the presence of description when creating or updating the letter --- app/controllers/home_controller.rb | 3 ++- app/controllers/letters_controller.rb | 4 ++-- app/models/letter.rb | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index b1752ed..4e759ba 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -2,7 +2,8 @@ class HomeController < ApplicationController def index @letter = Letter.new - @letters = Letter.order('(SELECT COUNT(*) FROM likes WHERE (letters.id=likes.letter_id)) DESC').includes([:likes, :user]).page(params[:page]).per(15) + @letters = Letter.order('(SELECT COUNT(*) FROM likes WHERE (letters.id=likes.letter_id)) DESC') + .includes([:likes, :user]).page(params[:page]).per(15) @recent_letters = Letter.limit(15).order('created_at DESC').includes(:user) end end \ No newline at end of file diff --git a/app/controllers/letters_controller.rb b/app/controllers/letters_controller.rb index 3fbe106..3601697 100644 --- a/app/controllers/letters_controller.rb +++ b/app/controllers/letters_controller.rb @@ -9,7 +9,7 @@ def create flash[:notice] = t('flash.saved_letter_successfully') redirect_to letter_path(@letter) else - flash[:error] = t('flash.error_saving_letter') + flash[:error] = 'Letter description cannot be empty. Try Again!' redirect_to root_path end end @@ -29,7 +29,7 @@ def update if @letter.update_attributes(params[:letter]) flash[:notice] = t('flash.updated_letter_successfully') else - flash[:error] = t('flash.error_updating_letter') + flash[:error] = 'Letter description cannot be empty. Try Again!' end redirect_to letter_path(@letter) end diff --git a/app/models/letter.rb b/app/models/letter.rb index 3dd9e4a..38079a0 100644 --- a/app/models/letter.rb +++ b/app/models/letter.rb @@ -2,6 +2,7 @@ class Letter < ActiveRecord::Base belongs_to :user has_many :likes + validates_presence_of :description def like_by?(user) likes.map(&:user_id).include?(user.id) From 3552b6e92f063a76afc20f9a42e4bfdab7eb4131 Mon Sep 17 00:00:00 2001 From: anubhav8421 Date: Wed, 7 Aug 2019 18:25:18 +0530 Subject: [PATCH 03/35] #8 - Add configuration for sentry --- Gemfile | 1 + Gemfile.lock | 7 ++++++- app/controllers/application_controller.rb | 7 +++++++ config/initializers/sentry.rb | 4 ++++ 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 config/initializers/sentry.rb diff --git a/Gemfile b/Gemfile index 36491c7..dab0632 100644 --- a/Gemfile +++ b/Gemfile @@ -27,6 +27,7 @@ gem 'uglifier', '>= 1.0.3' gem 'byebug', '~>11.0.1' gem 'puma', '~> 4.0.1' gem 'twitter', '~> 6.2.0' +gem 'sentry-raven', '~> 2.11' #gem 'jquery-rails' gem "devise", "~> 4.6.2" gem "omniauth", "~>1.9.0" diff --git a/Gemfile.lock b/Gemfile.lock index 27fde3d..90ee532 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -88,13 +88,15 @@ GEM erubi (1.8.0) erubis (2.7.0) execjs (2.7.0) + faraday (0.15.4) + multipart-post (>= 1.2, < 3) ffi (1.11.1) formtastic (3.1.5) actionpack (>= 3.2.13) formtastic_i18n (0.6.0) globalid (0.4.2) activesupport (>= 4.2.0) - haml (5.1.1) + haml (5.1.2) temple (>= 0.8.0) tilt haml-rails (2.0.1) @@ -244,6 +246,8 @@ GEM sprockets (> 3.0) sprockets-rails tilt + sentry-raven (2.11.0) + faraday (>= 0.7.6, < 1.0) sexp_processor (4.12.1) simple_oauth (0.3.1) sprockets (3.7.2) @@ -310,6 +314,7 @@ DEPENDENCIES pygmentize (~> 0.0.3) rails (= 5.2.3) redcarpet (~> 1.17.2) + sentry-raven (~> 2.11) twitter (~> 6.2.0) twitter-bootstrap-rails (~> 4.0.0) uglifier (>= 1.0.3) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 45eef3a..79ecd4b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,11 @@ class ApplicationController < ActionController::Base protect_from_forgery + before_action :set_raven_context + + private + def set_raven_context + Raven.user_context(id: session[:current_user_id]) + Raven.extra_context(params: params.to_unsafe_h, url: request.url) + end end diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb new file mode 100644 index 0000000..b504df8 --- /dev/null +++ b/config/initializers/sentry.rb @@ -0,0 +1,4 @@ +Raven.configure do |config| + config.dsn = ENV['SENTRY_DSN'] + config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s) +end From b0399d0cf91ca3df0b0aa0f092fe86e301cef02e Mon Sep 17 00:00:00 2001 From: anubhav8421 Date: Thu, 8 Aug 2019 12:50:11 +0530 Subject: [PATCH 04/35] Add validation to check the presence of description when creating or updating the letter Make letters editable and deletable --- app/controllers/home_controller.rb | 3 ++- app/controllers/letters_controller.rb | 14 +++++++++++--- app/models/letter.rb | 3 ++- app/views/letters/show.html.haml | 3 +++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index b1752ed..4e759ba 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -2,7 +2,8 @@ class HomeController < ApplicationController def index @letter = Letter.new - @letters = Letter.order('(SELECT COUNT(*) FROM likes WHERE (letters.id=likes.letter_id)) DESC').includes([:likes, :user]).page(params[:page]).per(15) + @letters = Letter.order('(SELECT COUNT(*) FROM likes WHERE (letters.id=likes.letter_id)) DESC') + .includes([:likes, :user]).page(params[:page]).per(15) @recent_letters = Letter.limit(15).order('created_at DESC').includes(:user) end end \ No newline at end of file diff --git a/app/controllers/letters_controller.rb b/app/controllers/letters_controller.rb index 3fbe106..6a8107f 100644 --- a/app/controllers/letters_controller.rb +++ b/app/controllers/letters_controller.rb @@ -9,7 +9,7 @@ def create flash[:notice] = t('flash.saved_letter_successfully') redirect_to letter_path(@letter) else - flash[:error] = t('flash.error_saving_letter') + flash[:error] = 'Letter description cannot be empty. Try Again!' redirect_to root_path end end @@ -26,10 +26,10 @@ def edit def update @letter = current_user.letters.find(params[:id]) - if @letter.update_attributes(params[:letter]) + if @letter.update_attributes(letter_params) flash[:notice] = t('flash.updated_letter_successfully') else - flash[:error] = t('flash.error_updating_letter') + flash[:error] = 'Letter description cannot be empty. Try Again!' end redirect_to letter_path(@letter) end @@ -43,6 +43,14 @@ def like end end + def destroy + letter = current_user.letters.find(params[:id]) + letter.destroy! + flash[:notice] = "Letter has been successfully deleted" + + redirect_to root_path + end + private def letter_params diff --git a/app/models/letter.rb b/app/models/letter.rb index 3dd9e4a..be04cf4 100644 --- a/app/models/letter.rb +++ b/app/models/letter.rb @@ -1,7 +1,8 @@ class Letter < ActiveRecord::Base belongs_to :user - has_many :likes + has_many :likes, dependent: :destroy + validates_presence_of :description def like_by?(user) likes.map(&:user_id).include?(user.id) diff --git a/app/views/letters/show.html.haml b/app/views/letters/show.html.haml index 4d032f7..549f5e9 100644 --- a/app/views/letters/show.html.haml +++ b/app/views/letters/show.html.haml @@ -26,4 +26,7 @@ - if user_signed_in? - if(current_user.id.to_i == @letter.user_id.to_i) = link_to "Edit", edit_letter_path(@letter) + = link_to "Delete", letter_path(@letter), + method: :delete, + data: {confirm: "Are you sure?"} %hr From bb4439d224a7e25316d173169cfd5c105a4b3577 Mon Sep 17 00:00:00 2001 From: anubhav8421 Date: Fri, 9 Aug 2019 12:26:34 +0530 Subject: [PATCH 05/35] Make Https compulsary in Production #10 --- config/environments/production.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index e88097a..7df79fa 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -47,7 +47,7 @@ # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - # config.force_ssl = true + config.force_ssl = true # Use the lowest log level to ensure availability of diagnostic information # when problems arise. From d15562a0b9532b87d0f1e1e2ca16da72a4c12bdc Mon Sep 17 00:00:00 2001 From: anubhav8421 Date: Fri, 9 Aug 2019 14:19:16 +0530 Subject: [PATCH 06/35] Add rake task to change URL scheme from http to https #14 --- lib/tasks/modify_user_data.rake | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/tasks/modify_user_data.rake b/lib/tasks/modify_user_data.rake index 0fb673e..5f7dfcf 100644 --- a/lib/tasks/modify_user_data.rake +++ b/lib/tasks/modify_user_data.rake @@ -15,4 +15,10 @@ namespace :modify_user_data do user.save! end end + + + desc "Update user profile image URI scheme from \'http\' to \'https\'" + task update_user_image_url_scheme_to_https: :environment do + User.find_each { |user| user.update! image: user.image.gsub('http://', 'https://') } + end end \ No newline at end of file From 4e08b4d160bb15a2ada73ebf104f98e3a2bb704b Mon Sep 17 00:00:00 2001 From: anubhav8421 Date: Fri, 9 Aug 2019 16:08:58 +0530 Subject: [PATCH 07/35] #15 update sentry config --- config/initializers/sentry.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb index b504df8..1ce0b3f 100644 --- a/config/initializers/sentry.rb +++ b/config/initializers/sentry.rb @@ -1,4 +1,5 @@ Raven.configure do |config| config.dsn = ENV['SENTRY_DSN'] config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s) + config.environments = %w(production) end From 7f03822d09323378e98edd29da861515e150158c Mon Sep 17 00:00:00 2001 From: anubhav8421 Date: Fri, 9 Aug 2019 17:19:01 +0530 Subject: [PATCH 08/35] Fix for broken images on production #12 --- app/views/layouts/application.html.haml | 12 ++++++------ {app/assets/images => public}/forkme_right_red.png | Bin {app/assets/images => public}/my_love_ruby.png | Bin 3 files changed, 6 insertions(+), 6 deletions(-) rename {app/assets/images => public}/forkme_right_red.png (100%) rename {app/assets/images => public}/my_love_ruby.png (100%) diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index dc56005..13c5e62 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -23,11 +23,11 @@ } = stylesheet_link_tag "application", :media => "all" / Le fav and touch icons - %link{:href => "images/favicon.ico", :rel => "shortcut icon"}/ - %link{:href => "images/apple-touch-icon.png", :rel => "apple-touch-icon"}/ - %link{:href => "images/apple-touch-icon-72x72.png", :rel => "apple-touch-icon", :sizes => "72x72"}/ - %link{:href => "images/apple-touch-icon-114x114.png", :rel => "apple-touch-icon", :sizes => "114x114"}/ - %link{:href => "http://fonts.googleapis.com/css?family=Sancreek", :rel => "stylesheet", :type => "text/css"}/ + %link{:href => "/favicon.ico", :rel => "shortcut icon"}/ + -#%link{:href => "images/apple-touch-icon.png", :rel => "apple-touch-icon"}/ + -#%link{:href => "images/apple-touch-icon-72x72.png", :rel => "apple-touch-icon", :sizes => "72x72"}/ + -#%link{:href => "images/apple-touch-icon-114x114.png", :rel => "apple-touch-icon", :sizes => "114x114"}/ + %link{:href => "https://fonts.googleapis.com/css?family=Sancreek", :rel => "stylesheet", :type => "text/css"}/ %body @@ -56,7 +56,7 @@ %div{:style => "position:absolute; top:0; right:0;z-index:1040"} = link_to 'https://github.com/rtdp/myloveruby', target: '_blank' do - %img{:alt => "Beta Version", :border => "0", :src => "/assets/forkme_right_red.png"} + %img{:alt => "Beta Version", :border => "0", :src => "/forkme_right_red.png"} .container .row diff --git a/app/assets/images/forkme_right_red.png b/public/forkme_right_red.png similarity index 100% rename from app/assets/images/forkme_right_red.png rename to public/forkme_right_red.png diff --git a/app/assets/images/my_love_ruby.png b/public/my_love_ruby.png similarity index 100% rename from app/assets/images/my_love_ruby.png rename to public/my_love_ruby.png From 476131a448631120dcfe1ea2c93f812ddd1948bd Mon Sep 17 00:00:00 2001 From: Ratnadeep Deshmane Date: Fri, 9 Aug 2019 19:16:46 +0530 Subject: [PATCH 09/35] style fixes --- app/views/layouts/application.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 13c5e62..ab54baf 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -31,7 +31,7 @@ %body - .navbar.navbar-fixed-top + .navbar.navbar-fixed-top.navbar-inverse .navbar-inner .container %a.btn.btn-navbar{"data-target" => ".nav-collapse", "data-toggle" => "collapse"} @@ -74,7 +74,7 @@ .span8 = yield .span1   - .span4 + .span4{ style: 'margin-top:30px;' } .well.sidebar-nav.extra-top-margin %h3 What it's about - %p From 5a89fad8e4db6d1cc152c0a524dda31c76069878 Mon Sep 17 00:00:00 2001 From: anubhav8421 Date: Fri, 9 Aug 2019 16:08:58 +0530 Subject: [PATCH 10/35] #15 update sentry config --- config/initializers/sentry.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb index b504df8..1ce0b3f 100644 --- a/config/initializers/sentry.rb +++ b/config/initializers/sentry.rb @@ -1,4 +1,5 @@ Raven.configure do |config| config.dsn = ENV['SENTRY_DSN'] config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s) + config.environments = %w(production) end From 9c484e55b6185f1ea63a82458e39493aa628b552 Mon Sep 17 00:00:00 2001 From: anubhav8421 Date: Fri, 9 Aug 2019 17:19:01 +0530 Subject: [PATCH 11/35] Fix for broken images on production #12 --- app/views/layouts/application.html.haml | 12 ++++++------ {app/assets/images => public}/forkme_right_red.png | Bin {app/assets/images => public}/my_love_ruby.png | Bin 3 files changed, 6 insertions(+), 6 deletions(-) rename {app/assets/images => public}/forkme_right_red.png (100%) rename {app/assets/images => public}/my_love_ruby.png (100%) diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index dc56005..13c5e62 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -23,11 +23,11 @@ } = stylesheet_link_tag "application", :media => "all" / Le fav and touch icons - %link{:href => "images/favicon.ico", :rel => "shortcut icon"}/ - %link{:href => "images/apple-touch-icon.png", :rel => "apple-touch-icon"}/ - %link{:href => "images/apple-touch-icon-72x72.png", :rel => "apple-touch-icon", :sizes => "72x72"}/ - %link{:href => "images/apple-touch-icon-114x114.png", :rel => "apple-touch-icon", :sizes => "114x114"}/ - %link{:href => "http://fonts.googleapis.com/css?family=Sancreek", :rel => "stylesheet", :type => "text/css"}/ + %link{:href => "/favicon.ico", :rel => "shortcut icon"}/ + -#%link{:href => "images/apple-touch-icon.png", :rel => "apple-touch-icon"}/ + -#%link{:href => "images/apple-touch-icon-72x72.png", :rel => "apple-touch-icon", :sizes => "72x72"}/ + -#%link{:href => "images/apple-touch-icon-114x114.png", :rel => "apple-touch-icon", :sizes => "114x114"}/ + %link{:href => "https://fonts.googleapis.com/css?family=Sancreek", :rel => "stylesheet", :type => "text/css"}/ %body @@ -56,7 +56,7 @@ %div{:style => "position:absolute; top:0; right:0;z-index:1040"} = link_to 'https://github.com/rtdp/myloveruby', target: '_blank' do - %img{:alt => "Beta Version", :border => "0", :src => "/assets/forkme_right_red.png"} + %img{:alt => "Beta Version", :border => "0", :src => "/forkme_right_red.png"} .container .row diff --git a/app/assets/images/forkme_right_red.png b/public/forkme_right_red.png similarity index 100% rename from app/assets/images/forkme_right_red.png rename to public/forkme_right_red.png diff --git a/app/assets/images/my_love_ruby.png b/public/my_love_ruby.png similarity index 100% rename from app/assets/images/my_love_ruby.png rename to public/my_love_ruby.png From d6015e55ac95c797f7e1b839d1d0ee2b2f618e30 Mon Sep 17 00:00:00 2001 From: anubhav8421 Date: Fri, 9 Aug 2019 18:18:02 +0530 Subject: [PATCH 12/35] Minor Fixes * Fix popup for sumbit button #13 * Fix topmost letter full display #13 * Fix website nav icon to "Why Love Ruby" #13 * Alignment of upvote count and user-info #13 * Fix meta tags for SEO #13 * Add favicon #13 * Twitter handle is link to twitter profile #13 * Add share via twitter button #13 * Fix alignment in recents section #13 * Truncate the username displayed in the recent section #13 --- app/assets/javascripts/bootstrap.js | 2288 +++++++++++++++++++- app/assets/javascripts/bootstrap_custom.js | 8 + app/assets/stylesheets/home.css.scss | 21 +- app/views/home/index.html.haml | 52 +- app/views/layouts/application.html.haml | 16 +- app/views/letters/show.html.haml | 46 +- public/ruby-vector-logo.png | Bin 0 -> 32847 bytes 7 files changed, 2381 insertions(+), 50 deletions(-) create mode 100644 app/assets/javascripts/bootstrap_custom.js create mode 100755 public/ruby-vector-logo.png diff --git a/app/assets/javascripts/bootstrap.js b/app/assets/javascripts/bootstrap.js index 9fc2e48..44109f6 100644 --- a/app/assets/javascripts/bootstrap.js +++ b/app/assets/javascripts/bootstrap.js @@ -1,8 +1,2280 @@ -jQuery(function() { - $("a[rel=popover]").popover(); -$(".tooltip").tooltip(); -return $("a[rel=tooltip]").tooltip(); -}); - -// --- -// generated by coffee-script 1.9.2 +/* =================================================== + * bootstrap-transition.js v2.3.2 + * http://getbootstrap.com/2.3.2/javascript.html#transitions + * =================================================== + * Copyright 2013 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* CSS TRANSITION SUPPORT (http://www.modernizr.com/) + * ======================================================= */ + + $(function () { + + $.support.transition = (function () { + + var transitionEnd = (function () { + + var el = document.createElement('bootstrap') + , transEndEventNames = { + 'WebkitTransition' : 'webkitTransitionEnd' + , 'MozTransition' : 'transitionend' + , 'OTransition' : 'oTransitionEnd otransitionend' + , 'transition' : 'transitionend' + } + , name + + for (name in transEndEventNames){ + if (el.style[name] !== undefined) { + return transEndEventNames[name] + } + } + + }()) + + return transitionEnd && { + end: transitionEnd + } + + })() + + }) + +}(window.jQuery);/* ========================================================== + * bootstrap-alert.js v2.3.2 + * http://getbootstrap.com/2.3.2/javascript.html#alerts + * ========================================================== + * Copyright 2013 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* ALERT CLASS DEFINITION + * ====================== */ + + var dismiss = '[data-dismiss="alert"]' + , Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.prototype.close = function (e) { + var $this = $(this) + , selector = $this.attr('data-target') + , $parent + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 + } + + $parent = $(selector) + + e && e.preventDefault() + + $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) + + $parent.trigger(e = $.Event('close')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + $parent + .trigger('closed') + .remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent.on($.support.transition.end, removeElement) : + removeElement() + } + + + /* ALERT PLUGIN DEFINITION + * ======================= */ + + var old = $.fn.alert + + $.fn.alert = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('alert') + if (!data) $this.data('alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.alert.Constructor = Alert + + + /* ALERT NO CONFLICT + * ================= */ + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + /* ALERT DATA-API + * ============== */ + + $(document).on('click.alert.data-api', dismiss, Alert.prototype.close) + +}(window.jQuery);/* ============================================================ + * bootstrap-button.js v2.3.2 + * http://getbootstrap.com/2.3.2/javascript.html#buttons + * ============================================================ + * Copyright 2013 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* BUTTON PUBLIC CLASS DEFINITION + * ============================== */ + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, $.fn.button.defaults, options) + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + , $el = this.$element + , data = $el.data() + , val = $el.is('input') ? 'val' : 'html' + + state = state + 'Text' + data.resetText || $el.data('resetText', $el[val]()) + + $el[val](data[state] || this.options[state]) + + // push to event loop to allow forms to submit + setTimeout(function () { + state == 'loadingText' ? + $el.addClass(d).attr(d, d) : + $el.removeClass(d).removeAttr(d) + }, 0) + } + + Button.prototype.toggle = function () { + var $parent = this.$element.closest('[data-toggle="buttons-radio"]') + + $parent && $parent + .find('.active') + .removeClass('active') + + this.$element.toggleClass('active') + } + + + /* BUTTON PLUGIN DEFINITION + * ======================== */ + + var old = $.fn.button + + $.fn.button = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('button') + , options = typeof option == 'object' && option + if (!data) $this.data('button', (data = new Button(this, options))) + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + $.fn.button.defaults = { + loadingText: 'loading...' + } + + $.fn.button.Constructor = Button + + + /* BUTTON NO CONFLICT + * ================== */ + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + /* BUTTON DATA-API + * =============== */ + + $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + $btn.button('toggle') + }) + +}(window.jQuery);/* ========================================================== + * bootstrap-carousel.js v2.3.2 + * http://getbootstrap.com/2.3.2/javascript.html#carousel + * ========================================================== + * Copyright 2013 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* CAROUSEL CLASS DEFINITION + * ========================= */ + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.options.pause == 'hover' && this.$element + .on('mouseenter', $.proxy(this.pause, this)) + .on('mouseleave', $.proxy(this.cycle, this)) + } + + Carousel.prototype = { + + cycle: function (e) { + if (!e) this.paused = false + if (this.interval) clearInterval(this.interval); + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + return this + } + + , getActiveIndex: function () { + this.$active = this.$element.find('.item.active') + this.$items = this.$active.parent().children() + return this.$items.index(this.$active) + } + + , to: function (pos) { + var activeIndex = this.getActiveIndex() + , that = this + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) { + return this.$element.one('slid', function () { + that.to(pos) + }) + } + + if (activeIndex == pos) { + return this.pause().cycle() + } + + return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) + } + + , pause: function (e) { + if (!e) this.paused = true + if (this.$element.find('.next, .prev').length && $.support.transition.end) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + clearInterval(this.interval) + this.interval = null + return this + } + + , next: function () { + if (this.sliding) return + return this.slide('next') + } + + , prev: function () { + if (this.sliding) return + return this.slide('prev') + } + + , slide: function (type, next) { + var $active = this.$element.find('.item.active') + , $next = next || $active[type]() + , isCycling = this.interval + , direction = type == 'next' ? 'left' : 'right' + , fallback = type == 'next' ? 'first' : 'last' + , that = this + , e + + this.sliding = true + + isCycling && this.pause() + + $next = $next.length ? $next : this.$element.find('.item')[fallback]() + + e = $.Event('slide', { + relatedTarget: $next[0] + , direction: direction + }) + + if ($next.hasClass('active')) return + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + this.$element.one('slid', function () { + var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) + $nextIndicator && $nextIndicator.addClass('active') + }) + } + + if ($.support.transition && this.$element.hasClass('slide')) { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + this.$element.one($.support.transition.end, function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { that.$element.trigger('slid') }, 0) + }) + } else { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger('slid') + } + + isCycling && this.cycle() + + return this + } + + } + + + /* CAROUSEL PLUGIN DEFINITION + * ========================== */ + + var old = $.fn.carousel + + $.fn.carousel = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('carousel') + , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option) + , action = typeof option == 'string' ? option : options.slide + if (!data) $this.data('carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + $.fn.carousel.defaults = { + interval: 5000 + , pause: 'hover' + } + + $.fn.carousel.Constructor = Carousel + + + /* CAROUSEL NO CONFLICT + * ==================== */ + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + /* CAROUSEL DATA-API + * ================= */ + + $(document).on('click.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { + var $this = $(this), href + , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 + , options = $.extend({}, $target.data(), $this.data()) + , slideIndex + + $target.carousel(options) + + if (slideIndex = $this.attr('data-slide-to')) { + $target.data('carousel').pause().to(slideIndex).cycle() + } + + e.preventDefault() + }) + +}(window.jQuery);/* ============================================================= + * bootstrap-collapse.js v2.3.2 + * http://getbootstrap.com/2.3.2/javascript.html#collapse + * ============================================================= + * Copyright 2013 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* COLLAPSE PUBLIC CLASS DEFINITION + * ================================ */ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, $.fn.collapse.defaults, options) + + if (this.options.parent) { + this.$parent = $(this.options.parent) + } + + this.options.toggle && this.toggle() + } + + Collapse.prototype = { + + constructor: Collapse + + , dimension: function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + , show: function () { + var dimension + , scroll + , actives + , hasData + + if (this.transitioning || this.$element.hasClass('in')) return + + dimension = this.dimension() + scroll = $.camelCase(['scroll', dimension].join('-')) + actives = this.$parent && this.$parent.find('> .accordion-group > .in') + + if (actives && actives.length) { + hasData = actives.data('collapse') + if (hasData && hasData.transitioning) return + actives.collapse('hide') + hasData || actives.data('collapse', null) + } + + this.$element[dimension](0) + this.transition('addClass', $.Event('show'), 'shown') + $.support.transition && this.$element[dimension](this.$element[0][scroll]) + } + + , hide: function () { + var dimension + if (this.transitioning || !this.$element.hasClass('in')) return + dimension = this.dimension() + this.reset(this.$element[dimension]()) + this.transition('removeClass', $.Event('hide'), 'hidden') + this.$element[dimension](0) + } + + , reset: function (size) { + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + [dimension](size || 'auto') + [0].offsetWidth + + this.$element[size !== null ? 'addClass' : 'removeClass']('collapse') + + return this + } + + , transition: function (method, startEvent, completeEvent) { + var that = this + , complete = function () { + if (startEvent.type == 'show') that.reset() + that.transitioning = 0 + that.$element.trigger(completeEvent) + } + + this.$element.trigger(startEvent) + + if (startEvent.isDefaultPrevented()) return + + this.transitioning = 1 + + this.$element[method]('in') + + $.support.transition && this.$element.hasClass('collapse') ? + this.$element.one($.support.transition.end, complete) : + complete() + } + + , toggle: function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + } + + + /* COLLAPSE PLUGIN DEFINITION + * ========================== */ + + var old = $.fn.collapse + + $.fn.collapse = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('collapse') + , options = $.extend({}, $.fn.collapse.defaults, $this.data(), typeof option == 'object' && option) + if (!data) $this.data('collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.collapse.defaults = { + toggle: true + } + + $.fn.collapse.Constructor = Collapse + + + /* COLLAPSE NO CONFLICT + * ==================== */ + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + /* COLLAPSE DATA-API + * ================= */ + + $(document).on('click.collapse.data-api', '[data-toggle=collapse]', function (e) { + var $this = $(this), href + , target = $this.attr('data-target') + || e.preventDefault() + || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 + , option = $(target).data('collapse') ? 'toggle' : $this.data() + $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed') + $(target).collapse(option) + }) + +}(window.jQuery);/* ============================================================ + * bootstrap-dropdown.js v2.3.2 + * http://getbootstrap.com/2.3.2/javascript.html#dropdowns + * ============================================================ + * Copyright 2013 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* DROPDOWN CLASS DEFINITION + * ========================= */ + + var toggle = '[data-toggle=dropdown]' + , Dropdown = function (element) { + var $el = $(element).on('click.dropdown.data-api', this.toggle) + $('html').on('click.dropdown.data-api', function () { + $el.parent().removeClass('open') + }) + } + + Dropdown.prototype = { + + constructor: Dropdown + + , toggle: function (e) { + var $this = $(this) + , $parent + , isActive + + if ($this.is('.disabled, :disabled')) return + + $parent = getParent($this) + + isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement) { + // if mobile we we use a backdrop because click events don't delegate + $('