diff --git a/Readme.md b/Readme.md index 96b8aea..cfd25b7 100644 --- a/Readme.md +++ b/Readme.md @@ -3,22 +3,18 @@ A web server blueprint using Sinatra and Redis. Storm Weather is only a vague idea. Trying to nail down a lightweight blueprint for -projects that have user accounts and admin accounts. +projects that have user accounts. ## Models ### Account Model -### Admin Model - ## Controllers ### Account Controller -### Admin Controller - ### Public Controller diff --git a/lib/class-ext.rb b/lib/class-ext.rb new file mode 100644 index 0000000..c08d50d --- /dev/null +++ b/lib/class-ext.rb @@ -0,0 +1,13 @@ +# Copyright 2012 Sami Samhuri + +class Class + + def singleton + (class < e + rescue Account::DuplicateFieldError => e flash[:warning] = "That email address is already taken." session[:fields] = fields session[:fields]['terms'] = params['terms'] @@ -48,7 +48,7 @@ module Stormy end get '/sign-in' do - redirect '/projects' if authorized? && production? + redirect '/account' if authorized? && production? title 'Sign In' stylesheet 'sign-in' @@ -70,7 +70,7 @@ module Stormy else response.delete_cookie('remembered') end - url = session.delete(:original_url) || '/projects' + url = session.delete(:original_url) || '/account' redirect url else flash[:warning] = "Incorrect email address or password." @@ -121,7 +121,7 @@ module Stormy authorize! current_account.password = params['password'] current_account.save! - redirect '/projects' + redirect '/account' end get '/account' do @@ -186,7 +186,7 @@ module Stormy current_account.update({ params['id'] => params['value'] }) end ok - rescue Account::EmailTakenError => e + rescue Account::DuplicateFieldError => e fail('taken') rescue Account::InvalidDataError => e fail('invalid') diff --git a/lib/stormy/controllers/admin_controller.rb b/lib/stormy/controllers/admin_controller.rb index 74a28a1..d803b98 100644 --- a/lib/stormy/controllers/admin_controller.rb +++ b/lib/stormy/controllers/admin_controller.rb @@ -6,48 +6,7 @@ module Stormy get '/admin' do admin_authorize! title "Dashboard" - erb :'admin/dashboard', :layout => :'admin/layout' - end - - get '/admin/sign-in' do - title "Sign In" - script 'sign-in' - stylesheet 'sign-in' - erb :'admin/sign-in', :layout => :'admin/layout' - end - - post '/admin/sign-in' do - if id = Admin.check_password(params['email'], params['password']) - authorize_admin(id) - redirect session.delete(:original_url) || '/admin' - else - flash[:notice] = "Incorrect email address or password." - redirect '/admin/sign-in' - end - end - - post '/admin/sign-out' do - session.delete(:admin_id) - redirect '/admin' - end - - get '/admin/password' do - admin_authorize! - title 'Change password' - erb :'admin/password', :layout => :'admin/layout' - end - - post '/admin/password' do - admin_authorize! - if params['password'] == params['password_confirmation'] - current_admin.password = params['password'] - current_admin.save - flash[:notice] = "Password changed." - redirect '/admin' - else - flash[:warning] = "Passwords do not match." - redirect '/admin/password' - end + erb :'admin/dashboard' end @@ -60,7 +19,7 @@ module Stormy mark_last_listing title "Accounts" @accounts = Account.fetch_all.sort { |a,b| a.name <=> b.name } - erb :'admin/accounts', :layout => :'admin/layout' + erb :'admin/accounts' end get '/admin/account/:email' do |email| @@ -69,7 +28,7 @@ module Stormy mark_last_listing title "#{@account.name}'s Account" script 'admin-account' - erb :'admin/account', :layout => :'admin/layout' + erb :'admin/account' else flash[:notice] = "No account with email #{email}" redirect last_listing @@ -79,7 +38,7 @@ module Stormy get '/admin/sign-in-as/:email' do |email| admin_authorize! authorize_account(Account.id_from_email(email)) - redirect '/projects' + redirect '/account' end get '/admin/account/:email/delete' do |email| @@ -105,7 +64,7 @@ module Stormy if new_email != @account.email begin @account.update_email(new_email) - rescue Account::EmailTakenError => e + rescue Account::DuplicateFieldError => e flash[:warning] = "That email address is already taken." redirect '/admin/account/' + email end @@ -124,38 +83,6 @@ module Stormy end - ################ - ### Projects ### - ################ - - get '/admin/projects' do - admin_authorize! - mark_last_listing - title "Projects" - @projects = Project.fetch_all.sort { |a,b| a.id <=> b.id } - erb :'admin/projects', :layout => :'admin/layout' - end - - get '/admin/project/:id' do |id| - admin_authorize! - if @project = Project.fetch(id) - title "Project ##{id}" - title "#{title} (#{@project.name})" if @project.name - script 'admin-project' - erb :'admin/project', :layout => :'admin/layout' - else - flash[:notice] = "No such project (ID #{id})." - redirect last_listing - end - end - - get '/admin/project/:id/delete' do |id| - admin_authorize! - Project.delete!(id) - redirect last_listing - end - - ########### ### FAQ ### ########### @@ -170,7 +97,7 @@ module Stormy

Yes my son.

