remove cruft, refactor
|
|
@ -3,22 +3,18 @@
|
||||||
A web server blueprint using Sinatra and Redis.
|
A web server blueprint using Sinatra and Redis.
|
||||||
|
|
||||||
Storm Weather is only a vague idea. Trying to nail down a lightweight blueprint for
|
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
|
## Models
|
||||||
|
|
||||||
### Account Model
|
### Account Model
|
||||||
|
|
||||||
### Admin Model
|
|
||||||
|
|
||||||
|
|
||||||
## Controllers
|
## Controllers
|
||||||
|
|
||||||
### Account Controller
|
### Account Controller
|
||||||
|
|
||||||
### Admin Controller
|
|
||||||
|
|
||||||
### Public Controller
|
### Public Controller
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
13
lib/class-ext.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Copyright 2012 Sami Samhuri <sami@samhuri.net>
|
||||||
|
|
||||||
|
class Class
|
||||||
|
|
||||||
|
def singleton
|
||||||
|
(class <<self; self end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def define_class_method(name, &body)
|
||||||
|
singleton.send(:define_method, name, &body)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
@ -9,6 +9,10 @@ require 'active_support/core_ext'
|
||||||
this_dir = File.dirname(__FILE__)
|
this_dir = File.dirname(__FILE__)
|
||||||
$LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir)
|
$LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir)
|
||||||
|
|
||||||
|
# Ruby extensions
|
||||||
|
require 'class-ext'
|
||||||
|
require 'hash-ext'
|
||||||
|
|
||||||
module Stormy
|
module Stormy
|
||||||
|
|
||||||
# key prefix for data stored in Redis (used for testing)
|
# key prefix for data stored in Redis (used for testing)
|
||||||
|
|
@ -16,12 +20,12 @@ module Stormy
|
||||||
KeyPrefix = ''
|
KeyPrefix = ''
|
||||||
end
|
end
|
||||||
|
|
||||||
# public directory for project photos
|
# public directory for photos
|
||||||
unless const_defined? :PhotoDir
|
unless const_defined? :PhotoDir
|
||||||
PhotoDir = File.expand_path('../public/photos', File.dirname(__FILE__))
|
PhotoDir = File.expand_path('../public/photos', File.dirname(__FILE__))
|
||||||
end
|
end
|
||||||
|
|
||||||
# public directory for project videos
|
# public directory for videos
|
||||||
unless const_defined? :VideoDir
|
unless const_defined? :VideoDir
|
||||||
VideoDir = File.expand_path('../public/videos', File.dirname(__FILE__))
|
VideoDir = File.expand_path('../public/videos', File.dirname(__FILE__))
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ module Stormy
|
||||||
class Server < Sinatra::Base
|
class Server < Sinatra::Base
|
||||||
|
|
||||||
get '/sign-up' do
|
get '/sign-up' do
|
||||||
redirect '/projects' if authorized? && production?
|
redirect '/account' if authorized? && production?
|
||||||
|
|
||||||
title 'Sign Up'
|
title 'Sign Up'
|
||||||
stylesheet 'sign-up'
|
stylesheet 'sign-up'
|
||||||
|
|
@ -26,9 +26,9 @@ module Stormy
|
||||||
@account = Account.create(fields)
|
@account = Account.create(fields)
|
||||||
authorize_account(@account.id)
|
authorize_account(@account.id)
|
||||||
send_verification_mail(@account, 'Welcome to Stormy Weather!')
|
send_verification_mail(@account, 'Welcome to Stormy Weather!')
|
||||||
redirect '/projects'
|
redirect '/account'
|
||||||
|
|
||||||
rescue Account::EmailTakenError => e
|
rescue Account::DuplicateFieldError => e
|
||||||
flash[:warning] = "That email address is already taken."
|
flash[:warning] = "That email address is already taken."
|
||||||
session[:fields] = fields
|
session[:fields] = fields
|
||||||
session[:fields]['terms'] = params['terms']
|
session[:fields]['terms'] = params['terms']
|
||||||
|
|
@ -48,7 +48,7 @@ module Stormy
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/sign-in' do
|
get '/sign-in' do
|
||||||
redirect '/projects' if authorized? && production?
|
redirect '/account' if authorized? && production?
|
||||||
|
|
||||||
title 'Sign In'
|
title 'Sign In'
|
||||||
stylesheet 'sign-in'
|
stylesheet 'sign-in'
|
||||||
|
|
@ -70,7 +70,7 @@ module Stormy
|
||||||
else
|
else
|
||||||
response.delete_cookie('remembered')
|
response.delete_cookie('remembered')
|
||||||
end
|
end
|
||||||
url = session.delete(:original_url) || '/projects'
|
url = session.delete(:original_url) || '/account'
|
||||||
redirect url
|
redirect url
|
||||||
else
|
else
|
||||||
flash[:warning] = "Incorrect email address or password."
|
flash[:warning] = "Incorrect email address or password."
|
||||||
|
|
@ -121,7 +121,7 @@ module Stormy
|
||||||
authorize!
|
authorize!
|
||||||
current_account.password = params['password']
|
current_account.password = params['password']
|
||||||
current_account.save!
|
current_account.save!
|
||||||
redirect '/projects'
|
redirect '/account'
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/account' do
|
get '/account' do
|
||||||
|
|
@ -186,7 +186,7 @@ module Stormy
|
||||||
current_account.update({ params['id'] => params['value'] })
|
current_account.update({ params['id'] => params['value'] })
|
||||||
end
|
end
|
||||||
ok
|
ok
|
||||||
rescue Account::EmailTakenError => e
|
rescue Account::DuplicateFieldError => e
|
||||||
fail('taken')
|
fail('taken')
|
||||||
rescue Account::InvalidDataError => e
|
rescue Account::InvalidDataError => e
|
||||||
fail('invalid')
|
fail('invalid')
|
||||||
|
|
|
||||||
|
|
@ -6,48 +6,7 @@ module Stormy
|
||||||
get '/admin' do
|
get '/admin' do
|
||||||
admin_authorize!
|
admin_authorize!
|
||||||
title "Dashboard"
|
title "Dashboard"
|
||||||
erb :'admin/dashboard', :layout => :'admin/layout'
|
erb :'admin/dashboard'
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -60,7 +19,7 @@ module Stormy
|
||||||
mark_last_listing
|
mark_last_listing
|
||||||
title "Accounts"
|
title "Accounts"
|
||||||
@accounts = Account.fetch_all.sort { |a,b| a.name <=> b.name }
|
@accounts = Account.fetch_all.sort { |a,b| a.name <=> b.name }
|
||||||
erb :'admin/accounts', :layout => :'admin/layout'
|
erb :'admin/accounts'
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/admin/account/:email' do |email|
|
get '/admin/account/:email' do |email|
|
||||||
|
|
@ -69,7 +28,7 @@ module Stormy
|
||||||
mark_last_listing
|
mark_last_listing
|
||||||
title "#{@account.name}'s Account"
|
title "#{@account.name}'s Account"
|
||||||
script 'admin-account'
|
script 'admin-account'
|
||||||
erb :'admin/account', :layout => :'admin/layout'
|
erb :'admin/account'
|
||||||
else
|
else
|
||||||
flash[:notice] = "No account with email #{email}"
|
flash[:notice] = "No account with email #{email}"
|
||||||
redirect last_listing
|
redirect last_listing
|
||||||
|
|
@ -79,7 +38,7 @@ module Stormy
|
||||||
get '/admin/sign-in-as/:email' do |email|
|
get '/admin/sign-in-as/:email' do |email|
|
||||||
admin_authorize!
|
admin_authorize!
|
||||||
authorize_account(Account.id_from_email(email))
|
authorize_account(Account.id_from_email(email))
|
||||||
redirect '/projects'
|
redirect '/account'
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/admin/account/:email/delete' do |email|
|
get '/admin/account/:email/delete' do |email|
|
||||||
|
|
@ -105,7 +64,7 @@ module Stormy
|
||||||
if new_email != @account.email
|
if new_email != @account.email
|
||||||
begin
|
begin
|
||||||
@account.update_email(new_email)
|
@account.update_email(new_email)
|
||||||
rescue Account::EmailTakenError => e
|
rescue Account::DuplicateFieldError => e
|
||||||
flash[:warning] = "That email address is already taken."
|
flash[:warning] = "That email address is already taken."
|
||||||
redirect '/admin/account/' + email
|
redirect '/admin/account/' + email
|
||||||
end
|
end
|
||||||
|
|
@ -124,38 +83,6 @@ module Stormy
|
||||||
end
|
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 ###
|
### FAQ ###
|
||||||
###########
|
###########
|
||||||
|
|
@ -170,7 +97,7 @@ module Stormy
|
||||||
<p class="answer">Yes my son.</p>
|
<p class="answer">Yes my son.</p>
|
||||||
EOT
|
EOT
|
||||||
end
|
end
|
||||||
erb :'admin/faq', :layout => :'admin/layout'
|
erb :'admin/faq'
|
||||||
end
|
end
|
||||||
|
|
||||||
post '/admin/faq' do
|
post '/admin/faq' do
|
||||||
|
|
@ -180,38 +107,5 @@ module Stormy
|
||||||
redirect '/admin/faq'
|
redirect '/admin/faq'
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -7,8 +7,6 @@ module Stormy
|
||||||
get '/' do
|
get '/' do
|
||||||
cache_control :public, :must_revalidate, :max_age => 60
|
cache_control :public, :must_revalidate, :max_age => 60
|
||||||
stylesheet 'index'
|
stylesheet 'index'
|
||||||
stylesheet 'jquery.lightbox-0.5'
|
|
||||||
script 'jquery.lightbox-0.5'
|
|
||||||
script 'index'
|
script 'index'
|
||||||
erb :index
|
erb :index
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ module Stormy
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_verification_mail(account = current_account, subject = nil)
|
def send_verification_mail(account = current_account, subject = nil)
|
||||||
account.create_email_verification_token
|
|
||||||
body = erb(:'email/email-verification', :layout => :'email/layout', :locals => {
|
body = erb(:'email/email-verification', :layout => :'email/layout', :locals => {
|
||||||
:name => account.first_name,
|
:name => account.first_name,
|
||||||
:email => account.email,
|
:email => account.email,
|
||||||
|
|
|
||||||
|
|
@ -10,21 +10,13 @@ module Stormy
|
||||||
Account.count
|
Account.count
|
||||||
end
|
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.
|
# Used to redirect back to the most recent list of things.
|
||||||
#
|
#
|
||||||
# i.e. someone goes to /admin -> /admin/account/foo -> /admin/project/007
|
# i.e. someone goes to /admin -> /admin/account/foo -> /admin/thing/007
|
||||||
# if they delete that project they should go back to /admin/account/foo
|
# if they delete that thing they should go back to /admin/account/foo
|
||||||
#
|
#
|
||||||
# however if they go /admin -> /admin/projects -> /admin/project/007
|
# however if they go /admin -> /admin/things -> /admin/thing/007
|
||||||
# and then delete that project they should go back to /admin/projects
|
# and then delete that thing they should go back to /admin/things
|
||||||
def last_listing
|
def last_listing
|
||||||
session.delete(:last_listing) || '/admin'
|
session.delete(:last_listing) || '/admin'
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -42,53 +42,14 @@ module Stormy
|
||||||
end
|
end
|
||||||
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?
|
def admin_authorized?
|
||||||
session[:admin_id] && Models::Admin.exists?(session[:admin_id])
|
authorized? && current_account.has_role?('admin')
|
||||||
end
|
end
|
||||||
|
|
||||||
def admin_authorize!
|
def admin_authorize!
|
||||||
unless admin_authorized?
|
unless admin_authorized?
|
||||||
session[:original_url] = request.url
|
session[:original_url] = request.url
|
||||||
redirect '/admin'
|
redirect '/sign-in'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -99,10 +60,6 @@ module Stormy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_admin
|
|
||||||
@current_admin ||= Models::Admin.fetch(session[:admin_id])
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
# Copyright 2012 Sami Samhuri <sami@samhuri.net>
|
# Copyright 2012 Sami Samhuri <sami@samhuri.net>
|
||||||
|
|
||||||
|
require 'json'
|
||||||
require 'stormy/config'
|
require 'stormy/config'
|
||||||
|
|
||||||
module Stormy
|
module Stormy
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,15 @@ module Stormy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def breadcrumbs
|
||||||
|
@breadcrumbs ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
def breadcrumb(crumb)
|
||||||
|
crumb[:path] ||= '/' + crumb[:name].downcase
|
||||||
|
breadcrumbs << crumb
|
||||||
|
end
|
||||||
|
|
||||||
def format_dollars(amount, currency = 'CAD')
|
def format_dollars(amount, currency = 'CAD')
|
||||||
'%s $%.2f' % [currency, amount / 100.0]
|
'%s $%.2f' % [currency, amount / 100.0]
|
||||||
end
|
end
|
||||||
|
|
@ -116,6 +125,10 @@ module Stormy
|
||||||
RDiscount.new(s.to_s).to_html
|
RDiscount.new(s.to_s).to_html
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def admin_page?(path = request.path_info)
|
||||||
|
path.starts_with?('/admin')
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -7,27 +7,26 @@ module Stormy
|
||||||
module Models
|
module Models
|
||||||
class Account < Base
|
class Account < Base
|
||||||
|
|
||||||
class EmailTakenError < RuntimeError; end
|
|
||||||
class IncorrectPasswordError < RuntimeError; end
|
class IncorrectPasswordError < RuntimeError; end
|
||||||
|
|
||||||
name 'account'
|
Roles = %w[user admin]
|
||||||
|
|
||||||
|
model_name 'account'
|
||||||
|
|
||||||
field :id, :required => true
|
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 :first_name, :required => true, :updatable => true
|
||||||
field :last_name, :required => true, :updatable => true
|
field :last_name, :required => true, :updatable => true
|
||||||
field :phone, :type => :phone, :updatable => true
|
field :phone, :type => :phone, :updatable => true
|
||||||
|
|
||||||
field :hashed_password, :required => true
|
|
||||||
field :password
|
|
||||||
|
|
||||||
field :created_timestamp, :type => :integer
|
field :created_timestamp, :type => :integer
|
||||||
field :email_verification_token, :nullify_if_blank => true
|
field :email_verification_token, :nullify_if_blank => true
|
||||||
field :email_verified?
|
field :email_verified?
|
||||||
|
field :hashed_password, :required => true
|
||||||
|
field :password
|
||||||
field :password_reset_token, :nullify_if_blank => true
|
field :password_reset_token, :nullify_if_blank => true
|
||||||
|
field :role, :required => true
|
||||||
@@account_email_index_key = Stormy.key('index:account-email')
|
|
||||||
|
|
||||||
|
|
||||||
### Class Methods
|
### Class Methods
|
||||||
|
|
@ -41,18 +40,8 @@ module Stormy
|
||||||
end
|
end
|
||||||
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)
|
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')
|
token = redis.hget(key, 'password_reset_token')
|
||||||
if token.blank?
|
if token.blank?
|
||||||
token = UUID.generate
|
token = UUID.generate
|
||||||
|
|
@ -75,12 +64,8 @@ module Stormy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.id_from_email(email)
|
|
||||||
redis.hget(@@account_email_index_key, email.strip.downcase)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.verify_email(email, token)
|
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')
|
expected_token = redis.hget(key, 'email_verification_token')
|
||||||
verified = token == expected_token
|
verified = token == expected_token
|
||||||
if verified
|
if verified
|
||||||
|
|
@ -92,21 +77,12 @@ module Stormy
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.email_verified?(email)
|
def self.email_verified?(email)
|
||||||
if key = key_from_email(email)
|
if key = key(id_from_email(email))
|
||||||
redis.hget(key, 'email_verified') == 'true'
|
redis.hget(key, 'email_verified') == 'true'
|
||||||
end
|
end
|
||||||
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
|
### Instance Methods
|
||||||
|
|
||||||
def initialize(fields = {}, options = {})
|
def initialize(fields = {}, options = {})
|
||||||
|
|
@ -120,36 +96,20 @@ module Stormy
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
raise EmailTakenError if email_taken?
|
self.role = 'user' if role.blank?
|
||||||
|
|
||||||
# new accounts get an id and timestamp
|
|
||||||
self.id = UUID.generate unless id.present?
|
|
||||||
self.created_timestamp = Time.now.to_i
|
|
||||||
|
|
||||||
super
|
super
|
||||||
|
|
||||||
create_email_verification_token
|
|
||||||
|
|
||||||
# add to index
|
|
||||||
redis.hset(@@account_email_index_key, email.downcase, id)
|
|
||||||
|
|
||||||
self
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete!
|
def has_role?(role)
|
||||||
project_ids.each { |id| Project.delete!(id) }
|
Roles.index(self.role) >= Roles.index(role)
|
||||||
super
|
|
||||||
redis.hdel(@@account_email_index_key, email.strip.downcase)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_taken?(email = @email)
|
def email_verification_token
|
||||||
self.class.email_taken?(email)
|
unless @email_verification_token
|
||||||
end
|
@email_verification_token = UUID.generate
|
||||||
|
redis.hset(key, 'email_verification_token', @email_verification_token)
|
||||||
def create_email_verification_token
|
end
|
||||||
self.email_verification_token ||= UUID.generate
|
@email_verification_token
|
||||||
redis.hset(key, 'email_verification_token', email_verification_token)
|
|
||||||
email_verification_token
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def password
|
def password
|
||||||
|
|
@ -167,41 +127,16 @@ module Stormy
|
||||||
"#{first_name} #{last_name}"
|
"#{first_name} #{last_name}"
|
||||||
end
|
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)
|
def update_email(new_email)
|
||||||
new_email = new_email.strip
|
new_email = new_email.strip
|
||||||
if email != new_email
|
if email != new_email
|
||||||
raise EmailTakenError if new_email.downcase != email.downcase && email_taken?(new_email)
|
changed = new_email.downcase != email.downcase
|
||||||
raise InvalidDataError.new({ 'email' => 'invalid' }) unless field_valid?('email', new_email)
|
raise DuplicateFieldError.new(:email => new_email) if changed && email_taken?(new_email)
|
||||||
if email.downcase != new_email.downcase
|
raise InvalidDataError.new('email' => 'invalid') unless field_valid?('email', new_email)
|
||||||
self.email_verified = false
|
self.email_verified = false if changed
|
||||||
redis.hdel(@@account_email_index_key, email.downcase)
|
remove_from_field_index(:email) if changed
|
||||||
redis.hset(@@account_email_index_key, new_email.downcase, id)
|
|
||||||
end
|
|
||||||
self.email = new_email
|
self.email = new_email
|
||||||
|
add_to_field_index(:email) if changed
|
||||||
save!
|
save!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -214,13 +149,6 @@ module Stormy
|
||||||
self.password = new_password
|
self.password = new_password
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def project_ids_key
|
|
||||||
@project_ids_key ||= "#{key}:project-ids"
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
# Copyright 2012 Sami Samhuri <sami@samhuri.net>
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
@ -15,11 +15,18 @@ module Stormy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class DuplicateFieldError < RuntimeError
|
||||||
|
attr_reader :field
|
||||||
|
def initialize(field = nil)
|
||||||
|
@field = field
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.clean_number(number)
|
def self.clean_number(number)
|
||||||
number.gsub(/[^\d]/, '').sub(/^1/, '')
|
number.gsub(/[^\d]/, '').sub(/^1/, '')
|
||||||
end
|
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|
|
PhoneNumberValidator = proc do |number|
|
||||||
if number.present?
|
if number.present?
|
||||||
clean_number(number).length == 10
|
clean_number(number).length == 10
|
||||||
|
|
@ -48,7 +55,7 @@ module Stormy
|
||||||
|
|
||||||
|
|
||||||
# Define or retrieve the name of this model.
|
# Define or retrieve the name of this model.
|
||||||
def self.name(name = nil)
|
def self.model_name(name = nil)
|
||||||
if name
|
if name
|
||||||
@model_name = name
|
@model_name = name
|
||||||
end
|
end
|
||||||
|
|
@ -61,20 +68,33 @@ module Stormy
|
||||||
@fields ||= {}
|
@fields ||= {}
|
||||||
end
|
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:
|
# Define fields like so:
|
||||||
#
|
#
|
||||||
# field :id, :type => :integer, :required => true
|
# 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?
|
# field :verified?
|
||||||
#
|
#
|
||||||
# Defaults: {
|
# Defaults: {
|
||||||
# :type => :string,
|
# :type => :string,
|
||||||
# :required => false,
|
# :required => false,
|
||||||
# :updatable => false,
|
# :updatable => false,
|
||||||
|
# :accessors => true,
|
||||||
# :validator => nil, # with some exceptions
|
# :validator => nil, # with some exceptions
|
||||||
# :default => {},
|
# :default => {},
|
||||||
# :nullify_if_blank => false
|
# :nullify_if_blank => false,
|
||||||
|
# :indexed => false,
|
||||||
|
# :unique => false
|
||||||
# }
|
# }
|
||||||
#
|
#
|
||||||
# Types: :string, :integer, :boolean, :json, as well as
|
# Types: :string, :integer, :boolean, :json, as well as
|
||||||
|
|
@ -91,12 +111,26 @@ module Stormy
|
||||||
# JSON fields accept a :default option used to initialize
|
# JSON fields accept a :default option used to initialize
|
||||||
# a JSON field, and also when a parse fails.
|
# a JSON field, and also when a parse fails.
|
||||||
#
|
#
|
||||||
# Attribute accessors are defined for each field and boolean
|
# Unless :accessors is false, attribute accessors are
|
||||||
# fields get a predicate method as well, e.g. verified?
|
# defined for each field. Boolean fields get a predicate
|
||||||
|
# method as well, e.g. verified?
|
||||||
#
|
#
|
||||||
# Changed fields are tracked and only changed fields are
|
# Changed fields are tracked and only changed fields are
|
||||||
# persisted on a `save`.
|
# 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 = {})
|
def self.field(name, options = {})
|
||||||
if name.to_s.ends_with?('?')
|
if name.to_s.ends_with?('?')
|
||||||
options[:type] = :boolean
|
options[:type] = :boolean
|
||||||
|
|
@ -106,79 +140,182 @@ module Stormy
|
||||||
name = name.to_sym
|
name = name.to_sym
|
||||||
options[:type] ||= :string
|
options[:type] ||= :string
|
||||||
|
|
||||||
|
unless options.has_key?(:accessors)
|
||||||
|
options[:accessors] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if options[:unique]
|
||||||
|
options[:indexed] = true
|
||||||
|
end
|
||||||
|
|
||||||
case options[:type]
|
case options[:type]
|
||||||
when :email
|
when :email
|
||||||
options[:validator] ||= EmailAddressValidator
|
options[:validator] = EmailAddressValidator unless options.has_key?(:validator)
|
||||||
options[:type] = :string
|
options[:type] = :string
|
||||||
when :phone
|
when :phone
|
||||||
options[:validator] ||= PhoneNumberValidator
|
options[:validator] = PhoneNumberValidator unless options.has_key?(:validator)
|
||||||
options[:type] = :string
|
options[:type] = :string
|
||||||
when :json
|
when :json
|
||||||
options[:default] ||= {}
|
options[:default] = {} unless options.has_key?(:default)
|
||||||
end
|
end
|
||||||
|
|
||||||
fields[name] = options
|
fields[name] = options
|
||||||
define_method(name) do
|
|
||||||
instance_variable_get("@#{name}")
|
|
||||||
end
|
|
||||||
|
|
||||||
case options[:type]
|
if options[:accessors]
|
||||||
when :string
|
define_method(name) do
|
||||||
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
|
|
||||||
instance_variable_get("@#{name}")
|
instance_variable_get("@#{name}")
|
||||||
end
|
end
|
||||||
|
|
||||||
when :json
|
case options[:type]
|
||||||
define_method(name) do
|
when :string
|
||||||
unless value = instance_variable_get("@#{name}")
|
define_method("#{name}=") do |value|
|
||||||
value = options[:default].dup
|
s =
|
||||||
send("#{name}=", value)
|
if options[:nullify_if_blank] && value.blank?
|
||||||
end
|
nil
|
||||||
value
|
|
||||||
end
|
|
||||||
define_method("#{name}=") do |value|
|
|
||||||
obj =
|
|
||||||
if value.is_a?(String)
|
|
||||||
if value.length > 0
|
|
||||||
JSON.parse(value)
|
|
||||||
else
|
else
|
||||||
options[:default].dup
|
value.to_s.strip
|
||||||
end
|
end
|
||||||
else
|
instance_variable_set("@#{name}", s)
|
||||||
value
|
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
|
end
|
||||||
instance_variable_set("@#{name}", obj)
|
value
|
||||||
changed_fields[name] = obj
|
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
|
end
|
||||||
|
|
||||||
else
|
define_class_method("id_from_#{name}") do |value|
|
||||||
define_method("#{name}=") do |value|
|
redis.hget(send(index_key_method_name), value.to_s.strip.downcase)
|
||||||
instance_variable_set("@#{name}", value)
|
end
|
||||||
changed_fields[name] = value
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -246,14 +383,46 @@ module Stormy
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
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
|
# raises if invalid
|
||||||
save
|
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
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete!
|
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)
|
redis.del(key)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -280,6 +449,12 @@ module Stormy
|
||||||
options[:validate] = true unless options.has_key?(:validate)
|
options[:validate] = true unless options.has_key?(:validate)
|
||||||
fields.each do |name, value|
|
fields.each do |name, value|
|
||||||
if options[:all] || field_updatable?(name)
|
if options[:all] || field_updatable?(name)
|
||||||
|
|
||||||
|
# ensure uniqueness
|
||||||
|
if options[:unique] && send("#{name}_taken?", value)
|
||||||
|
raise DuplicateFieldError.new(name => value)
|
||||||
|
end
|
||||||
|
|
||||||
send("#{name}=", value)
|
send("#{name}=", value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -296,6 +471,10 @@ module Stormy
|
||||||
end
|
end
|
||||||
|
|
||||||
def save!
|
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
|
# always update JSON fields because they can be updated without our knowledge
|
||||||
field_names.each do |name|
|
field_names.each do |name|
|
||||||
if field_type(name) == :json
|
if field_type(name) == :json
|
||||||
|
|
@ -317,6 +496,7 @@ module Stormy
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate
|
def validate
|
||||||
|
# check for invalid fields
|
||||||
invalid_fields = field_names.inject({}) do |fields, name|
|
invalid_fields = field_names.inject({}) do |fields, name|
|
||||||
if field_validates?(name)
|
if field_validates?(name)
|
||||||
result = validate_field(name, send(name))
|
result = validate_field(name, send(name))
|
||||||
|
|
@ -329,6 +509,10 @@ module Stormy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fields
|
||||||
|
Hash[field_names.zip(field_names.map { |name| send(name) })]
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
|
@ -336,8 +520,39 @@ module Stormy
|
||||||
@key ||= self.class.key(self.id)
|
@key ||= self.class.key(self.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_to_index
|
def model_name
|
||||||
redis.sadd(self.class.model_ids_key, self.id)
|
@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
|
end
|
||||||
|
|
||||||
def changed_fields
|
def changed_fields
|
||||||
|
|
@ -348,6 +563,10 @@ module Stormy
|
||||||
self.class.clean_number(number)
|
self.class.clean_number(number)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def has_field?(name)
|
||||||
|
self.class.fields.has_key?(name.to_sym)
|
||||||
|
end
|
||||||
|
|
||||||
def field_names
|
def field_names
|
||||||
self.class.fields.keys
|
self.class.fields.keys
|
||||||
end
|
end
|
||||||
|
|
@ -360,6 +579,10 @@ module Stormy
|
||||||
self.class.fields[name.to_sym][:updatable]
|
self.class.fields[name.to_sym][:updatable]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def field_required?(name)
|
||||||
|
self.class.fields[name.to_sym][:required]
|
||||||
|
end
|
||||||
|
|
||||||
def validate_field(name, value)
|
def validate_field(name, value)
|
||||||
valid = true
|
valid = true
|
||||||
reason = nil
|
reason = nil
|
||||||
|
|
|
||||||
|
|
@ -1,162 +0,0 @@
|
||||||
# Copyright 2012 Sami Samhuri <sami@samhuri.net>
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
@ -7,15 +7,11 @@ require 'sinatra/cookie_thief'
|
||||||
require 'sinatra/flash'
|
require 'sinatra/flash'
|
||||||
|
|
||||||
require 'erubis'
|
require 'erubis'
|
||||||
require 'json'
|
|
||||||
require 'pony'
|
require 'pony'
|
||||||
require 'redis'
|
require 'redis'
|
||||||
require 'redis-store'
|
require 'redis-store'
|
||||||
require 'uuid'
|
require 'uuid'
|
||||||
|
|
||||||
# Ruby extensions
|
|
||||||
require 'hash-ext'
|
|
||||||
|
|
||||||
require 'stormy/models'
|
require 'stormy/models'
|
||||||
require 'stormy/controllers'
|
require 'stormy/controllers'
|
||||||
require 'stormy/helpers'
|
require 'stormy/helpers'
|
||||||
|
|
@ -74,8 +70,6 @@ module Stormy
|
||||||
if production?
|
if production?
|
||||||
body = erb(:'email/error-notification', :layout => false, :locals => {
|
body = erb(:'email/error-notification', :layout => false, :locals => {
|
||||||
:account => current_account,
|
:account => current_account,
|
||||||
:project => current_project,
|
|
||||||
:admin => current_admin,
|
|
||||||
:error => env['sinatra.error']
|
:error => env['sinatra.error']
|
||||||
})
|
})
|
||||||
Pony.mail({
|
Pony.mail({
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 43 B |
|
Before Width: | Height: | Size: 700 B |
|
Before Width: | Height: | Size: 812 B |
|
Before Width: | Height: | Size: 832 B |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 4 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
|
@ -1,9 +1,9 @@
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
||||||
$('#change-password-link').click(function() {
|
$('#change-password-link').click(function() {
|
||||||
$(this).hide()
|
$(this).addClass('hidden')
|
||||||
$('#change-password').show()
|
$('#change-password').removeClass('hidden')
|
||||||
$('#password-changed').hide()
|
$('#password-changed').addClass('hidden')
|
||||||
$('#old-password').focus()
|
$('#old-password').focus()
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
@ -14,8 +14,8 @@ $(function() {
|
||||||
})
|
})
|
||||||
|
|
||||||
$('#send-email-verification').click(function() {
|
$('#send-email-verification').click(function() {
|
||||||
$('#sending-email-verification').show()
|
$('#sending-email-verification').removeClass('hidden')
|
||||||
$(this).hide()
|
$(this).addClass('hidden')
|
||||||
var self = this
|
var self = this
|
||||||
$.post('/account/send-email-verification', function(data) {
|
$.post('/account/send-email-verification', function(data) {
|
||||||
if (data.status === 'ok') {
|
if (data.status === 'ok') {
|
||||||
|
|
@ -29,8 +29,8 @@ $(function() {
|
||||||
}).error(function() {
|
}).error(function() {
|
||||||
alert('Failed to send verification email. Try again later.')
|
alert('Failed to send verification email. Try again later.')
|
||||||
}).complete(function() {
|
}).complete(function() {
|
||||||
$('#sending-email-verification').hide()
|
$('#sending-email-verification').addClass('hidden')
|
||||||
$(self).show()
|
$(self).removeClass('hidden')
|
||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
@ -43,14 +43,14 @@ function changePassword() {
|
||||||
, confirmation = $('#password-confirmation').val()
|
, confirmation = $('#password-confirmation').val()
|
||||||
if ($.trim(oldPassword) && $.trim(newPassword) && newPassword === confirmation) {
|
if ($.trim(oldPassword) && $.trim(newPassword) && newPassword === confirmation) {
|
||||||
$('#change-password-form input[type="password"]').removeClass('error')
|
$('#change-password-form input[type="password"]').removeClass('error')
|
||||||
$('#change-password-form input[type="submit"]').hide()
|
$('#change-password-form input[type="submit"]').addClass('hidden')
|
||||||
$('#change-password-form .spinner').show()
|
$('#change-password-spinner').removeClass('hidden')
|
||||||
$.post('/account/password', $('#change-password-form').serialize(), function(data) {
|
$.post('/account/password', $('#change-password-form').serialize(), function(data) {
|
||||||
if (data.status === 'ok') {
|
if (data.status === 'ok') {
|
||||||
$('input[type="password"]').val('')
|
$('input[type="password"]').val('')
|
||||||
$('#change-password').hide()
|
$('#change-password').addClass('hidden')
|
||||||
$('#change-password-link').show()
|
$('#change-password-link').removeClass('hidden')
|
||||||
$('#password-changed').show()
|
$('#password-changed').removeClass('hidden')
|
||||||
}
|
}
|
||||||
// incorrect old password
|
// incorrect old password
|
||||||
else if (data.reason === 'incorrect') {
|
else if (data.reason === 'incorrect') {
|
||||||
|
|
@ -72,8 +72,8 @@ function changePassword() {
|
||||||
}).error(function(x) {
|
}).error(function(x) {
|
||||||
alert('Failed to change password. Try again later.')
|
alert('Failed to change password. Try again later.')
|
||||||
}).complete(function() {
|
}).complete(function() {
|
||||||
$('#change-password-form input[type="submit"]').show()
|
$('#change-password-form input[type="submit"]').removeClass('hidden')
|
||||||
$('#change-password-form .spinner').hide()
|
$('#change-password-spinner').addClass('hidden')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
$(function() {
|
|
||||||
|
|
||||||
$('#delete').click(function() {
|
|
||||||
return confirm("Are you sure?")
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
@ -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: '<li class="placeholder"><div></div></li>'
|
|
||||||
, 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('<li id="add-photo-spinner"><img src="/images/spinner.gif"></li>')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
$('.add-photo').click(function() {
|
|
||||||
$photoUploader.focus().click()
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
$photoUploader.change(function() {
|
|
||||||
$addPhotoBox.addClass('hidden').before('<li id="add-photo-spinner"><img src="/images/spinner.gif"></li>')
|
|
||||||
$('#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('<img id="' + spinnerId + '" src="/images/spinner.gif">')
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -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: "<li> </li>",
|
|
||||||
scrollContainer: window,
|
|
||||||
scrollSpeed: 5
|
|
||||||
};
|
|
||||||
|
|
||||||
})(jQuery);
|
|
||||||
|
|
@ -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:
|
|
||||||
<div id="jquery-overlay"></div>
|
|
||||||
<div id="jquery-lightbox">
|
|
||||||
<div id="lightbox-container-image-box">
|
|
||||||
<div id="lightbox-container-image">
|
|
||||||
<img src="../fotos/XX.jpg" id="lightbox-image">
|
|
||||||
<div id="lightbox-nav">
|
|
||||||
<a href="#" id="lightbox-nav-btnPrev"></a>
|
|
||||||
<a href="#" id="lightbox-nav-btnNext"></a>
|
|
||||||
</div>
|
|
||||||
<div id="lightbox-loading">
|
|
||||||
<a href="#" id="lightbox-loading-link">
|
|
||||||
<img src="/images/lightbox-ico-loading.gif">
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="lightbox-container-image-data-box">
|
|
||||||
<div id="lightbox-container-image-data">
|
|
||||||
<div id="lightbox-image-details">
|
|
||||||
<span id="lightbox-image-details-caption"></span>
|
|
||||||
<span id="lightbox-image-details-currentNumber"></span>
|
|
||||||
</div>
|
|
||||||
<div id="lightbox-secNav">
|
|
||||||
<a href="#" id="lightbox-secNav-btnClose">
|
|
||||||
<img src="/images/lightbox-btn-close.gif">
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function _set_interface() {
|
|
||||||
// Apply the HTML markup into body tag
|
|
||||||
$('body').append('<div id="jquery-overlay"></div><div id="jquery-lightbox"><div id="lightbox-container-image-box"><div id="lightbox-container-image"><img id="lightbox-image"><div style="" id="lightbox-nav"><a href="#" id="lightbox-nav-btnPrev"></a><a href="#" id="lightbox-nav-btnNext"></a></div><div id="lightbox-loading"><a href="#" id="lightbox-loading-link"><img src="' + settings.imageLoading + '"></a></div></div></div><div id="lightbox-container-image-data-box"><div id="lightbox-container-image-data"><div id="lightbox-image-details"><span id="lightbox-image-details-caption"></span><span id="lightbox-image-details-currentNumber"></span></div><div id="lightbox-secNav"><a href="#" id="lightbox-secNav-btnClose"><img src="' + settings.imageBtnClose + '"></a></div></div></div></div>');
|
|
||||||
// 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
|
|
||||||
|
|
@ -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('<div id="' + jQuery(this).attr('id') + 'Uploader"></div>');
|
|
||||||
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('<div id="' + jQuery(this).attr('id') + 'Queue" class="uploadifyQueue"></div>');
|
|
||||||
} 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('<div id="' + jQuery(this).attr('id') + ID + '" class="uploadifyQueueItem">\
|
|
||||||
<div class="cancel">\
|
|
||||||
<a href="javascript:jQuery(\'#' + jQuery(this).attr('id') + '\').uploadifyCancel(\'' + ID + '\')"><img src="' + settings.cancelImg + '" border="0" /></a>\
|
|
||||||
</div>\
|
|
||||||
<span class="fileName">' + fileName + ' (' + byteSize + suffix + ')</span><span class="percentage"></span>\
|
|
||||||
<div class="uploadifyProgress">\
|
|
||||||
<div id="' + jQuery(this).attr('id') + ID + 'ProgressBar" class="uploadifyProgressBar"><!--Progress Bar--></div>\
|
|
||||||
</div>\
|
|
||||||
</div>');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
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);
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
$(function() {
|
|
||||||
|
|
||||||
// this space intentionally left blank
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
||||||
$('input[name="email"]').focus()
|
|
||||||
|
|
||||||
$('#forgot-password-link').click(function() {
|
$('#forgot-password-link').click(function() {
|
||||||
window.location.href = '/forgot-password/' + $('#email').val()
|
window.location.href = '/forgot-password/' + $('#email').val()
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 2.9 KiB |
|
|
@ -237,49 +237,6 @@ module Stormy
|
||||||
|
|
||||||
end # Accounts
|
end # Accounts
|
||||||
|
|
||||||
|
|
||||||
module Admins
|
|
||||||
|
|
||||||
include Stormy::Models
|
|
||||||
|
|
||||||
def setup_admins
|
|
||||||
@admin_data ||= fixtures('admins')['sami']
|
|
||||||
@admin = Admin.create(@admin_data)
|
|
||||||
end
|
|
||||||
|
|
||||||
def teardown_admins
|
|
||||||
@admin.delete!
|
|
||||||
end
|
|
||||||
|
|
||||||
end # Admins
|
|
||||||
|
|
||||||
module Projects
|
|
||||||
|
|
||||||
include Stormy::Models
|
|
||||||
|
|
||||||
def projects
|
|
||||||
@projects ||= fixtures('projects')
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup_projects(options = {})
|
|
||||||
account = options[:owner] || @existing_account
|
|
||||||
@existing_project_data = projects['stormy']
|
|
||||||
if account
|
|
||||||
@existing_project_data['account_id'] = account.id
|
|
||||||
end
|
|
||||||
@existing_project = Project.create(@existing_project_data)
|
|
||||||
|
|
||||||
@new_project_data = projects['dating-free']
|
|
||||||
end
|
|
||||||
|
|
||||||
def teardown_projects
|
|
||||||
Project.list_ids.each do |id|
|
|
||||||
Project.delete!(id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end # Projects
|
|
||||||
|
|
||||||
end # Helpers
|
end # Helpers
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ require 'common'
|
||||||
class AccountsControllerTest < Stormy::Test::ControllerCase
|
class AccountsControllerTest < Stormy::Test::ControllerCase
|
||||||
|
|
||||||
include Stormy::Test::Helpers::Accounts
|
include Stormy::Test::Helpers::Accounts
|
||||||
include Stormy::Test::Helpers::Projects
|
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
setup_accounts
|
setup_accounts
|
||||||
|
|
@ -32,7 +31,7 @@ class AccountsControllerTest < Stormy::Test::ControllerCase
|
||||||
|
|
||||||
def test_sign_up_with_valid_data
|
def test_sign_up_with_valid_data
|
||||||
post '/sign-up', @account_data
|
post '/sign-up', @account_data
|
||||||
assert_redirected '/projects'
|
assert_redirected '/account'
|
||||||
assert_equal 1, Pony.sent_mail.length
|
assert_equal 1, Pony.sent_mail.length
|
||||||
assert mail = Pony.sent_mail.shift
|
assert mail = Pony.sent_mail.shift
|
||||||
end
|
end
|
||||||
|
|
@ -80,17 +79,17 @@ class AccountsControllerTest < Stormy::Test::ControllerCase
|
||||||
|
|
||||||
def test_sign_in_submit
|
def test_sign_in_submit
|
||||||
sign_in
|
sign_in
|
||||||
assert_redirected '/projects'
|
assert_redirected '/account'
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_sign_in_remember
|
def test_sign_in_remember
|
||||||
sign_in(@existing_account_data, 'remember' => 'on')
|
sign_in(@existing_account_data, 'remember' => 'on')
|
||||||
assert_redirected '/projects'
|
assert_redirected '/account'
|
||||||
|
|
||||||
post '/sign-out'
|
post '/sign-out'
|
||||||
assert_redirected '/'
|
assert_redirected '/'
|
||||||
|
|
||||||
get '/projects'
|
get '/account'
|
||||||
assert_ok
|
assert_ok
|
||||||
|
|
||||||
# deletes remembered cookie
|
# deletes remembered cookie
|
||||||
|
|
@ -154,7 +153,7 @@ class AccountsControllerTest < Stormy::Test::ControllerCase
|
||||||
|
|
||||||
new_password = 'new password'
|
new_password = 'new password'
|
||||||
post '/account/reset-password', { '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_equal @existing_account.id, Account.check_password(@existing_account.email, new_password)
|
||||||
assert Account.fetch(@existing_account.id).password == new_password
|
assert Account.fetch(@existing_account.id).password == new_password
|
||||||
|
|
||||||
|
|
@ -256,7 +255,7 @@ class AccountsControllerTest < Stormy::Test::ControllerCase
|
||||||
def test_verify_email
|
def test_verify_email
|
||||||
get "/account/verify/#{@existing_account.email}/#{@existing_account.email_verification_token}"
|
get "/account/verify/#{@existing_account.email}/#{@existing_account.email_verification_token}"
|
||||||
assert_redirected '/account'
|
assert_redirected '/account'
|
||||||
assert_nil Account.fetch(@existing_account.id).email_verification_token
|
assert redis.hget(@existing_account.id, 'email_verification_token').blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_verify_email_with_invalid_token_signed_in
|
def test_verify_email_with_invalid_token_signed_in
|
||||||
|
|
|
||||||
|
|
@ -7,89 +7,33 @@ require 'common'
|
||||||
class AdminControllerTest < Stormy::Test::ControllerCase
|
class AdminControllerTest < Stormy::Test::ControllerCase
|
||||||
|
|
||||||
include Stormy::Test::Helpers::Accounts
|
include Stormy::Test::Helpers::Accounts
|
||||||
include Stormy::Test::Helpers::Projects
|
|
||||||
include Stormy::Helpers::Admin
|
include Stormy::Helpers::Admin
|
||||||
include Stormy::Helpers::FAQ
|
include Stormy::Helpers::FAQ
|
||||||
include Stormy::Helpers::Utils
|
include Stormy::Helpers::Utils
|
||||||
|
|
||||||
def admins
|
|
||||||
@admins ||= fixtures('admins')
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@existing_admin_data = admins['sami']
|
setup_accounts
|
||||||
@existing_admin = Admin.create(@existing_admin_data)
|
|
||||||
|
|
||||||
@admin_data = admins['freddy']
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def teardown
|
def teardown
|
||||||
post '/admin/sign-out'
|
post '/sign-out'
|
||||||
Admin.list_ids.each do |id|
|
teardown_accounts
|
||||||
Admin.delete!(id)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def sign_in(admin = @existing_admin_data)
|
def sign_in(admin = @existing_account_data)
|
||||||
post '/admin/sign-in', admin
|
post '/sign-in', admin
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
#####################
|
#################
|
||||||
### Sign In & Out ###
|
### Dashboard ###
|
||||||
#####################
|
#################
|
||||||
|
|
||||||
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 ###
|
|
||||||
############################
|
|
||||||
|
|
||||||
def test_dashboard
|
def test_dashboard
|
||||||
sign_in
|
sign_in
|
||||||
get '/admin'
|
get '/admin'
|
||||||
assert_ok
|
assert_ok
|
||||||
assert last_response.body.match(/<title>Dashboard/)
|
assert last_response.body.match(/<title>[^<]*Dashboard[^<]*<\/title>/)
|
||||||
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'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -104,7 +48,6 @@ class AdminControllerTest < Stormy::Test::ControllerCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_account
|
def test_account
|
||||||
setup_accounts
|
|
||||||
sign_in
|
sign_in
|
||||||
|
|
||||||
get '/admin/account/' + @existing_account.email
|
get '/admin/account/' + @existing_account.email
|
||||||
|
|
@ -113,12 +56,9 @@ class AdminControllerTest < Stormy::Test::ControllerCase
|
||||||
get '/admin/account/not@an.account'
|
get '/admin/account/not@an.account'
|
||||||
# this was the previous listing, kind of weird but meh
|
# this was the previous listing, kind of weird but meh
|
||||||
assert_redirected '/admin/account/' + @existing_account.email
|
assert_redirected '/admin/account/' + @existing_account.email
|
||||||
|
|
||||||
teardown_accounts
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_update_account
|
def test_update_account
|
||||||
setup_accounts
|
|
||||||
sign_in
|
sign_in
|
||||||
|
|
||||||
# redirected to proper page when changing email addresses
|
# 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 'Samson', @existing_account.first_name
|
||||||
assert_equal 'Simpson', @existing_account.last_name
|
assert_equal 'Simpson', @existing_account.last_name
|
||||||
assert_equal '+12501234567', @existing_account.phone
|
assert_equal '+12501234567', @existing_account.phone
|
||||||
|
|
||||||
teardown_accounts
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_sign_in_as_user
|
def test_sign_in_as_user
|
||||||
setup_accounts
|
|
||||||
sign_in
|
sign_in
|
||||||
|
|
||||||
get '/admin/sign-in-as/' + @existing_account.email
|
get '/admin/sign-in-as/' + @existing_account.email
|
||||||
assert_equal @existing_account.id, session[:id]
|
assert_equal @existing_account.id, session[:id]
|
||||||
assert_redirected '/projects'
|
assert_redirected '/account'
|
||||||
|
|
||||||
teardown_accounts
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_delete_account
|
def test_delete_account
|
||||||
setup_accounts
|
|
||||||
setup_projects
|
|
||||||
sign_in
|
sign_in
|
||||||
|
|
||||||
# make sure the last listing is marked so we are redirected correctly
|
# make sure the last listing is marked so we are redirected correctly
|
||||||
get '/admin/accounts'
|
get '/admin/accounts'
|
||||||
assert_ok
|
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_redirected '/admin/accounts'
|
||||||
|
|
||||||
assert_nil Account.fetch(@existing_account.id)
|
assert_nil Account.fetch(@other_account.id)
|
||||||
assert_nil Project.fetch(@existing_project.id)
|
|
||||||
|
|
||||||
# non-existent accounts are already gone, so no problem
|
# non-existent accounts are already gone, so no problem
|
||||||
get "/admin/account/nobody@nowhere.net/delete"
|
get "/admin/account/nobody@nowhere.net/delete"
|
||||||
|
|
||||||
# this time the last listing was not marked, so we are redirected to the dashboard
|
# this time the last listing was not marked, so we are redirected to the dashboard
|
||||||
assert_redirected '/admin'
|
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
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -276,51 +156,4 @@ class AdminControllerTest < Stormy::Test::ControllerCase
|
||||||
self.faq = original_faq
|
self.faq = original_faq
|
||||||
end
|
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
|
end
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
2
test/fixtures/accounts.json
vendored
|
|
@ -4,6 +4,7 @@
|
||||||
, "email" : "sami@example.com"
|
, "email" : "sami@example.com"
|
||||||
, "phone" : "250-216-6216"
|
, "phone" : "250-216-6216"
|
||||||
, "password" : "super secret"
|
, "password" : "super secret"
|
||||||
|
, "role" : "admin"
|
||||||
}
|
}
|
||||||
|
|
||||||
, "freddy": {
|
, "freddy": {
|
||||||
|
|
@ -12,5 +13,6 @@
|
||||||
, "email" : "freddy@example.com"
|
, "email" : "freddy@example.com"
|
||||||
, "phone" : "(250) 555-9999"
|
, "phone" : "(250) 555-9999"
|
||||||
, "password" : "even more secreter"
|
, "password" : "even more secreter"
|
||||||
|
, "role" : "user"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
test/fixtures/admins.json
vendored
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
14
test/fixtures/projects.json
vendored
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,6 @@ class AccountsHelperTest < Stormy::Test::HelperCase
|
||||||
|
|
||||||
include Stormy::Helpers::Accounts
|
include Stormy::Helpers::Accounts
|
||||||
include Stormy::Test::Helpers::Accounts
|
include Stormy::Test::Helpers::Accounts
|
||||||
include Stormy::Test::Helpers::Projects
|
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
setup_accounts
|
setup_accounts
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,6 @@ class AdminHelperTest < Stormy::Test::HelperCase
|
||||||
assert_equal 0, num_accounts
|
assert_equal 0, num_accounts
|
||||||
end
|
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
|
def test_last_listing
|
||||||
assert_equal '/admin', last_listing
|
assert_equal '/admin', last_listing
|
||||||
mark_last_listing '/admin/accounts'
|
mark_last_listing '/admin/accounts'
|
||||||
|
|
|
||||||
|
|
@ -8,21 +8,16 @@ class AuthorizationHelperTest < Stormy::Test::HelperCase
|
||||||
|
|
||||||
include Stormy::Helpers::Authorization
|
include Stormy::Helpers::Authorization
|
||||||
include Stormy::Test::Helpers::Accounts
|
include Stormy::Test::Helpers::Accounts
|
||||||
include Stormy::Test::Helpers::Admins
|
|
||||||
include Stormy::Test::Helpers::Projects
|
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
setup_accounts
|
setup_accounts
|
||||||
setup_projects
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def teardown
|
def teardown
|
||||||
deauthorize
|
deauthorize
|
||||||
teardown_request
|
teardown_request
|
||||||
teardown_accounts
|
teardown_accounts
|
||||||
teardown_projects
|
|
||||||
@current_account = nil
|
@current_account = nil
|
||||||
@current_project = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def teardown_request
|
def teardown_request
|
||||||
|
|
@ -69,7 +64,7 @@ class AuthorizationHelperTest < Stormy::Test::HelperCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def url
|
def url
|
||||||
'/projects'
|
'/account'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -153,116 +148,31 @@ class AuthorizationHelperTest < Stormy::Test::HelperCase
|
||||||
assert_equal @existing_account.id, current_account.id
|
assert_equal @existing_account.id, current_account.id
|
||||||
end
|
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?
|
def test_admin_authorized?
|
||||||
setup_admins
|
|
||||||
assert !admin_authorized?
|
assert !admin_authorized?
|
||||||
authorize_admin(@admin.id)
|
authorize_account(@existing_account.id)
|
||||||
assert admin_authorized?
|
assert admin_authorized?
|
||||||
|
|
||||||
@current_admin = nil
|
@current_account = nil
|
||||||
authorize_admin('does not exist')
|
authorize_account('does not exist')
|
||||||
assert !admin_authorized?
|
assert !admin_authorized?
|
||||||
teardown_admins
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_admin_authorize!
|
def test_admin_authorize!
|
||||||
setup_admins
|
authorize_account(@existing_account.id)
|
||||||
authorize_admin(@admin.id)
|
|
||||||
admin_authorize!
|
admin_authorize!
|
||||||
assert_not_redirected
|
assert_not_redirected
|
||||||
assert !session.has_key?(:original_url)
|
assert !session.has_key?(:original_url)
|
||||||
teardown_admins
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_admin_authorize_redirects_to_sign_in
|
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]
|
assert_equal request.url, session[:original_url]
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_admin_authorize_api!
|
def test_admin_authorize_api!
|
||||||
setup_admins
|
authorize_account(@existing_account.id)
|
||||||
authorize_admin(@admin.id)
|
|
||||||
assert_nil catch(:halt) { admin_authorize_api! }
|
assert_nil catch(:halt) { admin_authorize_api! }
|
||||||
teardown_admins
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_admin_authorize_api_throws_if_unauthorized
|
def test_admin_authorize_api_throws_if_unauthorized
|
||||||
|
|
@ -270,13 +180,4 @@ class AuthorizationHelperTest < Stormy::Test::HelperCase
|
||||||
assert_content_type 'text/plain'
|
assert_content_type 'text/plain'
|
||||||
end
|
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
|
end
|
||||||
|
|
|
||||||
|
|
@ -203,4 +203,11 @@ class ViewsHelperTest < Stormy::Test::HelperCase
|
||||||
assert_equal "<p>42</p>\n", markdown(42)
|
assert_equal "<p>42</p>\n", markdown(42)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_admin_page?
|
||||||
|
assert admin_page?('/admin')
|
||||||
|
assert admin_page?('/admin/accounts')
|
||||||
|
assert !admin_page?('/')
|
||||||
|
assert !admin_page?('/account')
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,9 @@ require 'common'
|
||||||
class AccountTest < Stormy::Test::Case
|
class AccountTest < Stormy::Test::Case
|
||||||
|
|
||||||
include Stormy::Test::Helpers::Accounts
|
include Stormy::Test::Helpers::Accounts
|
||||||
include Stormy::Test::Helpers::Projects
|
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
setup_accounts
|
setup_accounts
|
||||||
setup_projects
|
|
||||||
|
|
||||||
@invalid_addresses = [
|
@invalid_addresses = [
|
||||||
'invalid email address',
|
'invalid email address',
|
||||||
|
|
@ -22,7 +20,6 @@ class AccountTest < Stormy::Test::Case
|
||||||
end
|
end
|
||||||
|
|
||||||
def teardown
|
def teardown
|
||||||
teardown_projects
|
|
||||||
teardown_accounts
|
teardown_accounts
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -75,7 +72,7 @@ class AccountTest < Stormy::Test::Case
|
||||||
assert Account.verify_email(@existing_account.email, @existing_account.email_verification_token)
|
assert Account.verify_email(@existing_account.email, @existing_account.email_verification_token)
|
||||||
account = Account.fetch(@existing_account.id)
|
account = Account.fetch(@existing_account.id)
|
||||||
assert account.email_verified
|
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')
|
assert !Account.verify_email('non@existent.email', 'insignificant token')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -90,15 +87,12 @@ class AccountTest < Stormy::Test::Case
|
||||||
|
|
||||||
# no new token is generated if one is present
|
# no new token is generated if one is present
|
||||||
token = @existing_account.email_verification_token
|
token = @existing_account.email_verification_token
|
||||||
@existing_account.create_email_verification_token
|
|
||||||
assert_equal @existing_account.email_verification_token, token
|
assert_equal @existing_account.email_verification_token, token
|
||||||
|
|
||||||
# a token is generated if necessary
|
# a token is generated if necessary
|
||||||
Account.verify_email(@existing_account.email, @existing_account.email_verification_token) # clears token
|
Account.verify_email(@existing_account.email, @existing_account.email_verification_token) # clears token
|
||||||
account = Account.fetch(@existing_account.id)
|
account = Account.fetch(@existing_account.id)
|
||||||
assert !account.email_verification_token
|
assert !account.instance_variable_get('@email_verification_token')
|
||||||
account.create_email_verification_token
|
|
||||||
assert account.email_verification_token
|
|
||||||
assert account.email_verification_token != token
|
assert account.email_verification_token != token
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -132,7 +126,7 @@ class AccountTest < Stormy::Test::Case
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_create_with_existing_email
|
def test_create_with_existing_email
|
||||||
assert_raises Account::EmailTakenError do
|
assert_raises Account::DuplicateFieldError do
|
||||||
Account.new(@existing_account_data).create
|
Account.new(@existing_account_data).create
|
||||||
end
|
end
|
||||||
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'
|
assert Account.fetch_by_email(@existing_account.email).nil?, 'Account was fetched by email after deletion'
|
||||||
|
|
||||||
# indexes
|
# 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'
|
assert !Account.exists?(@existing_account.id), 'Account exists after deletion'
|
||||||
|
|
||||||
# projects are deleted
|
|
||||||
assert_equal [], @existing_account.project_ids
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_name
|
def test_name
|
||||||
assert_equal "#{@existing_account.first_name} #{@existing_account.last_name}", @existing_account.name
|
assert_equal "#{@existing_account.first_name} #{@existing_account.last_name}", @existing_account.name
|
||||||
end
|
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
|
def test_update
|
||||||
original_data = {
|
original_data = {
|
||||||
'id' => @existing_account.id,
|
'id' => @existing_account.id,
|
||||||
|
|
@ -377,7 +339,7 @@ class AccountTest < Stormy::Test::Case
|
||||||
# all should be updated
|
# all should be updated
|
||||||
check_account_fields(@existing_account, updated_data)
|
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)
|
@existing_account.update!(original_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -9,8 +9,10 @@ class ModelBaseTest < Stormy::Test::Case
|
||||||
def setup
|
def setup
|
||||||
@my_model_class = Class.new(Stormy::Models::Base)
|
@my_model_class = Class.new(Stormy::Models::Base)
|
||||||
@my_model_class.class_eval do
|
@my_model_class.class_eval do
|
||||||
|
model_name 'my_model'
|
||||||
field :id, :required => true
|
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, {
|
field :age, {
|
||||||
:type => :integer,
|
:type => :integer,
|
||||||
:required => true,
|
:required => true,
|
||||||
|
|
@ -18,14 +20,8 @@ class ModelBaseTest < Stormy::Test::Case
|
||||||
:validator => proc { |n| n >= 18 }
|
:validator => proc { |n| n >= 18 }
|
||||||
}
|
}
|
||||||
field :verified, :type => :boolean
|
field :verified, :type => :boolean
|
||||||
|
|
||||||
def create
|
|
||||||
self.id = UUID.generate unless id.present?
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
@fields = { 'name' => 'Sami', 'age' => '29' }
|
@fields = { 'name' => 'Sami', 'age' => '29', 'email' => 'sami@samhuri.net' }
|
||||||
@my_model = @my_model_class.create(@fields)
|
@my_model = @my_model_class.create(@fields)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -39,15 +35,16 @@ class ModelBaseTest < Stormy::Test::Case
|
||||||
### Class Methods
|
### Class Methods
|
||||||
|
|
||||||
def test_name
|
def test_name
|
||||||
@my_model_class.name 'my_model'
|
@my_model_class.model_name 'my_model'
|
||||||
assert_equal 'my_model', @my_model_class.name
|
assert_equal 'my_model', @my_model_class.model_name
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_id_field
|
def test_id_field
|
||||||
# has id by default
|
# has id
|
||||||
id_field = {
|
id_field = {
|
||||||
:type => :string,
|
:type => :string,
|
||||||
:required => true
|
:required => true,
|
||||||
|
:accessors => true
|
||||||
}
|
}
|
||||||
assert_equal(id_field, @my_model_class.fields[:id])
|
assert_equal(id_field, @my_model_class.fields[:id])
|
||||||
methods = %w[id id=]
|
methods = %w[id id=]
|
||||||
|
|
@ -60,7 +57,9 @@ class ModelBaseTest < Stormy::Test::Case
|
||||||
name_field = {
|
name_field = {
|
||||||
:type => :string,
|
:type => :string,
|
||||||
:required => true,
|
:required => true,
|
||||||
:updatable => true
|
:updatable => true,
|
||||||
|
:accessors => true,
|
||||||
|
:indexed => true
|
||||||
}
|
}
|
||||||
assert_equal(name_field, @my_model_class.fields[:name])
|
assert_equal(name_field, @my_model_class.fields[:name])
|
||||||
methods = %w[name name=]
|
methods = %w[name name=]
|
||||||
|
|
@ -85,7 +84,7 @@ class ModelBaseTest < Stormy::Test::Case
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_verified_field
|
def test_verified_field
|
||||||
verified_field = { :type => :boolean }
|
verified_field = { :type => :boolean, :accessors => true }
|
||||||
assert_equal(verified_field, @my_model_class.fields[:verified])
|
assert_equal(verified_field, @my_model_class.fields[:verified])
|
||||||
methods = %w[verified verified= verified?]
|
methods = %w[verified verified= verified?]
|
||||||
methods.each do |name|
|
methods.each do |name|
|
||||||
|
|
@ -125,7 +124,7 @@ class ModelBaseTest < Stormy::Test::Case
|
||||||
|
|
||||||
def test_key
|
def test_key
|
||||||
id = @my_model.id
|
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)
|
assert_equal key, @my_model_class.key(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
Before Width: | Height: | Size: 22 KiB |
|
|
@ -38,7 +38,7 @@ class ConfigTest < Stormy::Test::Case
|
||||||
|
|
||||||
def test_method_missing_get
|
def test_method_missing_get
|
||||||
# (none yet)
|
# (none yet)
|
||||||
# assert_equal defaults['demo_project_id'], @config.demo_project_id
|
# assert_equal defaults['foo_bar'], @config.foo_bar
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_method_missing_get_default
|
def test_method_missing_get_default
|
||||||
|
|
|
||||||
|
|
@ -1,65 +1,82 @@
|
||||||
<h2 class="section-heading top">Account</h2>
|
|
||||||
|
|
||||||
<%== flash_message %>
|
<%== flash_message %>
|
||||||
|
|
||||||
<table id="account">
|
<div class="container">
|
||||||
<tr>
|
<div class="content">
|
||||||
<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>
|
|
||||||
|
|
||||||
<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">
|
<div class="clearfix">
|
||||||
<table id="change-password">
|
<p>
|
||||||
<tr>
|
<strong>First name</strong>
|
||||||
<th><label for="old-password">Current password</label></th>
|
<br>
|
||||||
<td><input type="password" id="old-password" name="old-password" placeholder="Current password"></td>
|
<span id="first_name" class="editable"><%= @account.first_name %></span>
|
||||||
</tr>
|
</p>
|
||||||
<tr>
|
</div>
|
||||||
<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>
|
|
||||||
|
|
||||||
<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' : '' %>">
|
<div class="clearfix">
|
||||||
<hr>
|
<p>
|
||||||
<p id="unverified-email" class="unverified">Your email address is unverified.</p>
|
<strong>Phone</strong>
|
||||||
<p><a href="#" id="send-email-verification">Send verification email</a></p>
|
<br>
|
||||||
<p id="sending-email-verification"><img src="/images/spinner.gif"> Sending...</p>
|
<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>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<th>Phone</th>
|
<th>Phone</th>
|
||||||
<th>Projects</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<% for account in @accounts %>
|
<% for account in @accounts %>
|
||||||
|
|
@ -12,7 +11,6 @@
|
||||||
<td class="name"><a href="/admin/account/<%= account.email %>"><%= account.name %></a></td>
|
<td class="name"><a href="/admin/account/<%= account.email %>"><%= account.name %></a></td>
|
||||||
<td><%= account.email %></td>
|
<td><%= account.email %></td>
|
||||||
<td><%= account.phone %></td>
|
<td><%= account.phone %></td>
|
||||||
<td><%= account.count_projects %></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title><%= @page_title %> - Stormy Weather Admin</title>
|
|
||||||
<link rel="stylesheet" href="/css/admin.css" charset="utf-8">
|
|
||||||
|
|
||||||
<% if @page_styles %>
|
|
||||||
<% for url in @page_styles %>
|
|
||||||
<link rel="stylesheet" href="<%= url %>" charset="utf-8">
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h1><%= @page_title %></h1>
|
|
||||||
|
|
||||||
<% if current_admin %>
|
|
||||||
<div id="sign-out">
|
|
||||||
Signed in as <%= current_admin.name %>.
|
|
||||||
<a href="#" id="sign-out-link">Sign Out</a>
|
|
||||||
<br>
|
|
||||||
<a href="/admin/password">Change password</a>
|
|
||||||
<form id="sign-out-form" action="/admin/sign-out" method="post"></form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%== flash_message %>
|
|
||||||
|
|
||||||
<ul id="nav">
|
|
||||||
<li><a href="/admin">Dashboard</a></li>
|
|
||||||
<li><a href="/admin/accounts">Accounts (<%= num_accounts %>)</a></li>
|
|
||||||
<li><a href="/admin/projects">Project IDs (<%= num_projects %>)</a></li>
|
|
||||||
<li><a href="/admin/faq">FAQ</a></li>
|
|
||||||
<li><a href="/admin/admins">Admin Accounts (<%= num_admins %>)</a></li>
|
|
||||||
</ul>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div id="content">
|
|
||||||
<%== yield %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% for url in scripts %>
|
|
||||||
<script src="<%= url %>"></script>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
<p><a href="/admin/projects"><< Projects</a></p>
|
|
||||||
|
|
||||||
<table id="project">
|
|
||||||
<tr>
|
|
||||||
<th>ID</th>
|
|
||||||
<td><%= @project.id %></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Owner</th>
|
|
||||||
<td><a href="/admin/account/<%= @project.account.email %>"><%= @project.account.name %></a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<td><%= @project.name %></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Created</th>
|
|
||||||
<td><%= format_date(Time.at(@project.created_timestamp)) %></td>
|
|
||||||
</tr>
|
|
||||||
<% if @project.funded? %>
|
|
||||||
<tr>
|
|
||||||
<th>Funded</th>
|
|
||||||
<td><%= format_date(Time.at(@project.funded_timestamp)) %></td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
<% if @project.fizzled? %>
|
|
||||||
<tr>
|
|
||||||
<th>Fizzled</th>
|
|
||||||
<td><%= format_date(Time.at(@project.fizzled_timestamp)) %></td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h2>Actions</h2>
|
|
||||||
<p align="center">
|
|
||||||
<a id="delete" href="/admin/project/<%= @project.id %>/delete">Delete this project</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Photos</h2>
|
|
||||||
<div>
|
|
||||||
<% for url in @project.photo_urls %>
|
|
||||||
<a href="<%= url %>"><img src="<%= url %>" alt=""></a>
|
|
||||||
<br>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
<table id="projects">
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Owner</th>
|
|
||||||
<th>Created</th>
|
|
||||||
<th>Fizzled</th>
|
|
||||||
<th>Funded</th>
|
|
||||||
</tr>
|
|
||||||
<% for project in @projects %>
|
|
||||||
<tr>
|
|
||||||
<td><a href="/admin/project/<%= project.id %>"><%= project.name %></a></td>
|
|
||||||
<td><a href="/admin/account/<%= project.account.email %>"><%= project.account.name %></a></td>
|
|
||||||
<td><%= format_date(Time.at(project.created_timestamp)) %>
|
|
||||||
<td><%= project.fizzled? ? format_date(Time.at(project.fizzled_timestamp)) : '-' %>
|
|
||||||
<td><%= project.funded? ? format_date(Time.at(project.funded_timestamp)) : '-' %>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</table>
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
<%== flash_message %>
|
|
||||||
|
|
||||||
<form id="sign-in-form" action="/admin/sign-in" method="post">
|
|
||||||
<table id="sign-in">
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th><label for="email">Email</label></th>
|
|
||||||
<td><input type="email" name="email" id="email" size="30" placeholder="jane.doe@example.com" value="<%= @email %>"></td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th><label for="password">Password</label></th>
|
|
||||||
<td id="password-cell">
|
|
||||||
<input type="password" name="password" id="password" size="30" placeholder="Secret">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<td id="submit-cell">
|
|
||||||
<input id="sign-in-button" type="submit" value="Sign In">
|
|
||||||
<span id="sign-in-spinner"><img src="/images/spinner.gif"> Signing in...</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</table>
|
|
||||||
</form>
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
<div id="about-us">
|
<div id="about-us">
|
||||||
<h2 class="section-heading top">About Us</h2>
|
<h3 class="section-heading top">About Us</h3>
|
||||||
<p class="section">
|
<p class="section">
|
||||||
Made of 100% pure awesome. #winning
|
Made of 100% pure awesome. #winning
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="contact-us">
|
<div id="contact-us">
|
||||||
<h2 class="section-heading top">Contact Us</h2>
|
<h3 class="section-heading top">Contact Us</h3>
|
||||||
|
|
||||||
<%== flash_message %>
|
<%== flash_message %>
|
||||||
|
|
||||||
|
|
@ -23,28 +23,28 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="contact-email">
|
<div id="contact-email">
|
||||||
<h2 class="section-heading">Email</h2>
|
<h3 class="section-heading">Email</h3>
|
||||||
|
|
||||||
<p class="section">Contact us via email.</p>
|
<p class="section">Contact us via email.</p>
|
||||||
|
|
||||||
<table class="section">
|
<table class="section">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Technical support:</th>
|
<th>Technical support:</th>
|
||||||
<td><a href="mailto:tech@example.com">tech@example.com</a></td>
|
<td><a href="mailto:tech@example.comapult.com">tech@example.comapult.com</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Accounting support:</th>
|
<th>Accounting support:</th>
|
||||||
<td><a href="mailto:accounts@example.com">accounts@example.com</a></td>
|
<td><a href="mailto:accounts@example.comapult.com">accounts@example.comapult.com</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>General inquiries:</th>
|
<th>General inquiries:</th>
|
||||||
<td><a href="mailto:info@example.com">info@example.com</a></td>
|
<td><a href="mailto:info@example.comapult.com">info@example.comapult.com</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="address-phone">
|
<div id="address-phone">
|
||||||
<h2 class="section-heading">Address and Phone</h2>
|
<h3 class="section-heading">Address and Phone</h3>
|
||||||
|
|
||||||
<p class="section">Or keep it old school.</p>
|
<p class="section">Or keep it old school.</p>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
<h2 class="section-heading top">Project Info</h2>
|
|
||||||
|
|
||||||
<%== flash_message %>
|
|
||||||
|
|
||||||
<![if !IE]>
|
|
||||||
<iframe id="upload-target" name="upload-target"></iframe>
|
|
||||||
|
|
||||||
<form id="photo-form" action="/project/add-photo" method="post" enctype="multipart/form-data" target="upload-target">
|
|
||||||
<input type="hidden" name="id" value="<%= @project.id %>">
|
|
||||||
<input type="file" id="photo-uploader" name="photo">
|
|
||||||
</form>
|
|
||||||
<![endif]>
|
|
||||||
|
|
||||||
<form action="/project/update" method="post" id="project">
|
|
||||||
<input type="hidden" name="id" value="<%= @project.id %>">
|
|
||||||
|
|
||||||
<p class="save">
|
|
||||||
<input type="submit" class="button save-button" value="Save <%= @project.name.present? ? @project.name : 'This Project' %>">
|
|
||||||
<span class="save-button-spinner"><img src="/images/spinner.gif"> Saving...</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<table id="project-info" class="section">
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th><label for="name">Name</label></th>
|
|
||||||
<td>
|
|
||||||
<input type="text" id="name" name="name" size="40" placeholder="Wild Wacky Action Bike" value="<%= @project.name %>">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
<h2 class="section-heading">Photos</h2>
|
|
||||||
|
|
||||||
<script type="text/html" id="photo-template">
|
|
||||||
<li class="photo" id="photo-<%%= id %>">
|
|
||||||
<a href="<%%= url %>" class="thumbnail" id="thumbnail-<%%= id %>"><img src="<%%= url %>" width="64" height="64"></a>
|
|
||||||
<br>
|
|
||||||
<a href="#" class="remove-photo" id="remove-photo-<%%= id %>">remove</a>
|
|
||||||
</li>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="section" id="photos-container">
|
|
||||||
<ul id="photos">
|
|
||||||
<% for photo in @project.photos %>
|
|
||||||
<li class="photo" id="photo-<%= photo['id'] %>">
|
|
||||||
<a href="<%= photo['url'] %>" class="thumbnail" id="thumbnail-<%= photo['id'] %>"><img src="<%= photo['url'] %>" width="64" height="64"></a>
|
|
||||||
<br>
|
|
||||||
<a href="#" class="remove-photo" id="remove-photo-<%= photo['id'] %>">remove</a>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<li id="add-photo-box" class="<%= @project.count_photos >= 10 ? 'hidden' : '' %>">
|
|
||||||
<!--[if IE]>
|
|
||||||
<input type="file" id="ie-photo-uploader" style="display: none">
|
|
||||||
<br>
|
|
||||||
add photo
|
|
||||||
<![endif]-->
|
|
||||||
<![if !IE]>
|
|
||||||
<a href="#photos" class="add-photo"><img src="/images/add-photo.png" width="64" height="64"></a>
|
|
||||||
<br>
|
|
||||||
<a href="#photos" class="add-photo">add photo</a>
|
|
||||||
<![endif]>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p id="drag-n-drop">Drag and drop photos to change their order.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="save">
|
|
||||||
<input type="submit" class="button save-button" value="Save <%= @project.name.present? ? @project.name : 'This Project' %>">
|
|
||||||
<span class="save-button-spinner"><img src="/images/spinner.gif"> Saving...</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
window.SI = window.SI || {}
|
|
||||||
window.SI.projectId = '<%= @project.id %>'
|
|
||||||
</script>
|
|
||||||
|
|
@ -8,21 +8,11 @@
|
||||||
|
|
||||||
<p><strong>session:</strong> <%= session.inspect %></p>
|
<p><strong>session:</strong> <%= session.inspect %></p>
|
||||||
|
|
||||||
<% if admin %>
|
|
||||||
<p><strong>Admin:</strong> <%= admin.name %> <<%= admin.email %>></p>
|
|
||||||
<pre><%= admin.field_array %></pre>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% if account %>
|
<% if account %>
|
||||||
<p><strong>Account:</strong> <%= account.name %> <<%= account.email %>></p>
|
<p><strong>Account:</strong> <%= account.name %> <<%= account.email %>></p>
|
||||||
<pre><%= account.field_array %></pre>
|
<pre><%= account.field_array %></pre>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if project %>
|
|
||||||
<p><strong>Project:</strong> <%= project.id %> (<%= project.name %>)</p>
|
|
||||||
<pre><%= project.field_array %></pre>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<%= error.class %>: <%= error.message %>
|
<%= error.class %>: <%= error.message %>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
<h2 class="section-heading top">Oops</h2>
|
<h3 class="section-heading top">Oops</h3>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<h2 class="section-heading top">Frequently Asked Questions</h2>
|
<h3 class="section-heading top">Frequently Asked Questions</h3>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<%== @faq %>
|
<%== @faq %>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<h2 class="section-heading top">Forgot Password</h2>
|
<h3 class="section-heading top">Forgot Password</h3>
|
||||||
|
|
||||||
<%== flash_message %>
|
<%== flash_message %>
|
||||||
|
|
||||||
|
|
|
||||||
118
views/layout.erb
|
|
@ -3,9 +3,22 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="description" content="Stormy Weather is bigger than The Beatles.">
|
<meta name="author" content="Sami Samhuri">
|
||||||
|
<meta name="description" content="My awesome site.">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
<title><%= @page_title ? @page_title + ' - ' : '' %>Stormy Weather</title>
|
<title>Stormy Weather<%= admin_page? ? ' Admin' : '' %><%= @page_title ? ' - ' + @page_title : '' %></title>
|
||||||
|
|
||||||
|
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
<!-- Le fav and touch icons -->
|
||||||
|
<link rel="shortcut icon" href="images/favicon.ico">
|
||||||
|
<link rel="apple-touch-icon" href="images/apple-touch-icon.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="72x72" href="images/apple-touch-icon-72x72.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="114x114" href="images/apple-touch-icon-114x114.png">
|
||||||
|
|
||||||
<% for url in stylesheets %>
|
<% for url in stylesheets %>
|
||||||
<link rel="stylesheet" href="<%= url %>" charset="utf-8">
|
<link rel="stylesheet" href="<%= url %>" charset="utf-8">
|
||||||
|
|
@ -15,50 +28,87 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
<div class="navbar navbar-fixed-top">
|
||||||
|
<div class="navbar-inner">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</a>
|
||||||
|
<a class="brand" href="/">Stormy Weather</a>
|
||||||
|
<div class="nav-collapse">
|
||||||
|
<ul class="nav">
|
||||||
|
<li <%== request.path_info == '/' ? 'class="active"' : '' %>><a href="/">Home</a></li>
|
||||||
|
|
||||||
|
<% if admin_authorized? %>
|
||||||
|
<li <%== request.path_info == '/admin' ? 'class="active"' : '' %>><a href="/admin">Admin Dashboard</a></li>
|
||||||
|
<li <%== request.path_info == '/admin/accounts' ? 'class="active"' : '' %>><a href="/admin/accounts">Accounts: <%= num_accounts %></a></li>
|
||||||
|
<li <%== request.path_info == '/admin/faq' ? 'class="active"' : '' %>><a href="/admin/faq">FAQ</a></li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
<% if authorized? || admin_authorized? %>
|
||||||
|
<p class="navbar-text pull-right">
|
||||||
|
<% if authorized? %>
|
||||||
|
Signed in as <a href="/account"><%= current_account.email %></a>
|
||||||
|
•
|
||||||
|
<a href="#" id="sign-out-link">Sign Out</a>
|
||||||
|
<% end %>
|
||||||
|
<% if admin_authorized? %>
|
||||||
|
<% if authorized? %><br><% end %>
|
||||||
|
Signed in as admin: <a href="/admin/password"><%= current_account.email %></a>
|
||||||
|
•
|
||||||
|
<a href="#" id="admin-sign-out-link">Sign out of admin</a>
|
||||||
|
<% end %>
|
||||||
|
</p>
|
||||||
|
<% else %>
|
||||||
|
<p class="navbar-text pull-right">
|
||||||
|
<a href="/sign-in">Sign in</a>
|
||||||
|
or
|
||||||
|
<a href="/sign-up">Sign up</a>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
</div><!--/.nav-collapse -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if authorized? %>
|
||||||
<form id="sign-out-form" action="/sign-out" method="post"></form>
|
<form id="sign-out-form" action="/sign-out" method="post"></form>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<div id="wrapper">
|
<div class="container">
|
||||||
|
<% if breadcrumbs.size > 0 %>
|
||||||
<p id="nav" class="<%= authorized? ? 'authorized' : '' %>">
|
<ul class="breadcrumb">
|
||||||
<% if authorized? %>
|
<% breadcrumbs.each_with_index do |crumb, i| %>
|
||||||
Signed in as <%= current_account.email %>
|
<li class="<%= crumb[:active] ? 'active' : '' %>">
|
||||||
<br>
|
<a href="<%= crumb[:path] %>"><%= crumb[:name] %></a>
|
||||||
<a href="/projects">Projects</a>
|
<% if i < breadcrumbs.size - 1%>
|
||||||
|
|
<span class="divider">/</span>
|
||||||
<a href="/account">Account</a>
|
<% end %>
|
||||||
|
|
</li>
|
||||||
<a href="#" id="sign-out-link">Sign Out</a>
|
|
||||||
<% else %>
|
|
||||||
<a href="/">Home</a>
|
|
||||||
|
|
|
||||||
<a href="/sign-up">Sign Up</a>
|
|
||||||
|
|
|
||||||
<a href="/sign-in">Sign In</a>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</ul>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<div id="header">
|
<%== flash_message %>
|
||||||
<a id="logo" href="/"><img src="/images/logo.png" width="600" height="72" alt="Stormy Weather"></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="content">
|
<%== yield %>
|
||||||
<%== yield %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="footer">
|
<hr>
|
||||||
<p id="copyright">© 2012 Sami Samhuri</p>
|
|
||||||
|
<footer>
|
||||||
|
<p>© 2012 Sami Samhuri</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="/contact">About Us</a>
|
<a href="/contact">Contact Us</a>
|
||||||
•
|
•
|
||||||
<a href="/faq">FAQ</a>
|
<a href="/faq">FAQ</a>
|
||||||
•
|
•
|
||||||
<a href="/contact">Contact Us</a>
|
|
||||||
•
|
|
||||||
<a href="/terms">Terms of Service</a>
|
<a href="/terms">Terms of Service</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</footer>
|
||||||
|
|
||||||
</div>
|
</div><!--/.fluid-container-->
|
||||||
|
|
||||||
<% for url in scripts %>
|
<% for url in scripts %>
|
||||||
<script src="<%= url %>"></script>
|
<script src="<%= url %>"></script>
|
||||||
|
|
@ -68,7 +118,7 @@
|
||||||
if (false) {
|
if (false) {
|
||||||
var _gaq = _gaq || [];
|
var _gaq = _gaq || [];
|
||||||
_gaq.push(['_setAccount', 'UA-XXXXXXXX-X']);
|
_gaq.push(['_setAccount', 'UA-XXXXXXXX-X']);
|
||||||
_gaq.push(['_setDomainName', 'example.com']);
|
_gaq.push(['_setDomainName', 'example.comapult.com']);
|
||||||
_gaq.push(['_trackPageview']);
|
_gaq.push(['_trackPageview']);
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,9 @@
|
||||||
|
|
||||||
<h2 class="section-heading top">:(</h2>
|
<h3 class="section-heading top">:(</h3>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<p align="center">Hmmm... We don't seem to have what you're looking for.</p>
|
<p align="center">Hmmm... We don't seem to have what you're looking for.</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<% if authorized? %>
|
<a href="/">Ok fine, I'm outta here!</a>
|
||||||
<a href="/projects">
|
|
||||||
<% else %>
|
|
||||||
<a href="/">
|
|
||||||
<% end %>
|
|
||||||
Ok fine, I'm outta here!
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
<h2 class="section-heading top">Projects</h2>
|
|
||||||
|
|
||||||
<%== flash_message %>
|
|
||||||
|
|
||||||
<% if @projects.empty? %>
|
|
||||||
<!-- no projects -->
|
|
||||||
<% else %>
|
|
||||||
<table id="projects" class="section">
|
|
||||||
<tr class="headings">
|
|
||||||
<th class="name">Name</th>
|
|
||||||
<th class="created">Created</th>
|
|
||||||
</tr>
|
|
||||||
<% first_project = true %>
|
|
||||||
<% @projects.each do |project| %>
|
|
||||||
<tr class="project <%= project.name.blank? ? 'new' : '' %> <%= first_project ? 'first' : '' %>">
|
|
||||||
<td class="name"><a href="/project/<%= project.id %>"><%= project.name %></a></td>
|
|
||||||
<td class="created"><%= format_date(Time.at(project.created_timestamp)) %></td>
|
|
||||||
</tr>
|
|
||||||
<% first_project = false %>
|
|
||||||
<% end %>
|
|
||||||
</table>
|
|
||||||
<% end %>
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
<h2 class="section-heading top">Reset My Password</h2>
|
<h3 class="section-heading top">Reset My Password</h3>
|
||||||
|
|
||||||
<%== flash_message %>
|
<%== flash_message %>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,40 @@
|
||||||
<%== flash_message %>
|
<%== flash_message %>
|
||||||
|
|
||||||
<form id="sign-in-form" action="/sign-in" method="post">
|
<div class="container">
|
||||||
<table id="sign-in">
|
<div class="content">
|
||||||
|
<div class="row">
|
||||||
|
<div class="sign-in-form">
|
||||||
|
<h2>Sign in</h2>
|
||||||
|
|
||||||
<tr>
|
<form id="sign-in-form" action="/sign-in" method="post">
|
||||||
<th><label for="email">Email</label></th>
|
<fieldset>
|
||||||
<td><input type="email" name="email" id="email" size="30" placeholder="jane.doe@example.com" value="<%= @email %>"></td>
|
<div class="clearfix">
|
||||||
</tr>
|
<input type="email" name="email" id="email" size="30" placeholder="Email" value="<%= @email %>">
|
||||||
|
</div>
|
||||||
|
<div class="clearfix">
|
||||||
|
<input type="password" name="password" id="password" size="30" placeholder="Password">
|
||||||
|
</div>
|
||||||
|
<div class="clearfix">
|
||||||
|
<input type="checkbox" name="remember" id="remember">
|
||||||
|
<label for="remember">Keep me signed in</label>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix submit">
|
||||||
|
<button id="sign-in-button" class="btn-primary" type="submit">Sign in</button>
|
||||||
|
<span id="sign-in-spinner"><img src="/images/spinner.gif"> Signing in...</span>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
<tr>
|
</div><!-- sign-in-form -->
|
||||||
<th><label for="password">Password</label></th>
|
</div><!-- row -->
|
||||||
<td id="password-cell">
|
</div> <!-- content -->
|
||||||
<input type="password" name="password" id="password" size="30" placeholder="Secret">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
<div class="row other-actions">
|
||||||
<th></th>
|
<p style="float:right">
|
||||||
<td>
|
|
||||||
<input type="checkbox" name="remember" id="remember">
|
|
||||||
<label for="remember">Keep me signed in</label>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<td id="submit-cell">
|
|
||||||
<input id="sign-in-button" type="submit" value="Sign In">
|
|
||||||
<span id="sign-in-spinner"><img src="/images/spinner.gif"> Signing in...</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td colspan="2" id="sign-up">
|
|
||||||
<a href="/forgot-password" id="forgot-password-link">Forgot your password?</a>
|
|
||||||
<br>
|
|
||||||
Not a member yet? <a href="/sign-up">Sign Up!</a>
|
Not a member yet? <a href="/sign-up">Sign Up!</a>
|
||||||
</td>
|
</p>
|
||||||
</tr>
|
<p>
|
||||||
|
<a href="/forgot-password" id="forgot-password-link">Forgot your password?</a>
|
||||||
</table>
|
</p>
|
||||||
</form>
|
</div><!-- row -->
|
||||||
|
</div><!-- container -->
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
<p id="blurb">Please provide the following information in order to sign up.</p>
|
|
||||||
|
|
||||||
<% if @errors %>
|
<% if @errors %>
|
||||||
<script>
|
<script>
|
||||||
window.SI = window.SI || {}
|
window.SI = window.SI || {}
|
||||||
|
|
@ -9,49 +7,41 @@ window.SI.errors = <%== JSON.fast_generate(@errors) %>
|
||||||
|
|
||||||
<%== flash_message %>
|
<%== flash_message %>
|
||||||
|
|
||||||
<form id="sign-up-form" action="/sign-up" method="post">
|
<div class="container">
|
||||||
<table id="sign-up-table">
|
<div class="content">
|
||||||
<tr>
|
<div class="row">
|
||||||
<th><label for="first_name">First name</label></th>
|
<div class="sign-up-form">
|
||||||
<td><input type="text" id="first_name" name="first_name" size="40" placeholder="Jane" value="<%= @fields['first_name'] %>"></td>
|
<h2>Sign up</h2>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th><label for="last_name">Last name</label></th>
|
|
||||||
<td><input type="text" id="last_name" name="last_name" size="40" placeholder="Doe" value="<%= @fields['last_name'] %>"></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th><label for="email">Email</label></th>
|
|
||||||
<td><input type="email" id="email" name="email" size="40" placeholder="jane.doe@example.com" value="<%= @fields['email'] %>"></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th><label for="password">Password</label></th>
|
|
||||||
<td><input type="password" id="password" name="password" size="40" placeholder="Secret" value="<%= @fields['password'] %>"></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th><label for="password_confirmation">Confirm password</label></th>
|
|
||||||
<td><input type="password" id="password_confirmation" name="password_confirmation" size="40" placeholder="Again" value="<%= @fields['password'] %>"></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<td id="terms-cell">
|
|
||||||
<input type="checkbox" id="terms" name="terms" <%= @fields['terms'] ? 'checked' : '' %>>
|
|
||||||
<label for="terms">Agree to the <a href="/terms">terms of service</a>.</label>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<td id="sign-up-cell">
|
|
||||||
<input id="sign-up-button" type="submit" value="SIGN UP">
|
|
||||||
<span id="sign-up-spinner"><img src="/images/spinner.gif"> Signing up...</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div id="sign-in">
|
<form id="sign-up-form" action="/sign-up" method="post">
|
||||||
<p>Already have an account?</p>
|
<fieldset>
|
||||||
<a href="/sign-in" id="sign-in-button"><button>Sign In</button></a>
|
<div class="clearfix">
|
||||||
<img src="/images/spinner.gif" id="sign-in-spinner">
|
<input type="text" id="first_name" name="first_name" size="40" placeholder="First name" value="<%= @fields['first_name'] %>">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="clearfix">
|
||||||
|
<input type="text" id="last_name" name="last_name" size="40" placeholder="Last name" value="<%= @fields['last_name'] %>">
|
||||||
|
</div>
|
||||||
|
<div class="clearfix">
|
||||||
|
<input type="email" id="email" name="email" size="40" placeholder="Email" value="<%= @fields['email'] %>">
|
||||||
|
</div>
|
||||||
|
<div class="clearfix">
|
||||||
|
<input type="password" id="password" name="password" size="40" placeholder="Password" value="<%= @fields['password'] %>">
|
||||||
|
</div>
|
||||||
|
<div class="clearfix">
|
||||||
|
<input type="password" id="password_confirmation" name="password_confirmation" size="40" placeholder="Confirm password" value="<%= @fields['password'] %>">
|
||||||
|
</div>
|
||||||
|
<div class="clearfix">
|
||||||
|
<input type="checkbox" id="terms" name="terms" <%= @fields['terms'] ? 'checked' : '' %>>
|
||||||
|
<label for="terms">Agree to the <a href="/terms">terms of service</a>.</label>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix submit">
|
||||||
|
<input id="sign-up-button" type="submit" class="btn-primary" value="Sign up">
|
||||||
|
<span id="sign-up-spinner"><img src="/images/spinner.gif"> Signing up...</span>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
<div class="clear"></div>
|
</div><!-- sign-up-form -->
|
||||||
|
</div><!-- row -->
|
||||||
|
</div><!-- content -->
|
||||||
|
</div><!-- container -->
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
<h2 class="section-heading top"><%= @page_title =%></h2>
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
<div class="section">
|
<h1><%= @page_title %></h1>
|
||||||
<h4>Legal Terms & Conditions</h4>
|
<p>legalese goes here</p>
|
||||||
|
</div>
|
||||||
<p>
|
|
||||||
Put yer legalese here.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||