EOT end - erb :'admin/faq', :layout => :'admin/layout' + erb :'admin/faq' end post '/admin/faq' do @@ -180,38 +107,5 @@ module Stormy redirect '/admin/faq' end - - ###################### - ### Admin Accounts ### - ###################### - - get '/admin/admins' do - admin_authorize! - @admins = Admin.fetch_all.sort { |a,b| a.name <=> b.name } - @fields = session.delete(:fields) || {} - title 'Admin Accounts' - stylesheet 'admins' - erb :'admin/admins', :layout => :'admin/layout' - end - - post '/admin/admins' do - admin_authorize! - if params['password'] == params['password_confirmation'] - admin = Admin.create(params) - flash[:notice] = "Added #{params['name']} (#{params['email']}) as an admin." - else - session[:fields] = params.slice('name', 'email') - flash[:warning] = "Passwords do not match." - end - redirect '/admin/admins' - end - - get '/admin/admins/:id/delete' do |id| - admin_authorize! - Admin.delete!(id) - flash[:notice] = "Deleted." - redirect '/admin/admins' - end - end end diff --git a/lib/stormy/controllers/projects_controller.rb b/lib/stormy/controllers/projects_controller.rb deleted file mode 100644 index c52d244..0000000 --- a/lib/stormy/controllers/projects_controller.rb +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright 2011 Beta Street Media - -module Stormy - class Server < Sinatra::Base - - get '/projects' do - authorize! - @projects = current_account.sorted_projects - title 'Projects' - stylesheet 'projects' - script 'projects' - erb :projects - end - - get '/project/:id' do |id| - authorize_project!(id) - if current_project.name.blank? - title "Project ID #{id}" - else - title current_project.name - end - stylesheet 'jquery.lightbox-0.5' - script 'jquery.lightbox-0.5' - script 'jquery.dragsort' - stylesheet 'edit-project' - script 'edit-project' - - # fuck IE - if request.user_agent.match(/msie/i) - stylesheet 'uploadify' - script 'swfobject' - script 'jquery.uploadify.v2.1.4' - end - - @errors = session.delete('errors') - @project = current_project - erb :'edit-project' - end - - post '/project/update' do - id = params['id'] - if admin_authorized? - current_project(id) - else - authorize_project!(id) - end - - begin - current_project.update(params) - - flash[:notice] = "Project saved." - rescue Project::InvalidDataError => e - flash[:warning] = "There are some errors with your project." - session['errors'] = e.fields - end - - redirect '/project/' + params['id'] - end - - post '/project/add-photo' do - content_type :json - id = params['id'] - if admin_authorized? - current_project(id) - else - authorize_project_api!(id) - end - - if photo = current_project.add_photo(params['photo'][:tempfile].path) - ok({ - 'n' => current_project.count_photos, - 'photo' => photo - }) - else - fail('limit') - end - end - - # fuck IE - post '/uploadify' do - content_type :json - authorize_project_api!(params['id']) - if photo = current_project.add_photo(params['Filedata'][:tempfile].path) - ok({ - 'n' => current_project.count_photos, - 'photo' => photo - }) - else - content_type 'text/plain' - bad_request - end - end - - post '/project/remove-photo' do - content_type :json - if admin_authorized? - current_project(params['id']) - else - authorize_project_api!(params['id']) - end - - current_project.remove_photo(params['photo_id']) - ok({ - 'photos' => current_project.photos - }) - end - - post '/project/photo-order' do - content_type :json - id = params['id'] - if admin_authorized? - current_project(id) - else - authorize_project_api!(id) - end - - current_project.photo_ids = params['order'] - current_project.save! - ok - end - - end -end diff --git a/lib/stormy/controllers/public_controller.rb b/lib/stormy/controllers/public_controller.rb index c073779..9e053cb 100644 --- a/lib/stormy/controllers/public_controller.rb +++ b/lib/stormy/controllers/public_controller.rb @@ -7,8 +7,6 @@ module Stormy get '/' do cache_control :public, :must_revalidate, :max_age => 60 stylesheet 'index' - stylesheet 'jquery.lightbox-0.5' - script 'jquery.lightbox-0.5' script 'index' erb :index end diff --git a/lib/stormy/helpers/accounts.rb b/lib/stormy/helpers/accounts.rb index 0f1f1d5..dc56ac7 100644 --- a/lib/stormy/helpers/accounts.rb +++ b/lib/stormy/helpers/accounts.rb @@ -25,7 +25,6 @@ module Stormy end def send_verification_mail(account = current_account, subject = nil) - account.create_email_verification_token body = erb(:'email/email-verification', :layout => :'email/layout', :locals => { :name => account.first_name, :email => account.email, diff --git a/lib/stormy/helpers/admin.rb b/lib/stormy/helpers/admin.rb index b35818e..e7ec214 100644 --- a/lib/stormy/helpers/admin.rb +++ b/lib/stormy/helpers/admin.rb @@ -10,21 +10,13 @@ module Stormy Account.count end - def num_admins - Models::Admin.count - end - - def num_projects - Project.count - end - # Used to redirect back to the most recent list of things. # - # i.e. someone goes to /admin -> /admin/account/foo -> /admin/project/007 - # if they delete that project they should go back to /admin/account/foo + # i.e. someone goes to /admin -> /admin/account/foo -> /admin/thing/007 + # if they delete that thing they should go back to /admin/account/foo # - # however if they go /admin -> /admin/projects -> /admin/project/007 - # and then delete that project they should go back to /admin/projects + # however if they go /admin -> /admin/things -> /admin/thing/007 + # and then delete that thing they should go back to /admin/things def last_listing session.delete(:last_listing) || '/admin' end diff --git a/lib/stormy/helpers/authorization.rb b/lib/stormy/helpers/authorization.rb index d57cff8..d4a1e9b 100644 --- a/lib/stormy/helpers/authorization.rb +++ b/lib/stormy/helpers/authorization.rb @@ -42,53 +42,14 @@ module Stormy end end - def current_project(id = nil) - if id - @current_project = Project.fetch(id) - else - @current_project - end - end - - def project_authorized? - current_project && current_account && current_project.account_id == current_account.id - end - - def authorize_project_api!(id) - authorize_api! - current_project(id) - throw(:halt, fail('no such project')) unless current_project - unless project_authorized? - content_type 'text/plain' - throw(:halt, not_authorized) - end - end - - def authorize_project!(id) - authorize! - current_project(id) - unless current_project && project_authorized? - flash[:warning] = 'No such project.' - redirect '/projects' - end - end - - def authorize_admin(id) - session[:admin_id] = id - end - - def deauthorize_admin - session.delete(:admin_id) - end - def admin_authorized? - session[:admin_id] && Models::Admin.exists?(session[:admin_id]) + authorized? && current_account.has_role?('admin') end def admin_authorize! unless admin_authorized? session[:original_url] = request.url - redirect '/admin' + redirect '/sign-in' end end @@ -99,10 +60,6 @@ module Stormy end end - def current_admin - @current_admin ||= Models::Admin.fetch(session[:admin_id]) - end - end end end diff --git a/lib/stormy/helpers/utils.rb b/lib/stormy/helpers/utils.rb index 99161b4..e24dffe 100644 --- a/lib/stormy/helpers/utils.rb +++ b/lib/stormy/helpers/utils.rb @@ -1,5 +1,6 @@ # Copyright 2012 Sami Samhuri +require 'json' require 'stormy/config' module Stormy diff --git a/lib/stormy/helpers/views.rb b/lib/stormy/helpers/views.rb index 2cbcf0f..e7e3dfd 100644 --- a/lib/stormy/helpers/views.rb +++ b/lib/stormy/helpers/views.rb @@ -64,6 +64,15 @@ module Stormy end end + def breadcrumbs + @breadcrumbs ||= [] + end + + def breadcrumb(crumb) + crumb[:path] ||= '/' + crumb[:name].downcase + breadcrumbs << crumb + end + def format_dollars(amount, currency = 'CAD') '%s $%.2f' % [currency, amount / 100.0] end @@ -116,6 +125,10 @@ module Stormy RDiscount.new(s.to_s).to_html end + def admin_page?(path = request.path_info) + path.starts_with?('/admin') + end + end end end diff --git a/lib/stormy/models/account.rb b/lib/stormy/models/account.rb index 269f7d1..3e44914 100644 --- a/lib/stormy/models/account.rb +++ b/lib/stormy/models/account.rb @@ -7,27 +7,26 @@ module Stormy module Models class Account < Base - class EmailTakenError < RuntimeError; end class IncorrectPasswordError < RuntimeError; end - name 'account' + Roles = %w[user admin] + + model_name 'account' field :id, :required => true - field :email, :type => :email, :required => true + field :email, :type => :email, :required => true, :unique => true field :first_name, :required => true, :updatable => true field :last_name, :required => true, :updatable => true field :phone, :type => :phone, :updatable => true - field :hashed_password, :required => true - field :password - field :created_timestamp, :type => :integer field :email_verification_token, :nullify_if_blank => true field :email_verified? + field :hashed_password, :required => true + field :password field :password_reset_token, :nullify_if_blank => true - - @@account_email_index_key = Stormy.key('index:account-email') + field :role, :required => true ### Class Methods @@ -41,18 +40,8 @@ module Stormy end end - def self.email_taken?(email) - !! redis.hget(@@account_email_index_key, email.to_s.strip.downcase) - end - - def self.fetch_by_email(email) - if id = id_from_email(email) - fetch(id) - end - end - def self.reset_password(email) - if key = key_from_email(email) + if key = key(id_from_email(email)) token = redis.hget(key, 'password_reset_token') if token.blank? token = UUID.generate @@ -75,12 +64,8 @@ module Stormy end end - def self.id_from_email(email) - redis.hget(@@account_email_index_key, email.strip.downcase) - end - def self.verify_email(email, token) - if key = key_from_email(email) + if key = key(id_from_email(email)) expected_token = redis.hget(key, 'email_verification_token') verified = token == expected_token if verified @@ -92,21 +77,12 @@ module Stormy end def self.email_verified?(email) - if key = key_from_email(email) + if key = key(id_from_email(email)) redis.hget(key, 'email_verified') == 'true' end end - ### Private Class Methods - - def self.key_from_email(email) - key(id_from_email(email)) - end - - private_class_method :key_from_email - - ### Instance Methods def initialize(fields = {}, options = {}) @@ -120,36 +96,20 @@ module Stormy end def create - raise EmailTakenError if email_taken? - - # new accounts get an id and timestamp - self.id = UUID.generate unless id.present? - self.created_timestamp = Time.now.to_i - + self.role = 'user' if role.blank? super - - create_email_verification_token - - # add to index - redis.hset(@@account_email_index_key, email.downcase, id) - - self end - def delete! - project_ids.each { |id| Project.delete!(id) } - super - redis.hdel(@@account_email_index_key, email.strip.downcase) + def has_role?(role) + Roles.index(self.role) >= Roles.index(role) end - def email_taken?(email = @email) - self.class.email_taken?(email) - end - - def create_email_verification_token - self.email_verification_token ||= UUID.generate - redis.hset(key, 'email_verification_token', email_verification_token) - email_verification_token + def email_verification_token + unless @email_verification_token + @email_verification_token = UUID.generate + redis.hset(key, 'email_verification_token', @email_verification_token) + end + @email_verification_token end def password @@ -167,41 +127,16 @@ module Stormy "#{first_name} #{last_name}" end - def count_projects - redis.scard(project_ids_key) - end - - def project_ids - redis.smembers(project_ids_key) - end - - def projects - project_ids.map { |pid| Project.fetch(pid) } - end - - def sorted_projects - @sorted_projects ||= projects.sort { |a,b| a.created_timestamp <=> b.created_timestamp } - end - - def add_project_id(id) - redis.sadd(project_ids_key, id) - end - - def remove_project_id(id) - redis.srem(project_ids_key, id) - end - def update_email(new_email) new_email = new_email.strip if email != new_email - raise EmailTakenError if new_email.downcase != email.downcase && email_taken?(new_email) - raise InvalidDataError.new({ 'email' => 'invalid' }) unless field_valid?('email', new_email) - if email.downcase != new_email.downcase - self.email_verified = false - redis.hdel(@@account_email_index_key, email.downcase) - redis.hset(@@account_email_index_key, new_email.downcase, id) - end + changed = new_email.downcase != email.downcase + raise DuplicateFieldError.new(:email => new_email) if changed && email_taken?(new_email) + raise InvalidDataError.new('email' => 'invalid') unless field_valid?('email', new_email) + self.email_verified = false if changed + remove_from_field_index(:email) if changed self.email = new_email + add_to_field_index(:email) if changed save! end end @@ -214,13 +149,6 @@ module Stormy self.password = new_password end - - private - - def project_ids_key - @project_ids_key ||= "#{key}:project-ids" - end - end end end \ No newline at end of file diff --git a/lib/stormy/models/admin.rb b/lib/stormy/models/admin.rb deleted file mode 100644 index 961073d..0000000 --- a/lib/stormy/models/admin.rb +++ /dev/null @@ -1,124 +0,0 @@ -# Copyright 2012 Sami Samhuri - -require 'bcrypt' -require 'uuid' - -module Stormy - module Models - class Admin < Base - - class EmailTakenError < RuntimeError; end - class IncorrectPasswordError < RuntimeError; end - - name 'admin' - - field :id, :required => true - - field :name, :required => true, :updatable => true - field :email, :type => :email, :required => true - field :hashed_password, :required => true - field :password - - @@admin_email_index_key = Stormy.key('index:admin-email') - - - ### Class Methods - - def self.key_from_email(email) - key(id_from_email(email)) - end - - def self.check_password(email, password) - id = id_from_email(email) - key = self.key(id) - if key - hashed_password = BCrypt::Password.new(redis.hget(key, 'hashed_password')) - id if hashed_password == password - end - end - - def self.email_taken?(email) - !! redis.hget(@@admin_email_index_key, email.to_s.strip.downcase) - end - - def self.fetch_by_email(email) - key = key_from_email(email) - new(redis.hgetall(key)) if key - end - - def self.id_from_email(email) - redis.hget(@@admin_email_index_key, email.strip.downcase) - end - - - ### Instance Methods - - def initialize(fields = {}, options = {}) - super(fields, options) - - if fields['hashed_password'] - self.hashed_password = BCrypt::Password.new(fields['hashed_password']) - else - self.password = fields['password'] - end - end - - def create - raise EmailTakenError if email_taken? - - self.id = UUID.generate unless id.present? - - super - - # add to index - redis.hset(@@admin_email_index_key, @email.downcase, @id) - - self - end - - def delete! - if super - redis.hdel(@@admin_email_index_key, @email.strip.downcase) - end - end - - def email_taken?(email = @email) - self.class.email_taken?(email) - end - - def password - @password ||= BCrypt::Password.new(hashed_password) - end - - def password=(new_password) - if new_password.present? - self.hashed_password = BCrypt::Password.create(new_password) - @password = nil - end - end - - def update_email(new_email) - new_email = new_email.strip - if email != new_email - raise EmailTakenError if new_email.downcase != email.downcase && email_taken?(new_email) - raise InvalidDataError.new({ 'email' => 'invalid' }) unless field_valid?('email', new_email) - if email.downcase != new_email.downcase - redis.hdel(@@admin_email_index_key, email.downcase) - redis.hset(@@admin_email_index_key, new_email.downcase, id) - end - self.email = new_email - save! - end - end - - def update_password(old_password, new_password) - hashed_password = BCrypt::Password.new(redis.hget(key, 'hashed_password')) - raise IncorrectPasswordError unless hashed_password == old_password - raise InvalidDataError.new({ 'password' => 'missing' }) if new_password.blank? - redis.hset(key, 'hashed_password', BCrypt::Password.create(new_password)) - self.password = new_password - end - - end - end -end \ No newline at end of file diff --git a/lib/stormy/models/base.rb b/lib/stormy/models/base.rb index 28529e7..b0ca47f 100644 --- a/lib/stormy/models/base.rb +++ b/lib/stormy/models/base.rb @@ -15,11 +15,18 @@ module Stormy end end + class DuplicateFieldError < RuntimeError + attr_reader :field + def initialize(field = nil) + @field = field + end + end + def self.clean_number(number) number.gsub(/[^\d]/, '').sub(/^1/, '') end - # Allows any 10 digit number in North America, or an empty field (for account creation). + # Allows any 10 digit number in North America, or an empty field PhoneNumberValidator = proc do |number| if number.present? clean_number(number).length == 10 @@ -48,7 +55,7 @@ module Stormy # Define or retrieve the name of this model. - def self.name(name = nil) + def self.model_name(name = nil) if name @model_name = name end @@ -61,20 +68,33 @@ module Stormy @fields ||= {} end + def self.inherit_from(parent) + fields.merge!(parent.fields) + parent.belongs_to_relationships.each do |key, value| + belongs_to_relationships[key] = value.dup + end + parent.has_many_relationships.each do |key, value| + has_many_relationships[key] = value.dup + end + end # Define fields like so: # # field :id, :type => :integer, :required => true - # field :name, :required => true, :updatable => true + # field :name, :required => true, :updatable => true, :indexed => true + # field :email, :required => true, :updatable => true, :unique => true # field :verified? # # Defaults: { # :type => :string, # :required => false, # :updatable => false, + # :accessors => true, # :validator => nil, # with some exceptions # :default => {}, - # :nullify_if_blank => false + # :nullify_if_blank => false, + # :indexed => false, + # :unique => false # } # # Types: :string, :integer, :boolean, :json, as well as @@ -91,12 +111,26 @@ module Stormy # JSON fields accept a :default option used to initialize # a JSON field, and also when a parse fails. # - # Attribute accessors are defined for each field and boolean - # fields get a predicate method as well, e.g. verified? + # Unless :accessors is false, attribute accessors are + # defined for each field. Boolean fields get a predicate + # method as well, e.g. verified? # # Changed fields are tracked and only changed fields are # persisted on a `save`. # + # If the :indexed option is truthy an index on that field + # will be created and maintained and there will be a class + # method to fetch objects by that field. + # + # e.g. fetch_by_name(name) + # + # If the :unique option is truthy then values for that field + # must be unique across all instances. This implies :indexed + # and adds a method to see if a value is taken, to the class + # and to instances. + # + # e.g. email_taken?(email) + # def self.field(name, options = {}) if name.to_s.ends_with?('?') options[:type] = :boolean @@ -106,79 +140,182 @@ module Stormy name = name.to_sym options[:type] ||= :string + unless options.has_key?(:accessors) + options[:accessors] = true + end + + if options[:unique] + options[:indexed] = true + end + case options[:type] when :email - options[:validator] ||= EmailAddressValidator + options[:validator] = EmailAddressValidator unless options.has_key?(:validator) options[:type] = :string when :phone - options[:validator] ||= PhoneNumberValidator + options[:validator] = PhoneNumberValidator unless options.has_key?(:validator) options[:type] = :string when :json - options[:default] ||= {} + options[:default] = {} unless options.has_key?(:default) end fields[name] = options - define_method(name) do - instance_variable_get("@#{name}") - end - case options[:type] - when :string - define_method("#{name}=") do |value| - s = - if options[:nullify_if_blank] && value.blank? - nil - else - value.to_s.strip - end - instance_variable_set("@#{name}", s) - changed_fields[name] = s - end - - when :integer - define_method("#{name}=") do |value| - i = value.to_i - instance_variable_set("@#{name}", i) - changed_fields[name] = i - end - - when :boolean - define_method("#{name}=") do |value| - b = value == 'true' || value == true - instance_variable_set("@#{name}", b) - changed_fields[name] = b - end - define_method("#{name}?") do + if options[:accessors] + define_method(name) do instance_variable_get("@#{name}") end - when :json - define_method(name) do - unless value = instance_variable_get("@#{name}") - value = options[:default].dup - send("#{name}=", value) - end - value - end - define_method("#{name}=") do |value| - obj = - if value.is_a?(String) - if value.length > 0 - JSON.parse(value) + case options[:type] + when :string + define_method("#{name}=") do |value| + s = + if options[:nullify_if_blank] && value.blank? + nil else - options[:default].dup + value.to_s.strip end - else - value + instance_variable_set("@#{name}", s) + changed_fields[name] = s + end + + when :integer + define_method("#{name}=") do |value| + i = value.to_i + instance_variable_set("@#{name}", i) + changed_fields[name] = i + end + + when :boolean + define_method("#{name}=") do |value| + b = value == 'true' || value == true + instance_variable_set("@#{name}", b) + changed_fields[name] = b + end + define_method("#{name}?") do + instance_variable_get("@#{name}") + end + + when :json + define_method(name) do + unless value = instance_variable_get("@#{name}") + value = options[:default].dup + send("#{name}=", value) end - instance_variable_set("@#{name}", obj) - changed_fields[name] = obj + value + end + define_method("#{name}=") do |value| + obj = + if value.is_a?(String) + if value.length > 0 + JSON.parse(value) + else + options[:default].dup + end + else + value + end + instance_variable_set("@#{name}", obj) + changed_fields[name] = obj + end + + else + define_method("#{name}=") do |value| + instance_variable_set("@#{name}", value) + changed_fields[name] = value + end + end + end # if options[:accessors] + + if options[:indexed] + index_key_method_name = "#{name}_index_key" + define_class_method(index_key_method_name) do + Stormy.key("index:#{model_name}-#{name}") + end + define_class_method("fetch_by_#{name}") do |value| + if id = send("id_from_#{name}", value) + fetch(id) + end end - else - define_method("#{name}=") do |value| - instance_variable_set("@#{name}", value) - changed_fields[name] = value + define_class_method("id_from_#{name}") do |value| + redis.hget(send(index_key_method_name), value.to_s.strip.downcase) + end + end + + if options[:unique] + define_class_method("#{name}_taken?") do |value| + !! send("id_from_#{name}", value) + end + + define_method("#{name}_taken?") do |value| + self.class.send("#{name}_taken?", value) + end + end + end + + + ##################### + ### Relationships ### + ##################### + + def self.has_many_relationships + @has_many_relationships ||= {} + end + + def self.has_many(things, options = {}) + thing = things.to_s.singularize + options[:class_name] ||= thing.capitalize + + has_many_relationships[thing] = options + + define_method("#{thing}_ids_key") do + ivar_name = "@#{thing}_ids_key" + unless ids_key = instance_variable_get(ivar_name) + ids_key = "#{key}:#{thing}-ids" + instance_variable_set(ivar_name, ids_key) + end + ids_key + end + private "#{thing}_ids_key" + + define_method("count_#{things}") do + redis.scard(send("#{thing}_ids_key")) + end + + define_method("#{thing}_ids") do + redis.smembers(send("#{thing}_ids_key")) + end + + define_method(things) do + klass = Stormy::Models.const_get(options[:class_name]) + send("#{thing}_ids").map { |id| klass.fetch(id) } + end + + define_method("add_#{thing}_id") do |id| + redis.sadd(send("#{thing}_ids_key"), id) + end + + define_method("remove_#{thing}_id") do |id| + redis.srem(send("#{thing}_ids_key"), id) + end + end + + def self.belongs_to_relationships + @belongs_to_relationships ||= {} + end + + def self.belongs_to(thing, options = {}) + options[:class_name] ||= thing.to_s.capitalize + + field "#{thing}_id".to_sym, :required => options[:required] + + belongs_to_relationships[thing] = options + + define_method(thing) do + klass = Stormy::Models.const_get(options[:class_name]) + if thing_id = send("#{thing}_id") + instance_variable_set("@#{thing}", klass.fetch(thing_id)) end end end @@ -246,14 +383,46 @@ module Stormy end def create + # check for unqiue fields + self.class.fields.each do |name, options| + if options[:unique] && send("#{name}_taken?", send(name)) + raise DuplicateFieldError.new(name => send(name)) + end + end + + if has_field?(:id) && field_required?(:id) + self.id = UUID.generate unless id.present? + end + + if has_field?(:created_timestamp) + self.created_timestamp = Time.now.to_i + end + # raises if invalid save - add_to_index + + add_to_indexes + + self.class.belongs_to_relationships.each do |thing, options| + if obj = send(thing) + obj.send("add_#{self.class.model_name}_id", id) + end + end + self end def delete! - if redis.srem(self.class.model_ids_key, id) + self.class.has_many_relationships.each do |thing, options| + klass = Stormy::Models.const_get(options[:class_name]) + send("#{thing}_ids").each { |id| klass.delete!(id) } + end + if remove_from_indexes + self.class.belongs_to_relationships.each do |thing, options| + if obj = send(thing) + obj.send("remove_#{self.class.model_name}_id", id) + end + end redis.del(key) end end @@ -280,6 +449,12 @@ module Stormy options[:validate] = true unless options.has_key?(:validate) fields.each do |name, value| if options[:all] || field_updatable?(name) + + # ensure uniqueness + if options[:unique] && send("#{name}_taken?", value) + raise DuplicateFieldError.new(name => value) + end + send("#{name}=", value) end end @@ -296,6 +471,10 @@ module Stormy end def save! + if has_field?(:updated_timestamp) + self.updated_timestamp = Time.now.to_i + end + # always update JSON fields because they can be updated without our knowledge field_names.each do |name| if field_type(name) == :json @@ -317,6 +496,7 @@ module Stormy end def validate + # check for invalid fields invalid_fields = field_names.inject({}) do |fields, name| if field_validates?(name) result = validate_field(name, send(name)) @@ -329,6 +509,10 @@ module Stormy end end + def fields + Hash[field_names.zip(field_names.map { |name| send(name) })] + end + private @@ -336,8 +520,39 @@ module Stormy @key ||= self.class.key(self.id) end - def add_to_index - redis.sadd(self.class.model_ids_key, self.id) + def model_name + @model_name ||= self.class.model_name + end + + def add_to_indexes + if redis.sadd(self.class.model_ids_key, id) + self.class.fields.each do |name, options| + add_to_field_index(name) if options[:indexed] + end + end + end + + def add_to_field_index(name) + index_key = self.class.send("#{name}_index_key") + redis.hset(index_key, send(name).to_s.strip.downcase, id) + end + + def remove_from_indexes + if redis.srem(self.class.model_ids_key, id) + success = true + self.class.fields.each do |name, options| + if options[:indexed] + success = success && remove_from_field_index(name) + end + break unless success + end + success + end + end + + def remove_from_field_index(name) + index_key = self.class.send("#{name}_index_key") + redis.hdel(index_key, send(name).to_s.strip.downcase) end def changed_fields @@ -348,6 +563,10 @@ module Stormy self.class.clean_number(number) end + def has_field?(name) + self.class.fields.has_key?(name.to_sym) + end + def field_names self.class.fields.keys end @@ -360,6 +579,10 @@ module Stormy self.class.fields[name.to_sym][:updatable] end + def field_required?(name) + self.class.fields[name.to_sym][:required] + end + def validate_field(name, value) valid = true reason = nil diff --git a/lib/stormy/models/project.rb b/lib/stormy/models/project.rb deleted file mode 100644 index a319b68..0000000 --- a/lib/stormy/models/project.rb +++ /dev/null @@ -1,162 +0,0 @@ -# Copyright 2012 Sami Samhuri - -require 'digest/sha1' -require 'fileutils' -require 'RMagick' - -module Stormy - module Models - class Project < Base - - # max width or height in pixels - MaxPhotoSize = 1200 - MaxPhotos = 10 - - name 'project' - - field :id, :required => true - - field :name, :required => true, :updatable => true - - field :account_id - field :created_timestamp, :type => :integer, :required => true - field :fizzled_timestamp, :type => :integer - field :funded_timestamp, :type => :integer - field :photo_ids, :type => :json, :default => [] - - @@project_name_index_key = Stormy.key('index:project-name') - - def self.fetch_by_name(name) - if id = id_from_name(name) - fetch(id) - end - end - - def self.id_from_name(name) - redis.hget(@@project_name_index_key, name.strip.downcase) - end - - def create - self.id = UUID.generate unless id.present? - self.created_timestamp = Time.now.to_i - - super - - # add to index - redis.hset(@@project_name_index_key, name.downcase, id) - - account.add_project_id(id) if account - - self - end - - def delete! - if super - remove_all_photos! - account.remove_project_id(id) if account - redis.hdel(@@project_name_index_key, name.strip.downcase) - end - end - - def funded? - funded_timestamp > 0 - end - - def funded! - self.funded_timestamp = Time.now.to_i - save! - end - - def fizzled? - fizzled_timestamp > 0 - end - - def fizzled! - self.fizzled_timestamp = Time.now.to_i - save! - end - - def count_photos - photo_ids.length - end - - def add_photo(path) - unless count_photos >= MaxPhotos - photo = Magick::Image.read(path).first - photo.auto_orient! - photo.change_geometry("#{MaxPhotoSize}x#{MaxPhotoSize}>") { |cols, rows, img| img.resize!(cols, rows) } - photo.format = 'jpg' - - FileUtils.mkdir_p(photo_dir) unless File.exists?(photo_dir) - - photo_id = Digest::SHA1.hexdigest(photo.to_blob) - photo.write(photo_path(photo_id)) { self.quality = 80 } - - photo_ids << photo_id - save! - - photo_data(photo_id) - end - end - - def remove_photo(photo_id) - path = photo_path(photo_id) - if i = photo_ids.index(photo_id) - photo_ids.delete_at(i) - end - FileUtils.rm(path) if File.exists?(path) && !photo_ids.include?(photo_id) - save! - end - - def photo_paths - photo_ids.map { |id| photo_path(id) } - end - - def photo_urls - photo_ids.map { |photo_id| "/photos/#{id}/#{photo_id}.jpg" } - end - - def photo_url(photo_id) - "/photos/#{id}/#{photo_id}.jpg" - end - - def photo_data(photo_id) - { - 'id' => photo_id, - 'url' => photo_url(photo_id) - } - end - - def photos - photo_ids.map { |id| photo_data(id) } - end - - def account - if account_id - @account ||= Account.fetch(account_id) - end - end - - - private - - def photo_dir - File.join(Stormy::PhotoDir, @id) - end - - def photo_path(id) - File.join(photo_dir, "#{id}.jpg") - end - - def photos_key - "#{key}:photos" - end - - def remove_all_photos! - FileUtils.rm_rf(photo_dir) if File.exists?(photo_dir) - self.photo_ids = [] - end - - end - end -end diff --git a/lib/stormy/server.rb b/lib/stormy/server.rb index ac7eabd..9186c38 100644 --- a/lib/stormy/server.rb +++ b/lib/stormy/server.rb @@ -7,15 +7,11 @@ require 'sinatra/cookie_thief' require 'sinatra/flash' require 'erubis' -require 'json' require 'pony' require 'redis' require 'redis-store' require 'uuid' -# Ruby extensions -require 'hash-ext' - require 'stormy/models' require 'stormy/controllers' require 'stormy/helpers' @@ -74,8 +70,6 @@ module Stormy if production? body = erb(:'email/error-notification', :layout => false, :locals => { :account => current_account, - :project => current_project, - :admin => current_admin, :error => env['sinatra.error'] }) Pony.mail({ diff --git a/public/css/edit-project.css b/public/css/edit-project.css deleted file mode 100644 index 07bf46a..0000000 --- a/public/css/edit-project.css +++ /dev/null @@ -1,86 +0,0 @@ -.section { width: 90% } - -#photo-form -{ height: 0 -; width: 0 -; opacity: 0 -; position: fixed -; top: 0 -} - -#upload-target -{ width: 0 -; height: 0 -; opacity: 0 -} - -.save -{ width: 90% -; margin: 1em auto -; text-align: right -} - -input.save-button -{ background-color: #092 -; border-color: #061 -} - -.save-button-spinner { display: none } - -table#project-info -{ width: 90% -; margin: 1em auto -} - -table#project-info th -{ color: #405e83 -; text-align: right -; vertical-align: top -; padding: 0.2em 0.5em 0.7em -} - -input[type="text"], -input[type="tel"], -input[type="url"] -{ padding: 0 0.2em -; border: #CCD3DB 1px solid -; font-size: 1em -; height: 1.5em -; margin: 0 -} - -textarea -{ border: #CCD3DB 1px solid -; font-size: 0.9em -; line-height: 1.2em -; padding: 0.3em -; margin: 0 -} - -#photos li -{ display: inline-block -; width: 72px -; height: 84px -; padding: 0 -; margin: 0.2em 0.25em 0.7em -; text-align: center -} - -li#add-photo-box { display: inline-block } -#photos li#add-photo-spinner { height: 18px } - -.add-photo { font-size: 0.8em } -a.add-photo { font-size: 0.9em } -.remove-photo { color: #900 } - -#ie-photo-uploaderQueue { display: none } - -#ie-photo-uploaderUploader -{ height: 64px -; width: 64px -} - -#drag-n-drop -{ padding-left: 3em -; font-style: italic -} diff --git a/public/css/jquery.lightbox-0.5.css b/public/css/jquery.lightbox-0.5.css deleted file mode 100644 index c7c3d1c..0000000 --- a/public/css/jquery.lightbox-0.5.css +++ /dev/null @@ -1,101 +0,0 @@ -/** - * jQuery lightBox plugin - * This jQuery plugin was inspired and based on Lightbox 2 by Lokesh Dhakar (http://www.huddletogether.com/projects/lightbox2/) - * and adapted to me for use like a plugin from jQuery. - * @name jquery-lightbox-0.5.css - * @author Leandro Vieira Pinho - http://leandrovieira.com - * @version 0.5 - * @date April 11, 2008 - * @category jQuery plugin - * @copyright (c) 2008 Leandro Vieira Pinho (leandrovieira.com) - * @license CCAttribution-ShareAlike 2.5 Brazil - http://creativecommons.org/licenses/by-sa/2.5/br/deed.en_US - * @example Visit http://leandrovieira.com/projects/jquery/lightbox/ for more informations about this jQuery plugin - */ -#jquery-overlay { - position: absolute; - top: 0; - left: 0; - z-index: 90; - width: 100%; - height: 500px; -} -#jquery-lightbox { - position: absolute; - top: 0; - left: 0; - width: 100%; - z-index: 100; - text-align: center; - line-height: 0; -} -#jquery-lightbox a img { border: none; } -#lightbox-container-image-box { - position: relative; - background-color: #fff; - width: 250px; - height: 250px; - margin: 0 auto; -} -#lightbox-container-image { padding: 10px; } -#lightbox-loading { - position: absolute; - top: 40%; - left: 0%; - height: 25%; - width: 100%; - text-align: center; - line-height: 0; -} -#lightbox-nav { - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 100%; - z-index: 10; -} -#lightbox-container-image-box > #lightbox-nav { left: 0; } -#lightbox-nav a { outline: none;} -#lightbox-nav-btnPrev, #lightbox-nav-btnNext { - width: 49%; - height: 100%; - zoom: 1; - display: block; -} -#lightbox-nav-btnPrev { - left: 0; - float: left; -} -#lightbox-nav-btnNext { - right: 0; - float: right; -} -#lightbox-container-image-data-box { - font: 10px Verdana, Helvetica, sans-serif; - background-color: #fff; - margin: 0 auto; - line-height: 1.4em; - overflow: auto; - width: 100%; - padding: 0 10px 0; -} -#lightbox-container-image-data { - padding: 0 10px; - color: #666; -} -#lightbox-container-image-data #lightbox-image-details { - width: 70%; - float: left; - text-align: left; -} -#lightbox-image-details-caption { font-weight: bold; } -#lightbox-image-details-currentNumber { - display: block; - clear: left; - padding-bottom: 1.0em; -} -#lightbox-secNav-btnClose { - width: 66px; - float: right; - padding-bottom: 0.7em; -} \ No newline at end of file diff --git a/public/css/projects.css b/public/css/projects.css deleted file mode 100644 index 9d31100..0000000 --- a/public/css/projects.css +++ /dev/null @@ -1,35 +0,0 @@ - -table#projects -{ border-collapse: collapse -; margin: 0 auto 2em -} - -#projects td { padding: 0.3em 0 } -#projects th { text-align: center } - -#projects tr.headings th -{ color: #666 -; padding: 0.3em 0 0.8em -} - -#projects th.name, -#projects td.name -{ text-align: left } - -#projects tr.project { background-color: #dce1e8 } -#projects tr.project:nth-child(even) { background-color: #e4e8ed } -#projects tr.project:hover { background-color: #eee } - -#projects tr.project td -{ border-bottom: solid 1px #aaa -; padding: 0.5em 0 -} - -#projects tr.project.first td { border-top: solid 1px #aaa } - -#projects td.created { text-align: center } - -#projects td.name a -{ color: #333 -; font-size: 1.2em -} diff --git a/public/css/uploadify.css b/public/css/uploadify.css deleted file mode 100644 index 405f59c..0000000 --- a/public/css/uploadify.css +++ /dev/null @@ -1,52 +0,0 @@ -/* -Uploadify v2.1.4 -Release Date: November 8, 2010 - -Copyright (c) 2010 Ronnie Garcia, Travis Nickels - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -.uploadifyQueueItem { - background-color: #F5F5F5; - border: 2px solid #E5E5E5; - font: 11px Verdana, Geneva, sans-serif; - margin-top: 5px; - padding: 10px; - width: 350px; -} -.uploadifyError { - background-color: #FDE5DD !important; - border: 2px solid #FBCBBC !important; -} -.uploadifyQueueItem .cancel { - float: right; -} -.uploadifyQueue .completed { - background-color: #E5E5E5; -} -.uploadifyProgress { - background-color: #E5E5E5; - margin-top: 10px; - width: 100%; -} -.uploadifyProgressBar { - background-color: #0099FF; - height: 3px; - width: 1px; -} \ No newline at end of file diff --git a/public/images/add-photo.png b/public/images/add-photo.png deleted file mode 100644 index 5d9c8c8..0000000 Binary files a/public/images/add-photo.png and /dev/null differ diff --git a/public/images/lightbox-blank.gif b/public/images/lightbox-blank.gif deleted file mode 100755 index 1d11fa9..0000000 Binary files a/public/images/lightbox-blank.gif and /dev/null differ diff --git a/public/images/lightbox-btn-close.gif b/public/images/lightbox-btn-close.gif deleted file mode 100755 index 33bcf51..0000000 Binary files a/public/images/lightbox-btn-close.gif and /dev/null differ diff --git a/public/images/lightbox-btn-next.gif b/public/images/lightbox-btn-next.gif deleted file mode 100755 index a0d4fcf..0000000 Binary files a/public/images/lightbox-btn-next.gif and /dev/null differ diff --git a/public/images/lightbox-btn-prev.gif b/public/images/lightbox-btn-prev.gif deleted file mode 100755 index 040ee59..0000000 Binary files a/public/images/lightbox-btn-prev.gif and /dev/null differ diff --git a/public/images/lightbox-ico-loading.gif b/public/images/lightbox-ico-loading.gif deleted file mode 100755 index 4f1429c..0000000 Binary files a/public/images/lightbox-ico-loading.gif and /dev/null differ diff --git a/public/images/logo.png b/public/images/logo.png index 7eec118..7af0d79 100644 Binary files a/public/images/logo.png and b/public/images/logo.png differ diff --git a/public/images/sign-up-now-button-hover.png b/public/images/sign-up-now-button-hover.png deleted file mode 100644 index 5d5af6a..0000000 Binary files a/public/images/sign-up-now-button-hover.png and /dev/null differ diff --git a/public/images/sign-up-now-button.png b/public/images/sign-up-now-button.png deleted file mode 100644 index 2032bff..0000000 Binary files a/public/images/sign-up-now-button.png and /dev/null differ diff --git a/public/js/account.js b/public/js/account.js index 37b62f2..165e963 100644 --- a/public/js/account.js +++ b/public/js/account.js @@ -1,9 +1,9 @@ $(function() { $('#change-password-link').click(function() { - $(this).hide() - $('#change-password').show() - $('#password-changed').hide() + $(this).addClass('hidden') + $('#change-password').removeClass('hidden') + $('#password-changed').addClass('hidden') $('#old-password').focus() return false }) @@ -14,8 +14,8 @@ $(function() { }) $('#send-email-verification').click(function() { - $('#sending-email-verification').show() - $(this).hide() + $('#sending-email-verification').removeClass('hidden') + $(this).addClass('hidden') var self = this $.post('/account/send-email-verification', function(data) { if (data.status === 'ok') { @@ -29,8 +29,8 @@ $(function() { }).error(function() { alert('Failed to send verification email. Try again later.') }).complete(function() { - $('#sending-email-verification').hide() - $(self).show() + $('#sending-email-verification').addClass('hidden') + $(self).removeClass('hidden') }) return false }) @@ -43,14 +43,14 @@ function changePassword() { , confirmation = $('#password-confirmation').val() if ($.trim(oldPassword) && $.trim(newPassword) && newPassword === confirmation) { $('#change-password-form input[type="password"]').removeClass('error') - $('#change-password-form input[type="submit"]').hide() - $('#change-password-form .spinner').show() + $('#change-password-form input[type="submit"]').addClass('hidden') + $('#change-password-spinner').removeClass('hidden') $.post('/account/password', $('#change-password-form').serialize(), function(data) { if (data.status === 'ok') { $('input[type="password"]').val('') - $('#change-password').hide() - $('#change-password-link').show() - $('#password-changed').show() + $('#change-password').addClass('hidden') + $('#change-password-link').removeClass('hidden') + $('#password-changed').removeClass('hidden') } // incorrect old password else if (data.reason === 'incorrect') { @@ -72,8 +72,8 @@ function changePassword() { }).error(function(x) { alert('Failed to change password. Try again later.') }).complete(function() { - $('#change-password-form input[type="submit"]').show() - $('#change-password-form .spinner').hide() + $('#change-password-form input[type="submit"]').removeClass('hidden') + $('#change-password-spinner').addClass('hidden') }) } else { diff --git a/public/js/admin-project.js b/public/js/admin-project.js deleted file mode 100644 index c130eee..0000000 --- a/public/js/admin-project.js +++ /dev/null @@ -1,7 +0,0 @@ -$(function() { - - $('#delete').click(function() { - return confirm("Are you sure?") - }) - -}) diff --git a/public/js/edit-project.js b/public/js/edit-project.js deleted file mode 100644 index 906e52e..0000000 --- a/public/js/edit-project.js +++ /dev/null @@ -1,215 +0,0 @@ -$(function() { - - initTmpl() - - $('#name').blur(function() { - var name = $.trim($(this).val()) - if (name === this.placeholder) name = '' - $('.save-button').val('Save ' + (name || 'This Project')) - }) - - $('form#project').submit(validateProject) - - initLightBox() - - $('#photos').dragsort({ - dragSelector: 'li.photo' - , itemSelector: 'li.photo' - , dragEnd: updatePhotoOrder - , placeHolderTemplate: '
  • ' - , itemClicked: function(item) { $('a.thumbnail', item).click() } - }) - - var $photos = $('#photos-container') - , $addPhotoBox = $('#add-photo-box') - , $photoUploader = $('#photo-uploader') - - // fuck IE - if ($.browser.msie) { - $('#ie-photo-uploader').uploadify({ - 'uploader' : '/uploadify/uploadify.swf', - 'script' : '/uploadify', - 'multi' : false, - 'buttonImg' : '/images/add-photo.png', - 'method' : 'post', - 'cancelImg' : '/uploadify/cancel.png', - 'auto' : true, - 'fileExt' : ['jpg', 'jpeg', 'png'], - 'sizeLimit' : 10 * 1024 * 1024 * 1024, // 10 MB is way more than enough - 'scriptData' : { id: window.SI.projectId }, - - 'onComplete': function(_a, _b, _c, text) { - completePhotoUpload(text) - }, - 'onError': function() { - completePhotoUpload('fail') - }, - 'onOpen': function() { - $('#add-photo-spinner').remove() - $addPhotoBox.addClass('hidden').before('
  • ') - } - }) - } - - $('.add-photo').click(function() { - $photoUploader.focus().click() - return false - }) - - $photoUploader.change(function() { - $addPhotoBox.addClass('hidden').before('
  • ') - $('#photo-form').submit() - return false - }) - - $('#upload-target').load(function() { - completePhotoUpload($(this).contents().text()) - }) - - var photoTemplate = window.SI.tmpl($('#photo-template').html()) - - function completePhotoUpload(text) { - $('#add-photo-spinner').remove() - var photoForm = $('#photo-form').get(0) - if (photoForm) photoForm.reset() - try { - var response = JSON.parse(text) - if (response.status === 'ok') { - $addPhotoBox.before(photoTemplate(response.data.photo)) - initLightBox() - if (response.data.n >= 10) { - $addPhotoBox.addClass('hidden') - } - else { - $addPhotoBox.removeClass('hidden') - } - } - else { - $addPhotoBox.removeClass('hidden') - alert('Failed to add photo. Try again later.') - } - } - catch (e) { - $addPhotoBox.removeClass('hidden') - alert('Failed to add photo. Try again later.') - } - } - - - var removeCount = 0 - $('.remove-photo').live('click', function() { - var id = this.id - , photoId = id.replace(/^remove-photo-/, '') - , data = { id: window.SI.projectId, photo_id: photoId } - , spinnerId = 'remove-photo-spinner-' + photoId - , $this = $(this) - $this.hide().after('') - removeCount += 1 - $.post('/project/remove-photo', data, function(response) { - removeCount -= 1 - if (response.status === 'ok' && removeCount === 0) { - $addPhotoBox.removeClass('hidden') - $('li.photo').remove() - - $.each(response.data.photos, function(i, photo) { - $addPhotoBox.before(photoTemplate(photo)) - }) - - initLightBox() - } - else { - if (removeCount === 0) { - $('#' + spinnerId).remove() - $this.show() - alert('Failed to remove photo. Try again later.') - } - } - }).error(function() { - removeCount -= 1 - if (removeCount === 0) { - $('#' + spinnerId).remove() - $this.show() - alert('Failed to remove photo. Try again later.') - } - }) - return false - }) - -}) - -function initLightBox() { - $('#photos a.thumbnail').lightBox() - $('#photos a.thumbnail').live('click', function(){ console.dir(this) }) -} - -// Simple JavaScript Templating -// John Resig - http://ejohn.org/ - MIT Licensed -// http://ejohn.org/blog/javascript-micro-templating/ -function initTmpl() { - var cache = {} - - window.SI = window.SI || {} - window.SI.tmpl = function tmpl(str, data) { - // Figure out if we're getting a template, or if we need to - // load the template - and be sure to cache the result. - var fn = !/\W/.test(str) ? - cache[str] = cache[str] || - tmpl($('#' + str).html()) : - - // Generate a reusable function that will serve as a template - // generator (and which will be cached). - new Function("obj", - "var p=[],print=function(){p.push.apply(p,arguments)};" + - - // Introduce the data as local variables using with(){} - "with(obj){p.push('" + - - // Convert the template into pure JavaScript - str - .replace(/[\r\t\n]/g, " ") - .split("<%").join("\t") - .replace(/((^|%>)[^\t]*)'/g, "$1\r") - .replace(/\t=(.*?)%>/g, "',$1,'") - .split("\t").join("');") - .split("%>").join("p.push('") - .split("\r").join("\\'") - + "')}return p.join('')") - - // Provide some basic currying to the user - return data ? fn( data ) : fn - } -} - -function updatePhotoOrder() { - initLightBox() - var ids = [] - $('#photos li.photo').each(function() { - ids.push(this.id.replace('photo-', '')) - }) - var data = { id: window.SI.projectId, order: ids } - $.post('/project/photo-order', data, function(response) { - // noop - }).error(function() { - alert('Failed to reorder photos. Try again later.') - }) -} - -function validateProject() { - var valid = true - , nameField = $('#name') - - if ($.trim(nameField.val()).length === 0) { - valid = false - nameField.addClass('error').focus().select() - } - else { - nameField.removeClass('error') - } - - if (valid) { - $('.save-button').hide() - $('.save-button-spinner').show() - } - - return valid -} diff --git a/public/js/jquery.dragsort.js b/public/js/jquery.dragsort.js deleted file mode 100644 index 23e87c4..0000000 --- a/public/js/jquery.dragsort.js +++ /dev/null @@ -1,297 +0,0 @@ -// jQuery List DragSort v0.4 -// Website: http://dragsort.codeplex.com/ -// License: http://dragsort.codeplex.com/license - -(function($) { - - $.fn.dragsort = function(options) { - var opts = $.extend({}, $.fn.dragsort.defaults, options); - var lists = []; - var list = null, lastPos = null; - - this.each(function(i, cont) { - - if ($(cont).is("table") && $(cont).children().size() == 1 && $(cont).children().is("tbody")) - cont = $(cont).children().get(0); - - var newList = { - draggedItem: null, - placeHolderItem: null, - pos: null, - offset: null, - offsetLimit: null, - scroll: null, - container: cont, - - init: function() { - $(this.container).attr("data-listIdx", i).mousedown(this.grabItem) - $(this.container).children(opts.itemSelector).each(function(j) { $(this).attr("data-itemIdx", j); }); - }, - - grabItem: function(e) { - if (e.which != 1 || $(e.target).is(opts.dragSelectorExclude)) - return; - - var elm = e.target; - while (!$(elm).is("[data-listIdx='" + $(this).attr("data-listIdx") + "'] " + opts.dragSelector)) { - if (elm == this) return; - elm = elm.parentNode; - } - - if (list != null && list.draggedItem != null) - list.dropItem(); - - list = lists[$(this).attr("data-listIdx")]; - list.draggedItem = $(elm).closest(opts.itemSelector); - var mt = parseInt(list.draggedItem.css("marginTop")); - var ml = parseInt(list.draggedItem.css("marginLeft")); - list.offset = list.draggedItem.offset(); - list.offset.top = e.pageY - list.offset.top + (isNaN(mt) ? 0 : mt) - 1; - list.offset.left = e.pageX - list.offset.left + (isNaN(ml) ? 0 : ml) - 1; - list.draggedItem.startOffset = list.draggedItem.offset() - list.draggedItem.startTime = +new Date() - - if (!opts.dragBetween) { - var containerHeight = $(list.container).outerHeight() == 0 ? Math.max(1, Math.round(0.5 + $(list.container).children(opts.itemSelector).size() * list.draggedItem.outerWidth() / $(list.container).outerWidth())) * list.draggedItem.outerHeight() : $(list.container).outerHeight(); - list.offsetLimit = $(list.container).offset(); - list.offsetLimit.right = list.offsetLimit.left + $(list.container).outerWidth() - list.draggedItem.outerWidth(); - list.offsetLimit.bottom = list.offsetLimit.top + containerHeight - list.draggedItem.outerHeight(); - } - - var h = list.draggedItem.height(); - var w = list.draggedItem.width(); - var orig = list.draggedItem.attr("style"); - list.draggedItem.attr("data-origStyle", orig ? orig : ""); - if (opts.itemSelector == "tr") { - list.draggedItem.children().each(function() { $(this).width($(this).width()); }); - list.placeHolderItem = list.draggedItem.clone().attr("data-placeHolder", true); - list.draggedItem.after(list.placeHolderItem); - list.placeHolderItem.children().each(function() { $(this).css({ borderWidth:0, width: $(this).width() + 1, height: $(this).height() + 1 }).html(" "); }); - } else { - list.draggedItem.after(opts.placeHolderTemplate); - list.placeHolderItem = list.draggedItem.next().css({ height: h, width: w }).attr("data-placeHolder", true); - } - list.draggedItem.css({ position: "absolute", opacity: 0.8, "z-index": 999, height: h, width: w }); - - $(lists).each(function(i, l) { l.createDropTargets(); l.buildPositionTable(); }); - - list.scroll = { moveX: 0, moveY: 0, maxX: $(document).width() - $(window).width(), maxY: $(document).height() - $(window).height() }; - list.scroll.scrollY = window.setInterval(function() { - if (opts.scrollContainer != window) { - $(opts.scrollContainer).scrollTop($(opts.scrollContainer).scrollTop() + list.scroll.moveY); - return; - } - var t = $(opts.scrollContainer).scrollTop(); - if (list.scroll.moveY > 0 && t < list.scroll.maxY || list.scroll.moveY < 0 && t > 0) { - $(opts.scrollContainer).scrollTop(t + list.scroll.moveY); - list.draggedItem.css("top", list.draggedItem.offset().top + list.scroll.moveY + 1); - } - }, 10); - list.scroll.scrollX = window.setInterval(function() { - if (opts.scrollContainer != window) { - $(opts.scrollContainer).scrollLeft($(opts.scrollContainer).scrollLeft() + list.scroll.moveX); - return; - } - var l = $(opts.scrollContainer).scrollLeft(); - if (list.scroll.moveX > 0 && l < list.scroll.maxX || list.scroll.moveX < 0 && l > 0) { - $(opts.scrollContainer).scrollLeft(l + list.scroll.moveX); - list.draggedItem.css("left", list.draggedItem.offset().left + list.scroll.moveX + 1); - } - }, 10); - - list.setPos(e.pageX, e.pageY); - $(document).bind("selectstart", list.stopBubble); //stop ie text selection - $(document).bind("mousemove", list.swapItems); - $(document).bind("mouseup", list.dropItem); - if (opts.scrollContainer != window) - $(window).bind("DOMMouseScroll mousewheel", list.wheel); - return false; //stop moz text selection - }, - - setPos: function(x, y) { - var top = y - this.offset.top; - var left = x - this.offset.left; - - if (!opts.dragBetween) { - top = Math.min(this.offsetLimit.bottom, Math.max(top, this.offsetLimit.top)); - left = Math.min(this.offsetLimit.right, Math.max(left, this.offsetLimit.left)); - } - - this.draggedItem.parents().each(function() { - if ($(this).css("position") != "static" && (!$.browser.mozilla || $(this).css("display") != "table")) { - var offset = $(this).offset(); - top -= offset.top; - left -= offset.left; - return false; - } - }); - - if (opts.scrollContainer == window) { - y -= $(window).scrollTop(); - x -= $(window).scrollLeft(); - y = Math.max(0, y - $(window).height() + 5) + Math.min(0, y - 5); - x = Math.max(0, x - $(window).width() + 5) + Math.min(0, x - 5); - } else { - var cont = $(opts.scrollContainer); - var offset = cont.offset(); - y = Math.max(0, y - cont.height() - offset.top) + Math.min(0, y - offset.top); - x = Math.max(0, x - cont.width() - offset.left) + Math.min(0, x - offset.left); - } - - list.scroll.moveX = x == 0 ? 0 : x * opts.scrollSpeed / Math.abs(x); - list.scroll.moveY = y == 0 ? 0 : y * opts.scrollSpeed / Math.abs(y); - - this.draggedItem.css({ top: top, left: left }); - }, - - wheel: function(e) { - if (($.browser.safari || $.browser.mozilla) && list && opts.scrollContainer != window) { - var cont = $(opts.scrollContainer); - var offset = cont.offset(); - if (e.pageX > offset.left && e.pageX < offset.left + cont.width() && e.pageY > offset.top && e.pageY < offset.top + cont.height()) { - var delta = e.detail ? e.detail * 5 : e.wheelDelta / -2; - cont.scrollTop(cont.scrollTop() + delta); - e.preventDefault(); - } - } - }, - - buildPositionTable: function() { - var item = this.draggedItem == null ? null : this.draggedItem.get(0); - var pos = []; - $(this.container).children(opts.itemSelector).each(function(i, elm) { - if (elm != item) { - var loc = $(elm).offset(); - loc.right = loc.left + $(elm).width(); - loc.bottom = loc.top + $(elm).height(); - loc.elm = elm; - pos.push(loc); - } - }); - this.pos = pos; - }, - - dropItem: function() { - if (list.draggedItem == null) - return; - - list.placeHolderItem.before(list.draggedItem); - - //list.draggedItem.attr("style", "") doesn't work on IE8 and jQuery 1.5 or lower - //list.draggedItem.removeAttr("style") doesn't work on chrome and jQuery 1.6 (works jQuery 1.5 or lower) - var orig = list.draggedItem.attr("data-origStyle"); - list.draggedItem.attr("style", orig); - if (orig == "") - list.draggedItem.removeAttr("style"); - list.draggedItem.removeAttr("data-origStyle"); - list.placeHolderItem.remove(); - - $("[data-dropTarget]").remove(); - - window.clearInterval(list.scroll.scrollY); - window.clearInterval(list.scroll.scrollX); - - var changed = false; - $(lists).each(function() { - $(this.container).children(opts.itemSelector).each(function(j) { - if (parseInt($(this).attr("data-itemIdx")) != j) { - changed = true; - $(this).attr("data-itemIdx", j); - } - }); - }); - - var duration = +new Date() - list.draggedItem.startTime - , offset = list.draggedItem.offset() - , diffY = Math.abs(list.draggedItem.startOffset.top - offset.top) - , diffX = Math.abs(list.draggedItem.startOffset.left - offset.left) - , itemClicked = duration < 500 && diffX < 3 && diffY < 3 - - if (changed) - opts.dragEnd.apply(list.draggedItem); - else if (itemClicked) - opts.itemClicked(list.draggedItem) - - list.draggedItem = null; - $(document).unbind("selectstart", list.stopBubble); - $(document).unbind("mousemove", list.swapItems); - $(document).unbind("mouseup", list.dropItem); - if (opts.scrollContainer != window) - $(window).unbind("DOMMouseScroll mousewheel", list.wheel); - return false; - }, - - stopBubble: function() { return false; }, - - swapItems: function(e) { - if (list.draggedItem == null) - return false; - - list.setPos(e.pageX, e.pageY); - - var ei = list.findPos(e.pageX, e.pageY); - var nlist = list; - for (var i = 0; ei == -1 && opts.dragBetween && i < lists.length; i++) { - ei = lists[i].findPos(e.pageX, e.pageY); - nlist = lists[i]; - } - - if (ei == -1 || $(nlist.pos[ei].elm).attr("data-placeHolder")) - return false; - - if (lastPos == null || lastPos.top > list.draggedItem.offset().top || lastPos.left > list.draggedItem.offset().left) - $(nlist.pos[ei].elm).before(list.placeHolderItem); - else - $(nlist.pos[ei].elm).after(list.placeHolderItem); - - $(lists).each(function(i, l) { l.createDropTargets(); l.buildPositionTable(); }); - lastPos = list.draggedItem.offset(); - return false; - }, - - findPos: function(x, y) { - for (var i = 0; i < this.pos.length; i++) { - if (this.pos[i].left < x && this.pos[i].right > x && this.pos[i].top < y && this.pos[i].bottom > y) - return i; - } - return -1; - }, - - createDropTargets: function() { - if (!opts.dragBetween) - return; - - $(lists).each(function() { - var ph = $(this.container).find("[data-placeHolder]"); - var dt = $(this.container).find("[data-dropTarget]"); - if (ph.size() > 0 && dt.size() > 0) - dt.remove(); - else if (ph.size() == 0 && dt.size() == 0) { - //list.placeHolderItem.clone().removeAttr("data-placeHolder") crashes in IE7 and jquery 1.5.1 (doesn't in jquery 1.4.2 or IE8) - $(this.container).append(list.placeHolderItem.removeAttr("data-placeHolder").clone().attr("data-dropTarget", true)); - list.placeHolderItem.attr("data-placeHolder", true); - } - }); - } - }; - - newList.init(); - lists.push(newList); - }); - - return this; - }; - - $.fn.dragsort.defaults = { - itemClicked: function() { }, - itemSelector: "li", - dragSelector: "li", - dragSelectorExclude: "input, textarea, a[href]", - dragEnd: function() { }, - dragBetween: false, - placeHolderTemplate: "
  •  
  • ", - scrollContainer: window, - scrollSpeed: 5 - }; - -})(jQuery); diff --git a/public/js/jquery.lightbox-0.5.js b/public/js/jquery.lightbox-0.5.js deleted file mode 100644 index 5d68a6e..0000000 --- a/public/js/jquery.lightbox-0.5.js +++ /dev/null @@ -1,472 +0,0 @@ -/** - * jQuery lightBox plugin - * This jQuery plugin was inspired and based on Lightbox 2 by Lokesh Dhakar (http://www.huddletogether.com/projects/lightbox2/) - * and adapted to me for use like a plugin from jQuery. - * @name jquery-lightbox-0.5.js - * @author Leandro Vieira Pinho - http://leandrovieira.com - * @version 0.5 - * @date April 11, 2008 - * @category jQuery plugin - * @copyright (c) 2008 Leandro Vieira Pinho (leandrovieira.com) - * @license CCAttribution-ShareAlike 2.5 Brazil - http://creativecommons.org/licenses/by-sa/2.5/br/deed.en_US - * @example Visit http://leandrovieira.com/projects/jquery/lightbox/ for more informations about this jQuery plugin - */ - -// Offering a Custom Alias suport - More info: http://docs.jquery.com/Plugins/Authoring#Custom_Alias -(function($) { - /** - * $ is an alias to jQuery object - * - */ - $.fn.lightBox = function(settings) { - // Settings to configure the jQuery lightBox plugin how you like - settings = jQuery.extend({ - // Configuration related to overlay - overlayBgColor: '#000', // (string) Background color to overlay; inform a hexadecimal value like: #RRGGBB. Where RR, GG, and BB are the hexadecimal values for the red, green, and blue values of the color. - overlayOpacity: 0.8, // (integer) Opacity value to overlay; inform: 0.X. Where X are number from 0 to 9 - // Configuration related to navigation - fixedNavigation: false, // (boolean) Boolean that informs if the navigation (next and prev button) will be fixed or not in the interface. - // Configuration related to images - imageLoading: '/images/lightbox-ico-loading.gif', // (string) Path and the name of the loading icon - imageBtnPrev: '/images/lightbox-btn-prev.gif', // (string) Path and the name of the prev button image - imageBtnNext: '/images/lightbox-btn-next.gif', // (string) Path and the name of the next button image - imageBtnClose: '/images/lightbox-btn-close.gif', // (string) Path and the name of the close btn - imageBlank: '/images/lightbox-blank.gif', // (string) Path and the name of a blank image (one pixel) - // Configuration related to container image box - containerBorderSize: 10, // (integer) If you adjust the padding in the CSS for the container, #lightbox-container-image-box, you will need to update this value - containerResizeSpeed: 400, // (integer) Specify the resize duration of container image. These number are miliseconds. 400 is default. - // Configuration related to texts in caption. For example: Image 2 of 8. You can alter either "Image" and "of" texts. - txtImage: 'Image', // (string) Specify text "Image" - txtOf: 'of', // (string) Specify text "of" - // Configuration related to keyboard navigation - keyToClose: 'c', // (string) (c = close) Letter to close the jQuery lightBox interface. Beyond this letter, the letter X and the SCAPE key is used to. - keyToPrev: 'p', // (string) (p = previous) Letter to show the previous image - keyToNext: 'n', // (string) (n = next) Letter to show the next image. - // Don´t alter these variables in any way - imageArray: [], - activeImage: 0 - },settings); - // Caching the jQuery object with all elements matched - var jQueryMatchedObj = this; // This, in this context, refer to jQuery object - /** - * Initializing the plugin calling the start function - * - * @return boolean false - */ - function _initialize() { - _start(this,jQueryMatchedObj); // This, in this context, refer to object (link) which the user have clicked - return false; // Avoid the browser following the link - } - /** - * Start the jQuery lightBox plugin - * - * @param object objClicked The object (link) whick the user have clicked - * @param object jQueryMatchedObj The jQuery object with all elements matched - */ - function _start(objClicked,jQueryMatchedObj) { - // Hime some elements to avoid conflict with overlay in IE. These elements appear above the overlay. - $('embed, object, select').css({ 'visibility' : 'hidden' }); - // Call the function to create the markup structure; style some elements; assign events in some elements. - _set_interface(); - // Unset total images in imageArray - settings.imageArray.length = 0; - // Unset image active information - settings.activeImage = 0; - // We have an image set? Or just an image? Let´s see it. - if ( jQueryMatchedObj.length == 1 ) { - settings.imageArray.push(new Array(objClicked.getAttribute('href'),objClicked.getAttribute('title'))); - } else { - // Add an Array (as many as we have), with href and title atributes, inside the Array that storage the images references - for ( var i = 0; i < jQueryMatchedObj.length; i++ ) { - settings.imageArray.push(new Array(jQueryMatchedObj[i].getAttribute('href'),jQueryMatchedObj[i].getAttribute('title'))); - } - } - while ( settings.imageArray[settings.activeImage][0] != objClicked.getAttribute('href') ) { - settings.activeImage++; - } - // Call the function that prepares image exibition - _set_image_to_view(); - } - /** - * Create the jQuery lightBox plugin interface - * - * The HTML markup will be like that: -
    -
    - - -
    - * - */ - function _set_interface() { - // Apply the HTML markup into body tag - $('body').append('
    '); - // Get page sizes - var arrPageSizes = ___getPageSize(); - // Style overlay and show it - $('#jquery-overlay').css({ - backgroundColor: settings.overlayBgColor, - opacity: settings.overlayOpacity, - width: arrPageSizes[0], - height: arrPageSizes[1] - }).fadeIn(); - // Get page scroll - var arrPageScroll = ___getPageScroll(); - // Calculate top and left offset for the jquery-lightbox div object and show it - $('#jquery-lightbox').css({ - top: arrPageScroll[1] + (arrPageSizes[3] / 10), - left: arrPageScroll[0] - }).show(); - // Assigning click events in elements to close overlay - $('#jquery-overlay,#jquery-lightbox').click(function() { - _finish(); - }); - // Assign the _finish function to lightbox-loading-link and lightbox-secNav-btnClose objects - $('#lightbox-loading-link,#lightbox-secNav-btnClose').click(function() { - _finish(); - return false; - }); - // If window was resized, calculate the new overlay dimensions - $(window).resize(function() { - // Get page sizes - var arrPageSizes = ___getPageSize(); - // Style overlay and show it - $('#jquery-overlay').css({ - width: arrPageSizes[0], - height: arrPageSizes[1] - }); - // Get page scroll - var arrPageScroll = ___getPageScroll(); - // Calculate top and left offset for the jquery-lightbox div object and show it - $('#jquery-lightbox').css({ - top: arrPageScroll[1] + (arrPageSizes[3] / 10), - left: arrPageScroll[0] - }); - }); - } - /** - * Prepares image exibition; doing a image´s preloader to calculate it´s size - * - */ - function _set_image_to_view() { // show the loading - // Show the loading - $('#lightbox-loading').show(); - if ( settings.fixedNavigation ) { - $('#lightbox-image,#lightbox-container-image-data-box,#lightbox-image-details-currentNumber').hide(); - } else { - // Hide some elements - $('#lightbox-image,#lightbox-nav,#lightbox-nav-btnPrev,#lightbox-nav-btnNext,#lightbox-container-image-data-box,#lightbox-image-details-currentNumber').hide(); - } - // Image preload process - var objImagePreloader = new Image(); - objImagePreloader.onload = function() { - $('#lightbox-image').attr('src',settings.imageArray[settings.activeImage][0]); - // Perfomance an effect in the image container resizing it - _resize_container_image_box(objImagePreloader.width,objImagePreloader.height); - // clear onLoad, IE behaves irratically with animated gifs otherwise - objImagePreloader.onload=function(){}; - }; - objImagePreloader.src = settings.imageArray[settings.activeImage][0]; - }; - /** - * Perfomance an effect in the image container resizing it - * - * @param integer intImageWidth The image´s width that will be showed - * @param integer intImageHeight The image´s height that will be showed - */ - function _resize_container_image_box(intImageWidth,intImageHeight) { - // Get current width and height - var intCurrentWidth = $('#lightbox-container-image-box').width(); - var intCurrentHeight = $('#lightbox-container-image-box').height(); - // Get the width and height of the selected image plus the padding - var intWidth = (intImageWidth + (settings.containerBorderSize * 2)); // Plus the image´s width and the left and right padding value - var intHeight = (intImageHeight + (settings.containerBorderSize * 2)); // Plus the image´s height and the left and right padding value - // Diferences - var intDiffW = intCurrentWidth - intWidth; - var intDiffH = intCurrentHeight - intHeight; - // Perfomance the effect - $('#lightbox-container-image-box').animate({ width: intWidth, height: intHeight },settings.containerResizeSpeed,function() { _show_image(); }); - if ( ( intDiffW == 0 ) && ( intDiffH == 0 ) ) { - if ( $.browser.msie ) { - ___pause(250); - } else { - ___pause(100); - } - } - $('#lightbox-container-image-data-box').css({ width: intImageWidth }); - $('#lightbox-nav-btnPrev,#lightbox-nav-btnNext').css({ height: intImageHeight + (settings.containerBorderSize * 2) }); - }; - /** - * Show the prepared image - * - */ - function _show_image() { - $('#lightbox-loading').hide(); - $('#lightbox-image').fadeIn(function() { - _show_image_data(); - _set_navigation(); - }); - _preload_neighbor_images(); - }; - /** - * Show the image information - * - */ - function _show_image_data() { - $('#lightbox-container-image-data-box').slideDown('fast'); - $('#lightbox-image-details-caption').hide(); - if ( settings.imageArray[settings.activeImage][1] ) { - $('#lightbox-image-details-caption').html(settings.imageArray[settings.activeImage][1]).show(); - } - // If we have a image set, display 'Image X of X' - if ( settings.imageArray.length > 1 ) { - $('#lightbox-image-details-currentNumber').html(settings.txtImage + ' ' + ( settings.activeImage + 1 ) + ' ' + settings.txtOf + ' ' + settings.imageArray.length).show(); - } - } - /** - * Display the button navigations - * - */ - function _set_navigation() { - $('#lightbox-nav').show(); - - // Instead to define this configuration in CSS file, we define here. And it´s need to IE. Just. - $('#lightbox-nav-btnPrev,#lightbox-nav-btnNext').css({ 'background' : 'transparent url(' + settings.imageBlank + ') no-repeat' }); - - // Show the prev button, if not the first image in set - if ( settings.activeImage != 0 ) { - if ( settings.fixedNavigation ) { - $('#lightbox-nav-btnPrev').css({ 'background' : 'url(' + settings.imageBtnPrev + ') left 15% no-repeat' }) - .unbind() - .bind('click',function() { - settings.activeImage = settings.activeImage - 1; - _set_image_to_view(); - return false; - }); - } else { - // Show the images button for Next buttons - $('#lightbox-nav-btnPrev').unbind().hover(function() { - $(this).css({ 'background' : 'url(' + settings.imageBtnPrev + ') left 15% no-repeat' }); - },function() { - $(this).css({ 'background' : 'transparent url(' + settings.imageBlank + ') no-repeat' }); - }).show().bind('click',function() { - settings.activeImage = settings.activeImage - 1; - _set_image_to_view(); - return false; - }); - } - } - - // Show the next button, if not the last image in set - if ( settings.activeImage != ( settings.imageArray.length -1 ) ) { - if ( settings.fixedNavigation ) { - $('#lightbox-nav-btnNext').css({ 'background' : 'url(' + settings.imageBtnNext + ') right 15% no-repeat' }) - .unbind() - .bind('click',function() { - settings.activeImage = settings.activeImage + 1; - _set_image_to_view(); - return false; - }); - } else { - // Show the images button for Next buttons - $('#lightbox-nav-btnNext').unbind().hover(function() { - $(this).css({ 'background' : 'url(' + settings.imageBtnNext + ') right 15% no-repeat' }); - },function() { - $(this).css({ 'background' : 'transparent url(' + settings.imageBlank + ') no-repeat' }); - }).show().bind('click',function() { - settings.activeImage = settings.activeImage + 1; - _set_image_to_view(); - return false; - }); - } - } - // Enable keyboard navigation - _enable_keyboard_navigation(); - } - /** - * Enable a support to keyboard navigation - * - */ - function _enable_keyboard_navigation() { - $(document).keydown(function(objEvent) { - _keyboard_action(objEvent); - }); - } - /** - * Disable the support to keyboard navigation - * - */ - function _disable_keyboard_navigation() { - $(document).unbind(); - } - /** - * Perform the keyboard actions - * - */ - function _keyboard_action(objEvent) { - // To ie - if ( objEvent == null ) { - keycode = event.keyCode; - escapeKey = 27; - // To Mozilla - } else { - keycode = objEvent.keyCode; - escapeKey = objEvent.DOM_VK_ESCAPE; - } - // Get the key in lower case form - key = String.fromCharCode(keycode).toLowerCase(); - // Verify the keys to close the ligthBox - if ( ( key == settings.keyToClose ) || ( key == 'x' ) || ( keycode == escapeKey ) ) { - _finish(); - } - // Verify the key to show the previous image - if ( ( key == settings.keyToPrev ) || ( keycode == 37 ) ) { - // If we´re not showing the first image, call the previous - if ( settings.activeImage != 0 ) { - settings.activeImage = settings.activeImage - 1; - _set_image_to_view(); - _disable_keyboard_navigation(); - } - } - // Verify the key to show the next image - if ( ( key == settings.keyToNext ) || ( keycode == 39 ) ) { - // If we´re not showing the last image, call the next - if ( settings.activeImage != ( settings.imageArray.length - 1 ) ) { - settings.activeImage = settings.activeImage + 1; - _set_image_to_view(); - _disable_keyboard_navigation(); - } - } - } - /** - * Preload prev and next images being showed - * - */ - function _preload_neighbor_images() { - if ( (settings.imageArray.length -1) > settings.activeImage ) { - objNext = new Image(); - objNext.src = settings.imageArray[settings.activeImage + 1][0]; - } - if ( settings.activeImage > 0 ) { - objPrev = new Image(); - objPrev.src = settings.imageArray[settings.activeImage -1][0]; - } - } - /** - * Remove jQuery lightBox plugin HTML markup - * - */ - function _finish() { - $('#jquery-lightbox').remove(); - $('#jquery-overlay').fadeOut(function() { $('#jquery-overlay').remove(); }); - // Show some elements to avoid conflict with overlay in IE. These elements appear above the overlay. - $('embed, object, select').css({ 'visibility' : 'visible' }); - } - /** - / THIRD FUNCTION - * getPageSize() by quirksmode.com - * - * @return Array Return an array with page width, height and window width, height - */ - function ___getPageSize() { - var xScroll, yScroll; - if (window.innerHeight && window.scrollMaxY) { - xScroll = window.innerWidth + window.scrollMaxX; - yScroll = window.innerHeight + window.scrollMaxY; - } else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac - xScroll = document.body.scrollWidth; - yScroll = document.body.scrollHeight; - } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari - xScroll = document.body.offsetWidth; - yScroll = document.body.offsetHeight; - } - var windowWidth, windowHeight; - if (self.innerHeight) { // all except Explorer - if(document.documentElement.clientWidth){ - windowWidth = document.documentElement.clientWidth; - } else { - windowWidth = self.innerWidth; - } - windowHeight = self.innerHeight; - } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode - windowWidth = document.documentElement.clientWidth; - windowHeight = document.documentElement.clientHeight; - } else if (document.body) { // other Explorers - windowWidth = document.body.clientWidth; - windowHeight = document.body.clientHeight; - } - // for small pages with total height less then height of the viewport - if(yScroll < windowHeight){ - pageHeight = windowHeight; - } else { - pageHeight = yScroll; - } - // for small pages with total width less then width of the viewport - if(xScroll < windowWidth){ - pageWidth = xScroll; - } else { - pageWidth = windowWidth; - } - arrayPageSize = new Array(pageWidth,pageHeight,windowWidth,windowHeight); - return arrayPageSize; - }; - /** - / THIRD FUNCTION - * getPageScroll() by quirksmode.com - * - * @return Array Return an array with x,y page scroll values. - */ - function ___getPageScroll() { - var xScroll, yScroll; - if (self.pageYOffset) { - yScroll = self.pageYOffset; - xScroll = self.pageXOffset; - } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict - yScroll = document.documentElement.scrollTop; - xScroll = document.documentElement.scrollLeft; - } else if (document.body) {// all other Explorers - yScroll = document.body.scrollTop; - xScroll = document.body.scrollLeft; - } - arrayPageScroll = new Array(xScroll,yScroll); - return arrayPageScroll; - }; - /** - * Stop the code execution from a escified time in milisecond - * - */ - function ___pause(ms) { - var date = new Date(); - curDate = null; - do { var curDate = new Date(); } - while ( curDate - date < ms); - }; - // Return the jQuery object for chaining. The unbind method is used to avoid click conflict when the plugin is called more than once - return this.unbind('click').click(_initialize); - }; -})(jQuery); // Call and execute the function immediately passing the jQuery object \ No newline at end of file diff --git a/public/js/jquery.uploadify.v2.1.4.js b/public/js/jquery.uploadify.v2.1.4.js deleted file mode 100644 index 2d20a43..0000000 --- a/public/js/jquery.uploadify.v2.1.4.js +++ /dev/null @@ -1,296 +0,0 @@ -/* -Uploadify v2.1.4 -Release Date: November 8, 2010 - -Copyright (c) 2010 Ronnie Garcia, Travis Nickels - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -if(jQuery)( - function(jQuery){ - jQuery.extend(jQuery.fn,{ - uploadify:function(options) { - jQuery(this).each(function(){ - var settings = jQuery.extend({ - id : jQuery(this).attr('id'), // The ID of the object being Uploadified - uploader : 'uploadify.swf', // The path to the uploadify swf file - script : 'uploadify.php', // The path to the uploadify backend upload script - expressInstall : null, // The path to the express install swf file - folder : '', // The path to the upload folder - height : 30, // The height of the flash button - width : 120, // The width of the flash button - cancelImg : 'cancel.png', // The path to the cancel image for the default file queue item container - wmode : 'opaque', // The wmode of the flash file - scriptAccess : 'sameDomain', // Set to "always" to allow script access across domains - fileDataName : 'Filedata', // The name of the file collection object in the backend upload script - method : 'POST', // The method for sending variables to the backend upload script - queueSizeLimit : 999, // The maximum size of the file queue - simUploadLimit : 1, // The number of simultaneous uploads allowed - queueID : false, // The optional ID of the queue container - displayData : 'percentage', // Set to "speed" to show the upload speed in the default queue item - removeCompleted : true, // Set to true if you want the queue items to be removed when a file is done uploading - onInit : function() {}, // Function to run when uploadify is initialized - onSelect : function() {}, // Function to run when a file is selected - onSelectOnce : function() {}, // Function to run once when files are added to the queue - onQueueFull : function() {}, // Function to run when the queue reaches capacity - onCheck : function() {}, // Function to run when script checks for duplicate files on the server - onCancel : function() {}, // Function to run when an item is cleared from the queue - onClearQueue : function() {}, // Function to run when the queue is manually cleared - onError : function() {}, // Function to run when an upload item returns an error - onProgress : function() {}, // Function to run each time the upload progress is updated - onComplete : function() {}, // Function to run when an upload is completed - onAllComplete : function() {} // Function to run when all uploads are completed - }, options); - jQuery(this).data('settings',settings); - var pagePath = location.pathname; - pagePath = pagePath.split('/'); - pagePath.pop(); - pagePath = pagePath.join('/') + '/'; - var data = {}; - data.uploadifyID = settings.id; - data.pagepath = pagePath; - if (settings.buttonImg) data.buttonImg = escape(settings.buttonImg); - if (settings.buttonText) data.buttonText = escape(settings.buttonText); - if (settings.rollover) data.rollover = true; - data.script = settings.script; - data.folder = escape(settings.folder); - if (settings.scriptData) { - var scriptDataString = ''; - for (var name in settings.scriptData) { - scriptDataString += '&' + name + '=' + settings.scriptData[name]; - } - data.scriptData = escape(scriptDataString.substr(1)); - } - data.width = settings.width; - data.height = settings.height; - data.wmode = settings.wmode; - data.method = settings.method; - data.queueSizeLimit = settings.queueSizeLimit; - data.simUploadLimit = settings.simUploadLimit; - if (settings.hideButton) data.hideButton = true; - if (settings.fileDesc) data.fileDesc = settings.fileDesc; - if (settings.fileExt) data.fileExt = settings.fileExt; - if (settings.multi) data.multi = true; - if (settings.auto) data.auto = true; - if (settings.sizeLimit) data.sizeLimit = settings.sizeLimit; - if (settings.checkScript) data.checkScript = settings.checkScript; - if (settings.fileDataName) data.fileDataName = settings.fileDataName; - if (settings.queueID) data.queueID = settings.queueID; - if (settings.onInit() !== false) { - jQuery(this).css('display','none'); - jQuery(this).after('
    '); - swfobject.embedSWF(settings.uploader, settings.id + 'Uploader', settings.width, settings.height, '9.0.24', settings.expressInstall, data, {'quality':'high','wmode':settings.wmode,'allowScriptAccess':settings.scriptAccess},{},function(event) { - if (typeof(settings.onSWFReady) == 'function' && event.success) settings.onSWFReady(); - }); - if (settings.queueID == false) { - jQuery("#" + jQuery(this).attr('id') + "Uploader").after('
    '); - } else { - jQuery("#" + settings.queueID).addClass('uploadifyQueue'); - } - } - if (typeof(settings.onOpen) == 'function') { - jQuery(this).bind("uploadifyOpen", settings.onOpen); - } - jQuery(this).bind("uploadifySelect", {'action': settings.onSelect, 'queueID': settings.queueID}, function(event, ID, fileObj) { - if (event.data.action(event, ID, fileObj) !== false) { - var byteSize = Math.round(fileObj.size / 1024 * 100) * .01; - var suffix = 'KB'; - if (byteSize > 1000) { - byteSize = Math.round(byteSize *.001 * 100) * .01; - suffix = 'MB'; - } - var sizeParts = byteSize.toString().split('.'); - if (sizeParts.length > 1) { - byteSize = sizeParts[0] + '.' + sizeParts[1].substr(0,2); - } else { - byteSize = sizeParts[0]; - } - if (fileObj.name.length > 20) { - fileName = fileObj.name.substr(0,20) + '...'; - } else { - fileName = fileObj.name; - } - queue = '#' + jQuery(this).attr('id') + 'Queue'; - if (event.data.queueID) { - queue = '#' + event.data.queueID; - } - jQuery(queue).append('
    \ -
    \ - \ -
    \ - ' + fileName + ' (' + byteSize + suffix + ')\ -
    \ -
    \ -
    \ -
    '); - } - }); - jQuery(this).bind("uploadifySelectOnce", {'action': settings.onSelectOnce}, function(event, data) { - event.data.action(event, data); - if (settings.auto) { - if (settings.checkScript) { - jQuery(this).uploadifyUpload(null, false); - } else { - jQuery(this).uploadifyUpload(null, true); - } - } - }); - jQuery(this).bind("uploadifyQueueFull", {'action': settings.onQueueFull}, function(event, queueSizeLimit) { - if (event.data.action(event, queueSizeLimit) !== false) { - alert('The queue is full. The max size is ' + queueSizeLimit + '.'); - } - }); - jQuery(this).bind("uploadifyCheckExist", {'action': settings.onCheck}, function(event, checkScript, fileQueueObj, folder, single) { - var postData = new Object(); - postData = fileQueueObj; - postData.folder = (folder.substr(0,1) == '/') ? folder : pagePath + folder; - if (single) { - for (var ID in fileQueueObj) { - var singleFileID = ID; - } - } - jQuery.post(checkScript, postData, function(data) { - for(var key in data) { - if (event.data.action(event, data, key) !== false) { - var replaceFile = confirm("Do you want to replace the file " + data[key] + "?"); - if (!replaceFile) { - document.getElementById(jQuery(event.target).attr('id') + 'Uploader').cancelFileUpload(key,true,true); - } - } - } - if (single) { - document.getElementById(jQuery(event.target).attr('id') + 'Uploader').startFileUpload(singleFileID, true); - } else { - document.getElementById(jQuery(event.target).attr('id') + 'Uploader').startFileUpload(null, true); - } - }, "json"); - }); - jQuery(this).bind("uploadifyCancel", {'action': settings.onCancel}, function(event, ID, fileObj, data, remove, clearFast) { - if (event.data.action(event, ID, fileObj, data, clearFast) !== false) { - if (remove) { - var fadeSpeed = (clearFast == true) ? 0 : 250; - jQuery("#" + jQuery(this).attr('id') + ID).fadeOut(fadeSpeed, function() { jQuery(this).remove() }); - } - } - }); - jQuery(this).bind("uploadifyClearQueue", {'action': settings.onClearQueue}, function(event, clearFast) { - var queueID = (settings.queueID) ? settings.queueID : jQuery(this).attr('id') + 'Queue'; - if (clearFast) { - jQuery("#" + queueID).find('.uploadifyQueueItem').remove(); - } - if (event.data.action(event, clearFast) !== false) { - jQuery("#" + queueID).find('.uploadifyQueueItem').each(function() { - var index = jQuery('.uploadifyQueueItem').index(this); - jQuery(this).delay(index * 100).fadeOut(250, function() { jQuery(this).remove() }); - }); - } - }); - var errorArray = []; - jQuery(this).bind("uploadifyError", {'action': settings.onError}, function(event, ID, fileObj, errorObj) { - if (event.data.action(event, ID, fileObj, errorObj) !== false) { - var fileArray = new Array(ID, fileObj, errorObj); - errorArray.push(fileArray); - jQuery("#" + jQuery(this).attr('id') + ID).find('.percentage').text(" - " + errorObj.type + " Error"); - jQuery("#" + jQuery(this).attr('id') + ID).find('.uploadifyProgress').hide(); - jQuery("#" + jQuery(this).attr('id') + ID).addClass('uploadifyError'); - } - }); - if (typeof(settings.onUpload) == 'function') { - jQuery(this).bind("uploadifyUpload", settings.onUpload); - } - jQuery(this).bind("uploadifyProgress", {'action': settings.onProgress, 'toDisplay': settings.displayData}, function(event, ID, fileObj, data) { - if (event.data.action(event, ID, fileObj, data) !== false) { - jQuery("#" + jQuery(this).attr('id') + ID + "ProgressBar").animate({'width': data.percentage + '%'},250,function() { - if (data.percentage == 100) { - jQuery(this).closest('.uploadifyProgress').fadeOut(250,function() {jQuery(this).remove()}); - } - }); - if (event.data.toDisplay == 'percentage') displayData = ' - ' + data.percentage + '%'; - if (event.data.toDisplay == 'speed') displayData = ' - ' + data.speed + 'KB/s'; - if (event.data.toDisplay == null) displayData = ' '; - jQuery("#" + jQuery(this).attr('id') + ID).find('.percentage').text(displayData); - } - }); - jQuery(this).bind("uploadifyComplete", {'action': settings.onComplete}, function(event, ID, fileObj, response, data) { - if (event.data.action(event, ID, fileObj, unescape(response), data) !== false) { - jQuery("#" + jQuery(this).attr('id') + ID).find('.percentage').text(' - Completed'); - if (settings.removeCompleted) { - jQuery("#" + jQuery(event.target).attr('id') + ID).fadeOut(250,function() {jQuery(this).remove()}); - } - jQuery("#" + jQuery(event.target).attr('id') + ID).addClass('completed'); - } - }); - if (typeof(settings.onAllComplete) == 'function') { - jQuery(this).bind("uploadifyAllComplete", {'action': settings.onAllComplete}, function(event, data) { - if (event.data.action(event, data) !== false) { - errorArray = []; - } - }); - } - }); - }, - uploadifySettings:function(settingName, settingValue, resetObject) { - var returnValue = false; - jQuery(this).each(function() { - if (settingName == 'scriptData' && settingValue != null) { - if (resetObject) { - var scriptData = settingValue; - } else { - var scriptData = jQuery.extend(jQuery(this).data('settings').scriptData, settingValue); - } - var scriptDataString = ''; - for (var name in scriptData) { - scriptDataString += '&' + name + '=' + scriptData[name]; - } - settingValue = escape(scriptDataString.substr(1)); - } - returnValue = document.getElementById(jQuery(this).attr('id') + 'Uploader').updateSettings(settingName, settingValue); - }); - if (settingValue == null) { - if (settingName == 'scriptData') { - var returnSplit = unescape(returnValue).split('&'); - var returnObj = new Object(); - for (var i = 0; i < returnSplit.length; i++) { - var iSplit = returnSplit[i].split('='); - returnObj[iSplit[0]] = iSplit[1]; - } - returnValue = returnObj; - } - } - return returnValue; - }, - uploadifyUpload:function(ID,checkComplete) { - jQuery(this).each(function() { - if (!checkComplete) checkComplete = false; - document.getElementById(jQuery(this).attr('id') + 'Uploader').startFileUpload(ID, checkComplete); - }); - }, - uploadifyCancel:function(ID) { - jQuery(this).each(function() { - document.getElementById(jQuery(this).attr('id') + 'Uploader').cancelFileUpload(ID, true, true, false); - }); - }, - uploadifyClearQueue:function() { - jQuery(this).each(function() { - document.getElementById(jQuery(this).attr('id') + 'Uploader').clearFileUploadQueue(false); - }); - } - }) -})(jQuery); \ No newline at end of file diff --git a/public/js/projects.js b/public/js/projects.js deleted file mode 100644 index 0107a80..0000000 --- a/public/js/projects.js +++ /dev/null @@ -1,5 +0,0 @@ -$(function() { - - // this space intentionally left blank - -}) diff --git a/public/js/sign-in.js b/public/js/sign-in.js index 8f6fc78..6be032e 100644 --- a/public/js/sign-in.js +++ b/public/js/sign-in.js @@ -1,7 +1,5 @@ $(function() { - $('input[name="email"]').focus() - $('#forgot-password-link').click(function() { window.location.href = '/forgot-password/' + $('#email').val() return false diff --git a/public/js/swfobject.js b/public/js/swfobject.js deleted file mode 100644 index 8eafe9d..0000000 --- a/public/js/swfobject.js +++ /dev/null @@ -1,4 +0,0 @@ -/* SWFObject v2.2 - is released under the MIT License -*/ -var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab 'on') - assert_redirected '/projects' + assert_redirected '/account' post '/sign-out' assert_redirected '/' - get '/projects' + get '/account' assert_ok # deletes remembered cookie @@ -154,7 +153,7 @@ class AccountsControllerTest < Stormy::Test::ControllerCase new_password = 'new password' post '/account/reset-password', { 'password' => new_password } - assert_redirected '/projects' + assert_redirected '/account' assert_equal @existing_account.id, Account.check_password(@existing_account.email, new_password) assert Account.fetch(@existing_account.id).password == new_password @@ -256,7 +255,7 @@ class AccountsControllerTest < Stormy::Test::ControllerCase def test_verify_email get "/account/verify/#{@existing_account.email}/#{@existing_account.email_verification_token}" assert_redirected '/account' - assert_nil Account.fetch(@existing_account.id).email_verification_token + assert redis.hget(@existing_account.id, 'email_verification_token').blank? end def test_verify_email_with_invalid_token_signed_in diff --git a/test/controllers/test-admin_controller.rb b/test/controllers/test-admin_controller.rb index 9fb0736..2ccbb41 100644 --- a/test/controllers/test-admin_controller.rb +++ b/test/controllers/test-admin_controller.rb @@ -7,89 +7,33 @@ require 'common' class AdminControllerTest < Stormy::Test::ControllerCase include Stormy::Test::Helpers::Accounts - include Stormy::Test::Helpers::Projects include Stormy::Helpers::Admin include Stormy::Helpers::FAQ include Stormy::Helpers::Utils - def admins - @admins ||= fixtures('admins') - end - def setup - @existing_admin_data = admins['sami'] - @existing_admin = Admin.create(@existing_admin_data) - - @admin_data = admins['freddy'] + setup_accounts end def teardown - post '/admin/sign-out' - Admin.list_ids.each do |id| - Admin.delete!(id) - end + post '/sign-out' + teardown_accounts end - def sign_in(admin = @existing_admin_data) - post '/admin/sign-in', admin + def sign_in(admin = @existing_account_data) + post '/sign-in', admin end - ##################### - ### Sign In & Out ### - ##################### - - def test_sign_in - get '/admin/sign-in' - assert_ok - end - - def test_sign_in_submit - sign_in - assert_redirected '/admin' - end - - def test_sign_in_with_invalid_credentials - sign_in(@admin_data) - assert_redirected '/admin/sign-in' - end - - def test_sign_in_redirect - sign_in - assert_redirected '/admin' - end - - def test_sign_out - post '/admin/sign-out' - assert_redirected '/admin' - end - - - ############################ - ### Dashboard & Password ### - ############################ + ################# + ### Dashboard ### + ################# def test_dashboard sign_in get '/admin' assert_ok - assert last_response.body.match(/Dashboard/) - end - - def test_change_password - sign_in - get '/admin/password' - assert_ok - - new_password = 'new password' - post '/admin/password', { 'password' => new_password, 'password_confirmation' => new_password } - assert_redirected '/admin' - @existing_admin.reload! - assert @existing_admin.password == new_password - - # incorrect confirmation - post '/admin/password', { 'password' => new_password, 'password_confirmation' => 'oops' } - assert_redirected '/admin/password' + assert last_response.body.match(/<title>[^<]*Dashboard[^<]*<\/title>/) end @@ -104,7 +48,6 @@ class AdminControllerTest < Stormy::Test::ControllerCase end def test_account - setup_accounts sign_in get '/admin/account/' + @existing_account.email @@ -113,12 +56,9 @@ class AdminControllerTest < Stormy::Test::ControllerCase get '/admin/account/not@an.account' # this was the previous listing, kind of weird but meh assert_redirected '/admin/account/' + @existing_account.email - - teardown_accounts end def test_update_account - setup_accounts sign_in # redirected to proper page when changing email addresses @@ -162,94 +102,34 @@ class AdminControllerTest < Stormy::Test::ControllerCase assert_equal 'Samson', @existing_account.first_name assert_equal 'Simpson', @existing_account.last_name assert_equal '+12501234567', @existing_account.phone - - teardown_accounts end def test_sign_in_as_user - setup_accounts sign_in get '/admin/sign-in-as/' + @existing_account.email assert_equal @existing_account.id, session[:id] - assert_redirected '/projects' - - teardown_accounts + assert_redirected '/account' end def test_delete_account - setup_accounts - setup_projects sign_in # make sure the last listing is marked so we are redirected correctly get '/admin/accounts' assert_ok - get "/admin/account/#{@existing_account.email}/delete" + @other_account = Account.create(@account_data) + get "/admin/account/#{@other_account.email}/delete" assert_redirected '/admin/accounts' - assert_nil Account.fetch(@existing_account.id) - assert_nil Project.fetch(@existing_project.id) + assert_nil Account.fetch(@other_account.id) # non-existent accounts are already gone, so no problem get "/admin/account/nobody@nowhere.net/delete" # this time the last listing was not marked, so we are redirected to the dashboard assert_redirected '/admin' - - teardown_projects - teardown_accounts - end - - - ################ - ### Projects ### - ################ - - def test_projects - setup_accounts - setup_projects - sign_in - - get '/admin/projects' - assert_ok - - teardown_projects - teardown_accounts - end - - def test_project - setup_accounts - setup_projects - sign_in - - # non-existent project - get '/admin/project/999' - assert_redirected '/admin' - - # existing project - get '/admin/project/' + @existing_project.id - assert_ok - - teardown_projects - teardown_accounts - end - - def test_delete_project - setup_accounts - setup_projects - sign_in - - # make sure the last listing is marked so we are redirected correctly - get '/admin/projects' - - get "/admin/project/#{@existing_project.id}/delete" - assert_redirected '/admin/projects' - assert_nil Project.fetch(@existing_project.id) - - teardown_projects - teardown_accounts end @@ -276,51 +156,4 @@ class AdminControllerTest < Stormy::Test::ControllerCase self.faq = original_faq end - - ###################### - ### Admin Accounts ### - ###################### - - def test_admins - sign_in - get '/admin/admins' - assert_ok - end - - def test_add_admin - sign_in - - password = 'password' - fields = { - 'name' => 'Freddy Kruger', - 'email' => 'freddy@example.com', - 'password' => password, - 'password_confirmation' => password - } - post '/admin/admins', fields - assert_redirected '/admin/admins' - admin = Admin.fetch_by_email('freddy@example.com') - assert admin.password == password - assert_equal fields['name'], admin.name - assert_equal fields['email'], admin.email - - # passwords do not match - fields = { - 'name' => 'Jason Vorhees', - 'email' => 'jason@example.com', - 'password' => 'my password', - 'password_confirmation' => 'not the same password' - } - post '/admin/admins', fields - assert_redirected '/admin/admins' - assert_nil Admin.fetch_by_email('jason@example.com') - end - - def test_delete_admin - sign_in - get "/admin/admins/#{@existing_admin.id}/delete" - assert_redirected '/admin/admins' - assert_equal 0, Admin.count - end - end diff --git a/test/controllers/test-projects_controller.rb b/test/controllers/test-projects_controller.rb deleted file mode 100644 index 19fcb75..0000000 --- a/test/controllers/test-projects_controller.rb +++ /dev/null @@ -1,262 +0,0 @@ -#!/usr/bin/env ruby -# -# Copyright 2011 Beta Street Media - -require 'common' - -class ProjectsControllerTest < Stormy::Test::ControllerCase - - include Stormy::Test::Helpers::Accounts - include Stormy::Test::Helpers::Admins - include Stormy::Test::Helpers::Projects - include Stormy::Helpers::Authorization - - def setup - header 'User-Agent', "rack/test (#{Rack::Test::VERSION})" - setup_accounts - setup_projects - sign_in - - @updated_project_data ||= { - :id => @existing_project.id, - :name => 'the super amazing project' - } - end - - def teardown - teardown_projects - teardown_accounts - end - - def create_other_account_and_project - @other_account = Account.create(@account_data) - @new_project_data['account_id'] = @other_account.id - @other_project = Project.create(@new_project_data) - end - - def photo_filenames - @photo_filenames ||= Dir[photo_file('*.jpg')] - end - - def add_photo(filename = photo_filenames.first) - post '/project/add-photo', { - :id => @existing_project.id, - :photo => Rack::Test::UploadedFile.new(filename, 'image/jpeg') - } - @existing_project.reload! - photo_id = @existing_project.photo_ids.last - assert_response_json_ok( - 'n' => @existing_project.count_photos, - 'photo' => { - 'id' => photo_id, - 'url' => @existing_project.photo_url(photo_id) - } - ) - end - - def add_all_photos - photo_filenames.each { |f| add_photo(f) } - end - - - ################## - ### Projects ### - ################## - - def test_projects - # must be authorized - sign_out - get '/projects' - assert_redirected '/sign-in' - - # now we can get the projects page - sign_in - get '/projects' - assert_ok - end - - def test_project - get "/project/#{@existing_project.id}" - assert_ok - end - - def test_project_without_a_name - @existing_project.name = '' - @existing_project.save! - get "/project/#{@existing_project.id}" - assert_ok - end - - def test_cannot_access_others_projects - create_other_account_and_project - get "/project/#{@other_project.id}" - assert_redirected '/projects' - follow_redirect! - assert_ok - assert last_response.body.match(/no such project/i) - end - - def test_update_project - data = @updated_project_data - post '/project/update', data - assert_redirected "/project/#{data[:id]}" - @existing_project.reload! - data.each do |name, value| - assert_equal value, @existing_project.send(name) - end - end - - def test_update_project_with_invalid_fields - expected_name = @existing_project.name - data = { - :id => @existing_project.id, - :name => '' - } - post '/project/update', data - assert_redirected "/project/#{data[:id]}" - @existing_project.reload! - assert_equal expected_name, @existing_project.name - end - - def test_update_project_by_admin - setup_admins - post '/admin/sign-in', @admin_data - - data = @updated_project_data - post '/project/update', data - assert_redirected "/project/#{data[:id]}" - - teardown_admins - end - - def test_cannot_update_others_projects - create_other_account_and_project - post '/project/update', { :id => @other_project.id } - assert_redirected '/projects' - follow_redirect! - assert_ok - assert last_response.body.match(/no such project/i) - end - - def test_add_photo - # also test /uploadify which is used for photo uploads in IE - %w[/project/add-photo /uploadify].each_with_index do |path, i| - post path, { - :id => @existing_project.id, - # /project/add-photo - :photo => Rack::Test::UploadedFile.new(photo_filenames.first, 'image/jpeg'), - # /uploadify - :Filedata => Rack::Test::UploadedFile.new(photo_filenames.first, 'image/jpeg') - } - @existing_project.reload! - photo_id = @existing_project.photo_ids[i] - assert_response_json_ok({ - 'n' => i + 1, - 'photo' => { - 'id' => photo_id, - 'url' => @existing_project.photo_url(photo_id) - } - }) - end - end - - def test_add_photo_fails_at_photo_limit - Project::MaxPhotos.times { add_photo } - - post '/project/add-photo', { - :id => @existing_project.id, - :photo => Rack::Test::UploadedFile.new(photo_filenames.first, 'image/jpeg'), - } - assert_response_json_fail('limit') - - post '/uploadify', { - :id => @existing_project.id, - :Filedata => Rack::Test::UploadedFile.new(photo_filenames.first, 'image/jpeg'), - } - assert_bad_request - end - - def test_add_photo_by_admin - setup_admins - post '/admin/sign-in', @admin_data - - post '/project/add-photo', { - :id => @existing_project.id, - :photo => Rack::Test::UploadedFile.new(photo_filenames.first, 'image/jpeg'), - } - @existing_project.reload! - photo_id = @existing_project.photo_ids.last - assert_response_json_ok({ - 'n' => 1, - 'photo' => { - 'id' => photo_id, - 'url' => @existing_project.photo_url(photo_id) - } - }) - - teardown_admins - end - - def test_remove_photo - add_photo - photo_id = @existing_project.photo_ids.last - post '/project/remove-photo', { - :id => @existing_project.id, - :photo_id => photo_id - } - @existing_project.reload! - assert_response_json_ok('photos' => []) - assert_equal 0, @existing_project.count_photos - end - - def test_remove_photo_by_admin - setup_admins - post '/admin/sign-in', @admin_data - - add_photo - photo_id = @existing_project.photo_ids.last - post '/project/remove-photo', { - :id => @existing_project.id, - :photo_id => photo_id - } - @existing_project.reload! - assert_response_json_ok('photos' => []) - assert_equal 0, @existing_project.count_photos - - teardown_admins - end - - def test_reorder_photos - add_all_photos - @existing_project.reload! - photo_ids = @existing_project.photo_ids - # move the first to the end - photo_ids.push(photo_ids.shift) - post '/project/photo-order', { - :id => @existing_project.id, - :order => photo_ids - } - @existing_project.reload! - assert_equal photo_ids, @existing_project.photo_ids - end - - def test_reorder_photos_by_admin - setup_admins - post '/admin/sign-in', @admin_data - - add_all_photos - @existing_project.reload! - photo_ids = @existing_project.photo_ids - # move the first to the end - photo_ids.push(photo_ids.shift) - post '/project/photo-order', { - :id => @existing_project.id, - :order => photo_ids - } - @existing_project.reload! - assert_equal photo_ids, @existing_project.photo_ids - - teardown_admins - end - -end diff --git a/test/fixtures/accounts.json b/test/fixtures/accounts.json index 926a257..f0b2a18 100644 --- a/test/fixtures/accounts.json +++ b/test/fixtures/accounts.json @@ -4,6 +4,7 @@ , "email" : "sami@example.com" , "phone" : "250-216-6216" , "password" : "super secret" + , "role" : "admin" } , "freddy": { @@ -12,5 +13,6 @@ , "email" : "freddy@example.com" , "phone" : "(250) 555-9999" , "password" : "even more secreter" + , "role" : "user" } } diff --git a/test/fixtures/admins.json b/test/fixtures/admins.json deleted file mode 100644 index 85244ba..0000000 --- a/test/fixtures/admins.json +++ /dev/null @@ -1,12 +0,0 @@ -{ "sami": { - "name" : "Sami" - , "email" : "sami@example.com" - , "password" : "super secret" - } - -, "freddy": { - "name" : "Freddy" - , "email" : "freddy@example.com" - , "password" : "even more secreter" - } -} diff --git a/test/fixtures/projects.json b/test/fixtures/projects.json deleted file mode 100644 index d18cb43..0000000 --- a/test/fixtures/projects.json +++ /dev/null @@ -1,14 +0,0 @@ -{ "stormy": { - "name" : "Stormy Weather" - } - -, "apps-for-you" : { - "name" : "Apps For You" - , "funded_timestamp" : 1328475981 - } - -, "dating-free" : { - "name" : "Dating Free" - , "fizzled_timestamp" : 1328475981 - } -} diff --git a/test/helpers/test-accounts.rb b/test/helpers/test-accounts.rb index 1d86e09..5648483 100644 --- a/test/helpers/test-accounts.rb +++ b/test/helpers/test-accounts.rb @@ -8,7 +8,6 @@ class AccountsHelperTest < Stormy::Test::HelperCase include Stormy::Helpers::Accounts include Stormy::Test::Helpers::Accounts - include Stormy::Test::Helpers::Projects def setup setup_accounts diff --git a/test/helpers/test-admin.rb b/test/helpers/test-admin.rb index 79b1424..35cdcd6 100644 --- a/test/helpers/test-admin.rb +++ b/test/helpers/test-admin.rb @@ -16,14 +16,6 @@ class AdminHelperTest < Stormy::Test::HelperCase assert_equal 0, num_accounts end - def test_num_admins - assert_equal 0, num_admins - end - - def test_num_projects - assert_equal 0, num_projects - end - def test_last_listing assert_equal '/admin', last_listing mark_last_listing '/admin/accounts' diff --git a/test/helpers/test-authorization.rb b/test/helpers/test-authorization.rb index b5385a3..63d3537 100644 --- a/test/helpers/test-authorization.rb +++ b/test/helpers/test-authorization.rb @@ -8,21 +8,16 @@ class AuthorizationHelperTest < Stormy::Test::HelperCase include Stormy::Helpers::Authorization include Stormy::Test::Helpers::Accounts - include Stormy::Test::Helpers::Admins - include Stormy::Test::Helpers::Projects def setup setup_accounts - setup_projects end def teardown deauthorize teardown_request teardown_accounts - teardown_projects @current_account = nil - @current_project = nil end def teardown_request @@ -69,7 +64,7 @@ class AuthorizationHelperTest < Stormy::Test::HelperCase end def url - '/projects' + '/account' end end end @@ -153,116 +148,31 @@ class AuthorizationHelperTest < Stormy::Test::HelperCase assert_equal @existing_account.id, current_account.id end - def test_current_project - assert_nil current_project - - current_project(@existing_project.id) - assert_equal @existing_project.id, current_project.id - end - - def test_project_authorized? - assert !project_authorized? - - current_project(@existing_project.id) - assert !project_authorized? - - authorize_account(@existing_account.id) - assert project_authorized? - - @current_account = nil - other_account = Account.create(@account_data) - authorize_account(other_account) - assert !project_authorized? - other_account.delete! - end - - def test_authorize_project_api! - assert_equal not_authorized, catch(:halt) { authorize_project_api!(@existing_project.id) } - assert_content_type 'text/plain' - @content_type = nil - - authorize_account(@existing_account.id) - authorize_project_api!(@existing_project.id) - - assert_equal fail('no such project'), catch(:halt) { authorize_project_api!('non-existent id') } - - @current_account = nil - other_account = Account.create(@account_data) - authorize_account(other_account.id) - assert_equal not_authorized, catch(:halt) { authorize_project_api!(@existing_project.id) } - assert_content_type 'text/plain' - other_account.delete! - end - - def test_authorize_project! - assert_redirected('/sign-in') { authorize_project!(@existing_project.id) } - assert !project_authorized? - - authorize_account(@existing_account.id) - assert_redirected('/projects') { authorize_project!('non-existent id') } - assert_equal 'No such project.', flash[:warning] - @redirect = nil - - authorize_project!(@existing_project.id) - assert_not_redirected - - @current_account = nil - other_account = Account.create(@account_data) - authorize_account(other_account.id) - assert_redirected('/projects') { authorize_project!(@existing_project.id) } - assert_equal 'No such project.', flash[:warning] - other_account.delete! - end - - def test_authorize_admin - setup_admins - authorize_admin(@admin.id) - assert_equal @admin.id, session[:admin_id] - assert admin_authorized? - teardown_admins - end - - def test_deauthorize_admin - setup_admins - authorize_admin(@admin.id) - - deauthorize_admin - assert !admin_authorized? - assert !session.has_key?(:admin_id) - teardown_admins - end - def test_admin_authorized? - setup_admins assert !admin_authorized? - authorize_admin(@admin.id) + authorize_account(@existing_account.id) assert admin_authorized? - @current_admin = nil - authorize_admin('does not exist') + @current_account = nil + authorize_account('does not exist') assert !admin_authorized? - teardown_admins end def test_admin_authorize! - setup_admins - authorize_admin(@admin.id) + authorize_account(@existing_account.id) admin_authorize! assert_not_redirected assert !session.has_key?(:original_url) - teardown_admins end def test_admin_authorize_redirects_to_sign_in - assert_redirected('/admin') { admin_authorize! } + assert_redirected('/sign-in') { admin_authorize! } assert_equal request.url, session[:original_url] end def test_admin_authorize_api! - setup_admins - authorize_admin(@admin.id) + authorize_account(@existing_account.id) assert_nil catch(:halt) { admin_authorize_api! } - teardown_admins end def test_admin_authorize_api_throws_if_unauthorized @@ -270,13 +180,4 @@ class AuthorizationHelperTest < Stormy::Test::HelperCase assert_content_type 'text/plain' end - def test_current_admin - setup_admins - assert_nil current_admin - - authorize_admin(@admin.id) - assert_equal @admin.id, current_admin.id - teardown_admins - end - end diff --git a/test/helpers/test-views.rb b/test/helpers/test-views.rb index ab5bf14..86454f4 100644 --- a/test/helpers/test-views.rb +++ b/test/helpers/test-views.rb @@ -203,4 +203,11 @@ class ViewsHelperTest < Stormy::Test::HelperCase assert_equal "<p>42</p>\n", markdown(42) end + def test_admin_page? + assert admin_page?('/admin') + assert admin_page?('/admin/accounts') + assert !admin_page?('/') + assert !admin_page?('/account') + end + end diff --git a/test/models/test-account.rb b/test/models/test-account.rb index bcec183..e345d9c 100644 --- a/test/models/test-account.rb +++ b/test/models/test-account.rb @@ -7,11 +7,9 @@ require 'common' class AccountTest < Stormy::Test::Case include Stormy::Test::Helpers::Accounts - include Stormy::Test::Helpers::Projects def setup setup_accounts - setup_projects @invalid_addresses = [ 'invalid email address', @@ -22,7 +20,6 @@ class AccountTest < Stormy::Test::Case end def teardown - teardown_projects teardown_accounts end @@ -75,7 +72,7 @@ class AccountTest < Stormy::Test::Case assert Account.verify_email(@existing_account.email, @existing_account.email_verification_token) account = Account.fetch(@existing_account.id) assert account.email_verified - assert !account.email_verification_token + assert !account.instance_variable_get('@email_verification_token') assert !Account.verify_email('non@existent.email', 'insignificant token') end @@ -90,15 +87,12 @@ class AccountTest < Stormy::Test::Case # no new token is generated if one is present token = @existing_account.email_verification_token - @existing_account.create_email_verification_token assert_equal @existing_account.email_verification_token, token # a token is generated if necessary Account.verify_email(@existing_account.email, @existing_account.email_verification_token) # clears token account = Account.fetch(@existing_account.id) - assert !account.email_verification_token - account.create_email_verification_token - assert account.email_verification_token + assert !account.instance_variable_get('@email_verification_token') assert account.email_verification_token != token end @@ -132,7 +126,7 @@ class AccountTest < Stormy::Test::Case end def test_create_with_existing_email - assert_raises Account::EmailTakenError do + assert_raises Account::DuplicateFieldError do Account.new(@existing_account_data).create end end @@ -195,46 +189,14 @@ class AccountTest < Stormy::Test::Case assert Account.fetch_by_email(@existing_account.email).nil?, 'Account was fetched by email after deletion' # indexes - assert !@existing_account.email_taken?, 'Account email is taken after deletion' + assert !@existing_account.email_taken?(@existing_account.email), 'Account email is taken after deletion' assert !Account.exists?(@existing_account.id), 'Account exists after deletion' - - # projects are deleted - assert_equal [], @existing_account.project_ids end def test_name assert_equal "#{@existing_account.first_name} #{@existing_account.last_name}", @existing_account.name end - def test_count_projects - assert_equal 1, @existing_account.count_projects - end - - def test_project_ids - assert_equal [@existing_project.id], @existing_account.project_ids - end - - def test_projects - assert_equal [@existing_project.id], @existing_account.projects.map { |p| p.id } - end - - def test_sorted_projects - # make sure created timestamp is in the future ... this stinks - sleep 1 - project = Project.create(@new_project_data.merge(:account_id => @existing_account.id)) - assert_equal [@existing_project.id, project.id], @existing_account.sorted_projects.map { |p| p.id } - end - - def test_add_project_id - @existing_account.add_project_id('fake-project-id') - assert_equal 2, @existing_account.count_projects - end - - def test_remove_project_id - @existing_account.remove_project_id(@existing_project.id) - assert_equal 0, @existing_account.count_projects - end - def test_update original_data = { 'id' => @existing_account.id, @@ -377,7 +339,7 @@ class AccountTest < Stormy::Test::Case # all should be updated check_account_fields(@existing_account, updated_data) - # restore fields required for project clean up + # restore fields required for clean up @existing_account.update!(original_data) end diff --git a/test/models/test-admin.rb b/test/models/test-admin.rb deleted file mode 100644 index 54e2984..0000000 --- a/test/models/test-admin.rb +++ /dev/null @@ -1,237 +0,0 @@ -#!/usr/bin/env ruby -# -# Copyright 2012 Sami Samhuri <sami@samhuri.net> - -require 'common' - -class AdminTest < Stormy::Test::Case - - def setup - admins = fixtures('admins') - @admin_data = admins['sami'] - @admin = Admin.create(@admin_data) - - @invalid_addresses = [ - 'invalid email address', - 'invalid@email@address', - 'invalid.email.address', - 'invalid.email@address' - ] - end - - def teardown - @admin.delete! - end - - - ### Class Methods - - def test_key_from_email - assert_equal @admin.send(:key), Admin.key_from_email(@admin.email) - assert_nil Admin.key_from_email('not a real email') - end - - def test_check_password - assert_equal @admin.id, Admin.check_password(@admin.email, @admin_data['password']) - assert_equal @admin.id, Admin.check_password(@admin.email.upcase, @admin_data['password']) - assert !Admin.check_password(@admin.email, 'incorrect password') - assert !Admin.check_password('non@existent.email', 'any password') - end - - def test_email_taken? - assert Admin.email_taken?(@admin.email) - assert !Admin.email_taken?('freddy@example.com'), "New email is reported as taken" - end - - def test_fetch_existing_by_id - admin = Admin.fetch(@admin.id) - assert admin - assert_equal @admin.id, admin.id - check_admin_fields(admin, @admin_data) - end - - def test_fetch_nonexistent_by_id - assert_nil Admin.fetch('this is not a real id') - end - - def test_fetch_existing_by_email - admin = Admin.fetch_by_email(@admin.email) - assert admin - assert_equal @admin.id, admin.id - check_admin_fields(admin, @admin_data) - end - - def test_fetch_nonexistent_by_email - assert_nil Admin.fetch_by_email('this is not a real email') - end - - def test_id_from_email - assert_equal @admin.id, Admin.id_from_email(@admin.email) - assert_nil Admin.id_from_email('not a real email') - end - - - ### Instance Methods - - def check_admin_fields(admin, fields) - fields.each do |key, expected| - if key == 'password' - assert admin.password == fields['password'], "<#{fields['password'].inspect}> expected but was <#{admin.password.inspect}>" - else - actual = admin.send(key) - assert_equal expected, actual, "#{key}: <#{expected.inspect}> expected but was <#{actual.inspect}>" - end - end - end - - def test_create - assert @admin - assert @admin.id - check_admin_fields(@admin, @admin_data) - - # indexes - assert Admin.fetch_by_email(@admin.email) - end - - def test_create_with_existing_email - assert_raises Admin::EmailTakenError do - Admin.new(@admin_data).create - end - end - - def test_create_with_missing_fields - # name - assert_raises Admin::InvalidDataError do - Admin.create({ 'email' => 'freddy@example.com', 'password' => 'secret password' }) - end - assert_raises Admin::InvalidDataError do - Admin.create({ 'name' => ' ', 'email' => 'freddy@example.com', 'password' => 'secret password' }) - end - - # email - assert_raises Admin::InvalidDataError do - Admin.create({ 'name' => 'Freddy', 'password' => 'secret password' }) - end - assert_raises Admin::InvalidDataError do - Admin.create({ 'name' => 'Freddy', 'email' => ' ', 'password' => 'secret password' }) - end - - # password - assert_raises Admin::InvalidDataError do - Admin.create({ 'name' => 'Freddy', 'email' => 'freddy@example.com' }) - end - assert_raises Admin::InvalidDataError do - Admin.create({ 'name' => 'Freddy', 'email' => 'freddy@example.com', 'password' => ' ' }) - end - end - - def test_create_with_invalid_fields - data = { - 'name' => 'Freddy', - 'password' => 'secret password' - } - - @invalid_addresses.each do |email| - data['email'] = email - assert_raises Admin::InvalidDataError do - Admin.create(data) - end - end - end - - def test_delete! - @admin.delete! - - assert Admin.fetch(@admin.id).nil?, 'Admin was fetched by id after deletion' - assert Admin.fetch_by_email(@admin.email).nil?, 'Admin was fetched by email after deletion' - - # indexes - assert !@admin.email_taken?, 'Admin email is taken after deletion' - assert !Admin.exists?(@admin.id), 'Admin exists after deletion' - end - - def test_update - original_data = { - 'id' => @admin.id, - 'email' => @admin.email, - } - - updated_data = { - # updatable - 'name' => 'Samson', - - # not updatable - 'id' => 'should be ignored', - 'email' => 'should be ignored', - 'password' => 'should be ignored', - } - @admin.update(updated_data) - - # should be updated - assert_equal updated_data['name'], @admin.name - - # should not be updated - assert_equal original_data['id'], @admin.id - assert_equal original_data['email'], @admin.email - assert @admin.password != updated_data['password'] - assert @admin.password == @admin_data['password'] - end - - def test_update_with_invalid_fields - assert_raises Admin::InvalidDataError do - @admin.update({ 'name' => ' ' }) - end - end - - def test_update_email - # pretend this address is verified - new_email = 'sami-different@example.com' - old_email = @admin.email - - # updates database immediately - @admin.update_email(new_email) - assert_equal new_email, @admin.email - assert_equal new_email, Admin.fetch(@admin.id).email - - # index is updated - assert Admin.email_taken?(new_email) - assert !Admin.email_taken?(old_email) - - # no change in address is a noop - @admin.update_email(new_email) - - # invalid addresses are rejected - @invalid_addresses.each do |email| - assert_raises Admin::InvalidDataError do - @admin.update_email(email) - end - end - end - - def test_update_email_changing_only_case - # change only the case - loud_email = @admin.email.upcase - @admin.update_email(loud_email) - assert_equal loud_email, @admin.email - - # is still indexed properly - assert Admin.email_taken?(loud_email) - end - - def test_update_password - old_password = @admin_data['password'] - new_password = 'the new password' - @admin.update_password(old_password, new_password) - assert @admin.password == new_password - assert Admin.fetch(@admin.id).password == new_password - - assert_raises Admin::IncorrectPasswordError do - @admin.update_password('incorrect', 'irrelevant') - end - - assert_raises Admin::InvalidDataError do - @admin.update_password(new_password, ' ') - end - end - -end diff --git a/test/models/test-base.rb b/test/models/test-base.rb index 1597f2d..eecd340 100644 --- a/test/models/test-base.rb +++ b/test/models/test-base.rb @@ -9,8 +9,10 @@ class ModelBaseTest < Stormy::Test::Case def setup @my_model_class = Class.new(Stormy::Models::Base) @my_model_class.class_eval do + model_name 'my_model' field :id, :required => true - field :name, :updatable => true, :required => true + field :name, :updatable => true, :required => true, :indexed => true + field :email, :required => true, :unique => true field :age, { :type => :integer, :required => true, @@ -18,14 +20,8 @@ class ModelBaseTest < Stormy::Test::Case :validator => proc { |n| n >= 18 } } field :verified, :type => :boolean - - def create - self.id = UUID.generate unless id.present? - super - end - end - @fields = { 'name' => 'Sami', 'age' => '29' } + @fields = { 'name' => 'Sami', 'age' => '29', 'email' => 'sami@samhuri.net' } @my_model = @my_model_class.create(@fields) end @@ -39,15 +35,16 @@ class ModelBaseTest < Stormy::Test::Case ### Class Methods def test_name - @my_model_class.name 'my_model' - assert_equal 'my_model', @my_model_class.name + @my_model_class.model_name 'my_model' + assert_equal 'my_model', @my_model_class.model_name end def test_id_field - # has id by default + # has id id_field = { :type => :string, - :required => true + :required => true, + :accessors => true } assert_equal(id_field, @my_model_class.fields[:id]) methods = %w[id id=] @@ -60,7 +57,9 @@ class ModelBaseTest < Stormy::Test::Case name_field = { :type => :string, :required => true, - :updatable => true + :updatable => true, + :accessors => true, + :indexed => true } assert_equal(name_field, @my_model_class.fields[:name]) methods = %w[name name=] @@ -85,7 +84,7 @@ class ModelBaseTest < Stormy::Test::Case end def test_verified_field - verified_field = { :type => :boolean } + verified_field = { :type => :boolean, :accessors => true } assert_equal(verified_field, @my_model_class.fields[:verified]) methods = %w[verified verified= verified?] methods.each do |name| @@ -125,7 +124,7 @@ class ModelBaseTest < Stormy::Test::Case def test_key id = @my_model.id - key = Stormy.key(@my_model_class.name, id) + key = Stormy.key(@my_model_class.model_name, id) assert_equal key, @my_model_class.key(id) end diff --git a/test/models/test-project.rb b/test/models/test-project.rb deleted file mode 100644 index 5a63e2a..0000000 --- a/test/models/test-project.rb +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env ruby -# -# Copyright 2011 Beta Street Media - -require 'common' - -JPEGHeader = "\xFF\xD8\xFF\xE0\u0000\u0010JFIF" - -class ProjectTest < Stormy::Test::Case - - include Stormy::Test::Helpers::Accounts - include Stormy::Test::Helpers::Projects - - def setup - setup_accounts - setup_projects - @test_photo_path = photo_file('wild-wacky-action-bike.jpg') - end - - def teardown - teardown_projects - teardown_accounts - end - - def check_project_fields(project, fields) - fields.each do |key, expected| - actual = project.send(key) - assert_equal expected, actual, "#{key}: <#{expected.inspect}> expected but was <#{actual.inspect}>" - end - end - - - ##################### - ### Class Methods ### - ##################### - - def test_fetch_by_name - project = Project.fetch_by_name(@existing_project.name) - assert project - assert_equal @existing_project.id, project.id - check_project_fields(project, @existing_project_data) - end - - def test_fetch_nonexistent_by_name - assert_nil Project.fetch_by_name('non-existent') - end - - - ######################## - ### Instance Methods ### - ######################## - - def test_create - assert @existing_project - assert @existing_project.id - check_project_fields(@existing_project, @existing_project_data) - - # ensure created time is set - # (timestamps may have been a second or two ago at this point, give some allowance for that) - created_delta = Time.now.to_i - @existing_project.created_timestamp - assert created_delta < 3 - - # adds iteslf to project id list on associated account - assert @existing_project.account.project_ids.include?(@existing_project.id) - end - - def test_save_with_missing_fields - Project.fields.each do |name, options| - if options[:required] - orig_value = @existing_project.send(name) - @existing_project.send("#{name}=", nil) - assert_raises Project::InvalidDataError, "#{name} should be required" do - @existing_project.save - end - empty_value = - case options[:type] - when :string - ' ' - when :integer - 0 - else - ' ' - end - @existing_project.send("#{name}=", empty_value) - assert_raises Project::InvalidDataError, "#{name} should be required" do - @existing_project.save - end - @existing_project.send("#{name}=", orig_value) - end - end - end - - def test_delete! - @existing_project.delete! - assert Project.fetch(@existing_project.id).nil?, 'Project was fetched by id after deletion' - assert !Project.exists?(@existing_project.id), 'Project exists after deletion' - - # removes iteslf from project id list on associated account - assert !@existing_project.account.project_ids.include?(@existing_project.id) - end - - def test_count_photos - 10.times do |i| - assert_equal i, @existing_project.count_photos - @existing_project.add_photo(@test_photo_path) - assert_equal i + 1, @existing_project.count_photos - end - end - - def test_add_photo - data = @existing_project.add_photo(@test_photo_path) - assert_equal 1, @existing_project.count_photos - path = @existing_project.send(:photo_path, data['id']) - assert File.exists?(path) - assert_equal JPEGHeader, File.read(path, JPEGHeader.length) - end - - def test_remove_photo - data = @existing_project.add_photo(@test_photo_path) - path = @existing_project.send(:photo_path, data['id']) - @existing_project.remove_photo(data['id']) - assert !File.exists?(path) - assert_equal 0, @existing_project.count_photos - end - - def test_photo_data - data = @existing_project.add_photo(@test_photo_path) - assert_equal data, @existing_project.photo_data(data['id']) - end - - def test_photo_urls - data = @existing_project.add_photo(@test_photo_path) - urls = @existing_project.photo_urls - assert_equal 1, urls.length - - url = urls.first - assert_equal "/photos/#{@existing_project.id}/#{data['id']}.jpg", url - assert_equal data['url'], url - end - - def test_photo_paths - @existing_project.add_photo(@test_photo_path) - assert_equal 1, @existing_project.photo_paths.length - end - - def test_photos - assert_equal [], @existing_project.photos - data = nil - 5.times do |i| - data = @existing_project.add_photo(@test_photo_path) - end - assert_equal [data] * 5, @existing_project.photos - end - - def test_account - assert @existing_project.account - assert_equal @existing_account.id, @existing_project.account.id - end - -end diff --git a/test/photos/wild-wacky-action-bike.jpg b/test/photos/wild-wacky-action-bike.jpg deleted file mode 100644 index 2fe6dfc..0000000 Binary files a/test/photos/wild-wacky-action-bike.jpg and /dev/null differ diff --git a/test/test-config.rb b/test/test-config.rb index bdfe6b0..e5de97e 100644 --- a/test/test-config.rb +++ b/test/test-config.rb @@ -38,7 +38,7 @@ class ConfigTest < Stormy::Test::Case def test_method_missing_get # (none yet) - # assert_equal defaults['demo_project_id'], @config.demo_project_id + # assert_equal defaults['foo_bar'], @config.foo_bar end def test_method_missing_get_default diff --git a/views/account.erb b/views/account.erb index dd2735a..e4713e0 100644 --- a/views/account.erb +++ b/views/account.erb @@ -1,65 +1,82 @@ -<h2 class="section-heading top">Account</h2> - <%== flash_message %> -<table id="account"> - <tr> - <th>First name</th> - <td><span id="first_name" class="editable"><%= @account.first_name %></span></td> - <td rowspan="4" class="edit-instructions"> - ← click to edit - </td> - </tr> - <tr> - <th>Last name</th> - <td><span id="last_name" class="editable"><%= @account.last_name %></span></td> - </tr> - <tr> - <th>Email</th> - <td> - <span id="email" class="editable-json"><%= @account.email %></span> - <span id="email-verified" class="verified <%= @account.email_verified? ? '' : 'hidden' %>">✓ Verified</span> - </td> - </tr> - <tr> - <th>Phone</th> - <td> - <span id="phone" class="editable"><%= @account.phone %></span> - </td> - </tr> -</table> +<div class="container"> + <div class="content"> -<p class="indented"><a href="#" id="change-password-link">Change my password</a></p> + <div class="row"> + <div class="fields span6"> + <h2>Your Account</h2> -<form action="/account/password" method="post" id="change-password-form"> -<table id="change-password"> - <tr> - <th><label for="old-password">Current password</label></th> - <td><input type="password" id="old-password" name="old-password" placeholder="Current password"></td> - </tr> - <tr> - <th><label for="new-password">New password</label></th> - <td><input type="password" id="new-password" name="new-password" placeholder="Secret"></td> - </tr> - <tr> - <th><label for="password-confirmation">Confirm</label></th> - <td><input type="password" id="password-confirmation" name="password-confirmation" placeholder="Again"></td> - </tr> - <tr> - <th></th> - <td align="right"> - <input type="submit" id="change-password-button" value="Change My Password"> - <img class="spinner" src="/images/spinner.gif"> - </td> - </tr> -</table> -</form> + <div class="clearfix"> + <p> + <strong>First name</strong> + <br> + <span id="first_name" class="editable"><%= @account.first_name %></span> + </p> + </div> -<p id="password-changed" class="indented">Password changed!</p> + <div class="clearfix"> + <p> + <strong>Last name</strong> + <br> + <span id="last_name" class="editable"><%= @account.last_name %></span> + </p> + </div> -<div id="email-verification" class="indented <%= @account.email_verified? ? 'hidden' : '' %>"> - <hr> - <p id="unverified-email" class="unverified">Your email address is unverified.</p> - <p><a href="#" id="send-email-verification">Send verification email</a></p> - <p id="sending-email-verification"><img src="/images/spinner.gif"> Sending...</p> + <div class="clearfix"> + <p> + <strong>Phone</strong> + <br> + <span id="phone" class="editable"><%= @account.phone %></span> + </p> + </div> + + <div class="clearfix"> + <span id="email-verified" class="verified <%= @account.email_verified? ? '' : 'hidden' %>">✓ Verified</span> + <p> + <strong>Email</strong> + <br> + <span id="email" class="editable-json"><%= @account.email %></span> + </p> + </div> + + <div id="email-verification" class="clearfix <%= @account.email_verified? ? 'hidden' : '' %>"> + <p id="unverified-email" class="unverified">Your email address is unverified.</p> + <p><button id="send-email-verification" class="btn">Send verification email</button></p> + <p id="sending-email-verification" class="hidden"><img src="/images/spinner.gif"> Sending...</p> + </div> + + </div> <!-- fields --> + + <div class="fields span6"> + <hr class="separator"> + + <h2>Password</h2> + + <form action="/account/password" method="post" id="change-password-form"> + <fieldset> + <div class="clearfix"> + <label for="old-password">Current password</label> + <input type="password" id="old-password" name="old-password" placeholder="Current password"> + </div> + <div class="clearfix"> + <label for="new-password">New password</label> + <input type="password" id="new-password" name="new-password" placeholder="Secret"> + </div> + <div class="clearfix"> + <label for="password-confirmation">Confirm</label> + <input type="password" id="password-confirmation" name="password-confirmation" placeholder="Again"> + </div> + <div class="clearfix"> + <input type="submit" id="change-password-button" class="btn" value="Change my password"> + <span id="change-password-spinner" class="hidden"><img class="spinner" src="/images/spinner.gif"> Changing...</span> + </div> + <p id="password-changed" class="notice hidden">Password changed!</p> + </fieldset> + </form> + + </div> <!-- fields --> + </div> <!-- row --> + + </div> </div> diff --git a/views/admin/accounts.erb b/views/admin/accounts.erb index eba9fdf..1d72b90 100644 --- a/views/admin/accounts.erb +++ b/views/admin/accounts.erb @@ -4,7 +4,6 @@ <th>Name</th> <th>Email</th> <th>Phone</th> - <th>Projects</th> </tr> <% for account in @accounts %> @@ -12,7 +11,6 @@ <td class="name"><a href="/admin/account/<%= account.email %>"><%= account.name %></a></td> <td><%= account.email %></td> <td><%= account.phone %></td> - <td><%= account.count_projects %></td> </tr> <% end %> </table> diff --git a/views/admin/admins.erb b/views/admin/admins.erb deleted file mode 100644 index 63bb771..0000000 --- a/views/admin/admins.erb +++ /dev/null @@ -1,44 +0,0 @@ -<p>Listing <%= @admins.length %> admin accounts</p> - -<table id="accounts" width="90%"> - <tr> - <th>Name</th> - <th>Email</th> - <th>Actions</th> - </tr> - - <% for admin in @admins %> - <tr> - <td class="name"><%= admin.name %></td> - <td><%= admin.email %></td> - <td><a href="/admin/admins/<%= admin.id %>/delete">Delete</a></td> - </tr> - <% end %> -</table> - -<h2>Add an admin</h2> -<form method="post"> - <table> - <tr> - <th>Name</th> - <td><input type="text" name="name" size="30" value="<%= @fields['name'] %>"></td> - </tr> - <tr> - <th>Email</th> - <td><input type="email" name="email" size="30" value="<%= @fields['email'] %>"></td> - </tr> - <tr> - <th>Password</th> - <td><input type="password" name="password" size="30"></td> - </tr> - <tr> - <th>Confirm</th> - <td><input type="password" name="password_confirmation" size="30"></td> - </tr> - <tr> - <td colspan="2" align="right"> - <input type="submit" value="Add admin"> - </td> - </tr> - </table> -</form> diff --git a/views/admin/layout.erb b/views/admin/layout.erb deleted file mode 100644 index 24075be..0000000 --- a/views/admin/layout.erb +++ /dev/null @@ -1,48 +0,0 @@ -<!doctype html> -<html lang="en"> - -<head> - <title><%= @page_title %> - Stormy Weather Admin - - - <% if @page_styles %> - <% for url in @page_styles %> - - <% end %> - <% end %> - - - - -

    <%= @page_title %>

    - -<% if current_admin %> -
    - Signed in as <%= current_admin.name %>. - Sign Out -
    - Change password -
    -
    - -<%== flash_message %> - - -<% end %> - -
    - <%== yield %> -
    - -<% for url in scripts %> - -<% end %> - - - diff --git a/views/admin/project.erb b/views/admin/project.erb deleted file mode 100644 index 723433a..0000000 --- a/views/admin/project.erb +++ /dev/null @@ -1,45 +0,0 @@ -

    << Projects

    - - - - - - - - - - - - - - - - - - - <% if @project.funded? %> - - - - - <% end %> - <% if @project.fizzled? %> - - - - - <% end %> -
    ID<%= @project.id %>
    Owner<%= @project.account.name %>
    Name<%= @project.name %>
    Created<%= format_date(Time.at(@project.created_timestamp)) %>
    Funded<%= format_date(Time.at(@project.funded_timestamp)) %>
    Fizzled<%= format_date(Time.at(@project.fizzled_timestamp)) %>
    - -

    Actions

    -

    - Delete this project -

    - -

    Photos

    -
    -<% for url in @project.photo_urls %> - -
    -<% end %> -
    diff --git a/views/admin/projects.erb b/views/admin/projects.erb deleted file mode 100644 index 17861da..0000000 --- a/views/admin/projects.erb +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - <% for project in @projects %> - - - - - <% end %> -
    NameOwnerCreatedFizzledFunded
    <%= project.name %><%= project.account.name %><%= format_date(Time.at(project.created_timestamp)) %> - <%= project.fizzled? ? format_date(Time.at(project.fizzled_timestamp)) : '-' %> - <%= project.funded? ? format_date(Time.at(project.funded_timestamp)) : '-' %> -
    diff --git a/views/admin/sign-in.erb b/views/admin/sign-in.erb deleted file mode 100644 index 46869a9..0000000 --- a/views/admin/sign-in.erb +++ /dev/null @@ -1,27 +0,0 @@ -<%== flash_message %> - -
    - - - - - - - - - - - - - - - - - -
    - -
    - - Signing in... -
    -
    diff --git a/views/contact.erb b/views/contact.erb index 2ecb652..3bcbcc5 100644 --- a/views/contact.erb +++ b/views/contact.erb @@ -1,12 +1,12 @@
    -

    About Us

    +

    About Us

    Made of 100% pure awesome. #winning

    -

    Contact Us

    +

    Contact Us

    <%== flash_message %> @@ -23,28 +23,28 @@
    -

    Email

    +

    Email

    Contact us via email.

    - + - + - +
    Technical support:tech@example.comtech@example.comapult.com
    Accounting support:accounts@example.comaccounts@example.comapult.com
    General inquiries:info@example.cominfo@example.comapult.com
    -

    Address and Phone

    +

    Address and Phone

    Or keep it old school.

    diff --git a/views/edit-project.erb b/views/edit-project.erb deleted file mode 100644 index d7e4000..0000000 --- a/views/edit-project.erb +++ /dev/null @@ -1,80 +0,0 @@ -

    Project Info

    - -<%== flash_message %> - - - - -
    - - -
    - - -
    - - -

    - - Saving... -

    - - - - - - - - -
    - -
    - - -

    Photos

    - - - -
    -
      - <% for photo in @project.photos %> -
    • - -
      - remove -
    • - <% end %> - -
    • - - - -
      - add photo - -
    • -
    -

    Drag and drop photos to change their order.

    -
    - -

    - - Saving... -

    - -
    - - diff --git a/views/email/error-notification.erb b/views/email/error-notification.erb index 2c9bfb1..30daaa4 100644 --- a/views/email/error-notification.erb +++ b/views/email/error-notification.erb @@ -8,21 +8,11 @@

    session: <%= session.inspect %>

    -<% if admin %> -

    Admin: <%= admin.name %> <<%= admin.email %>>

    -
    <%= admin.field_array %>
    -<% end %> - <% if account %>

    Account: <%= account.name %> <<%= account.email %>>

    <%= account.field_array %>
    <% end %> -<% if project %> -

    Project: <%= project.id %> (<%= project.name %>)

    -
    <%= project.field_array %>
    -<% end %> -
     <%= error.class %>: <%= error.message %>
     
    diff --git a/views/error.erb b/views/error.erb
    index 70e8174..1c20f05 100644
    --- a/views/error.erb
    +++ b/views/error.erb
    @@ -1,5 +1,5 @@
     
    -

    Oops

    +

    Oops

    diff --git a/views/faq.erb b/views/faq.erb index b59a439..b1c0e61 100644 --- a/views/faq.erb +++ b/views/faq.erb @@ -1,4 +1,4 @@ -

    Frequently Asked Questions

    +

    Frequently Asked Questions

    <%== @faq %> diff --git a/views/forgot-password.erb b/views/forgot-password.erb index 1864e52..0e5ea25 100644 --- a/views/forgot-password.erb +++ b/views/forgot-password.erb @@ -1,4 +1,4 @@ -

    Forgot Password

    +

    Forgot Password

    <%== flash_message %> diff --git a/views/layout.erb b/views/layout.erb index 769e4b1..6908241 100644 --- a/views/layout.erb +++ b/views/layout.erb @@ -3,9 +3,22 @@ - + + + - <%= @page_title ? @page_title + ' - ' : '' %>Stormy Weather + Stormy Weather<%= admin_page? ? ' Admin' : '' %><%= @page_title ? ' - ' + @page_title : '' %> + + + + + + + + + <% for url in stylesheets %> @@ -15,50 +28,87 @@ + + +<% if authorized? %>
    +<% end %> -
    - -
    + <% if breadcrumbs.size > 0 %> + + <% end %> - + <%== flash_message %> -
    - <%== yield %> -
    + <%== yield %> - + -
    +
    <% for url in scripts %> @@ -68,7 +118,7 @@ if (false) { var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-XXXXXXXX-X']); - _gaq.push(['_setDomainName', 'example.com']); + _gaq.push(['_setDomainName', 'example.comapult.com']); _gaq.push(['_trackPageview']); (function() { diff --git a/views/not-found.erb b/views/not-found.erb index 1c73319..b84200a 100644 --- a/views/not-found.erb +++ b/views/not-found.erb @@ -1,15 +1,9 @@ -

    :(

    +

    :(

    Hmmm... We don't seem to have what you're looking for.

    - <% if authorized? %> - - <% else %> - - <% end %> - Ok fine, I'm outta here! - + Ok fine, I'm outta here!

    diff --git a/views/projects.erb b/views/projects.erb deleted file mode 100644 index 330dbe8..0000000 --- a/views/projects.erb +++ /dev/null @@ -1,22 +0,0 @@ -

    Projects

    - -<%== flash_message %> - -<% if @projects.empty? %> - -<% else %> - - - - - - <% first_project = true %> - <% @projects.each do |project| %> - - - - - <% first_project = false %> - <% end %> -
    NameCreated
    <%= project.name %><%= format_date(Time.at(project.created_timestamp)) %>
    -<% end %> diff --git a/views/reset-password.erb b/views/reset-password.erb index 6550586..35c3d3f 100644 --- a/views/reset-password.erb +++ b/views/reset-password.erb @@ -1,5 +1,5 @@ -

    Reset My Password

    +

    Reset My Password

    <%== flash_message %> diff --git a/views/sign-in.erb b/views/sign-in.erb index 6c19530..fc907d7 100644 --- a/views/sign-in.erb +++ b/views/sign-in.erb @@ -1,43 +1,40 @@ <%== flash_message %> -
    - +
    +
    +
    +
    - - - + +
    +
    + +
    +
    + +
    +
    + + +
    +
    + + Signing in... +
    +
    + - - - - + + + - - - - - - - - - - - - - - -
    - -
    - - -
    - - Signing in... -
    - Forgot your password? -
    +
    +

    Not a member yet? Sign Up! -

    - +

    +

    + Forgot your password? +

    +
    +
    diff --git a/views/sign-up.erb b/views/sign-up.erb index 8bf0a07..81f418c 100644 --- a/views/sign-up.erb +++ b/views/sign-up.erb @@ -1,5 +1,3 @@ -

    Please provide the following information in order to sign up.

    - <% if @errors %>