remove cruft, refactor
|
|
@ -3,22 +3,18 @@
|
|||
A web server blueprint using Sinatra and Redis.
|
||||
|
||||
Storm Weather is only a vague idea. Trying to nail down a lightweight blueprint for
|
||||
projects that have user accounts and admin accounts.
|
||||
projects that have user accounts.
|
||||
|
||||
|
||||
## Models
|
||||
|
||||
### Account Model
|
||||
|
||||
### Admin Model
|
||||
|
||||
|
||||
## Controllers
|
||||
|
||||
### Account Controller
|
||||
|
||||
### Admin Controller
|
||||
|
||||
### Public Controller
|
||||
|
||||
|
||||
|
|
|
|||
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__)
|
||||
$LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir)
|
||||
|
||||
# Ruby extensions
|
||||
require 'class-ext'
|
||||
require 'hash-ext'
|
||||
|
||||
module Stormy
|
||||
|
||||
# key prefix for data stored in Redis (used for testing)
|
||||
|
|
@ -16,12 +20,12 @@ module Stormy
|
|||
KeyPrefix = ''
|
||||
end
|
||||
|
||||
# public directory for project photos
|
||||
# public directory for photos
|
||||
unless const_defined? :PhotoDir
|
||||
PhotoDir = File.expand_path('../public/photos', File.dirname(__FILE__))
|
||||
end
|
||||
|
||||
# public directory for project videos
|
||||
# public directory for videos
|
||||
unless const_defined? :VideoDir
|
||||
VideoDir = File.expand_path('../public/videos', File.dirname(__FILE__))
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ module Stormy
|
|||
class Server < Sinatra::Base
|
||||
|
||||
get '/sign-up' do
|
||||
redirect '/projects' if authorized? && production?
|
||||
redirect '/account' if authorized? && production?
|
||||
|
||||
title 'Sign Up'
|
||||
stylesheet 'sign-up'
|
||||
|
|
@ -26,9 +26,9 @@ module Stormy
|
|||
@account = Account.create(fields)
|
||||
authorize_account(@account.id)
|
||||
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."
|
||||
session[:fields] = fields
|
||||
session[:fields]['terms'] = params['terms']
|
||||
|
|
@ -48,7 +48,7 @@ module Stormy
|
|||
end
|
||||
|
||||
get '/sign-in' do
|
||||
redirect '/projects' if authorized? && production?
|
||||
redirect '/account' if authorized? && production?
|
||||
|
||||
title 'Sign In'
|
||||
stylesheet 'sign-in'
|
||||
|
|
@ -70,7 +70,7 @@ module Stormy
|
|||
else
|
||||
response.delete_cookie('remembered')
|
||||
end
|
||||
url = session.delete(:original_url) || '/projects'
|
||||
url = session.delete(:original_url) || '/account'
|
||||
redirect url
|
||||
else
|
||||
flash[:warning] = "Incorrect email address or password."
|
||||
|
|
@ -121,7 +121,7 @@ module Stormy
|
|||
authorize!
|
||||
current_account.password = params['password']
|
||||
current_account.save!
|
||||
redirect '/projects'
|
||||
redirect '/account'
|
||||
end
|
||||
|
||||
get '/account' do
|
||||
|
|
@ -186,7 +186,7 @@ module Stormy
|
|||
current_account.update({ params['id'] => params['value'] })
|
||||
end
|
||||
ok
|
||||
rescue Account::EmailTakenError => e
|
||||
rescue Account::DuplicateFieldError => e
|
||||
fail('taken')
|
||||
rescue Account::InvalidDataError => e
|
||||
fail('invalid')
|
||||
|
|
|
|||
|
|
@ -6,48 +6,7 @@ module Stormy
|
|||
get '/admin' do
|
||||
admin_authorize!
|
||||
title "Dashboard"
|
||||
erb :'admin/dashboard', :layout => :'admin/layout'
|
||||
end
|
||||
|
||||
get '/admin/sign-in' do
|
||||
title "Sign In"
|
||||
script 'sign-in'
|
||||
stylesheet 'sign-in'
|
||||
erb :'admin/sign-in', :layout => :'admin/layout'
|
||||
end
|
||||
|
||||
post '/admin/sign-in' do
|
||||
if id = Admin.check_password(params['email'], params['password'])
|
||||
authorize_admin(id)
|
||||
redirect session.delete(:original_url) || '/admin'
|
||||
else
|
||||
flash[:notice] = "Incorrect email address or password."
|
||||
redirect '/admin/sign-in'
|
||||
end
|
||||
end
|
||||
|
||||
post '/admin/sign-out' do
|
||||
session.delete(:admin_id)
|
||||
redirect '/admin'
|
||||
end
|
||||
|
||||
get '/admin/password' do
|
||||
admin_authorize!
|
||||
title 'Change password'
|
||||
erb :'admin/password', :layout => :'admin/layout'
|
||||
end
|
||||
|
||||
post '/admin/password' do
|
||||
admin_authorize!
|
||||
if params['password'] == params['password_confirmation']
|
||||
current_admin.password = params['password']
|
||||
current_admin.save
|
||||
flash[:notice] = "Password changed."
|
||||
redirect '/admin'
|
||||
else
|
||||
flash[:warning] = "Passwords do not match."
|
||||
redirect '/admin/password'
|
||||
end
|
||||
erb :'admin/dashboard'
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -60,7 +19,7 @@ module Stormy
|
|||
mark_last_listing
|
||||
title "Accounts"
|
||||
@accounts = Account.fetch_all.sort { |a,b| a.name <=> b.name }
|
||||
erb :'admin/accounts', :layout => :'admin/layout'
|
||||
erb :'admin/accounts'
|
||||
end
|
||||
|
||||
get '/admin/account/:email' do |email|
|
||||
|
|
@ -69,7 +28,7 @@ module Stormy
|
|||
mark_last_listing
|
||||
title "#{@account.name}'s Account"
|
||||
script 'admin-account'
|
||||
erb :'admin/account', :layout => :'admin/layout'
|
||||
erb :'admin/account'
|
||||
else
|
||||
flash[:notice] = "No account with email #{email}"
|
||||
redirect last_listing
|
||||
|
|
@ -79,7 +38,7 @@ module Stormy
|
|||
get '/admin/sign-in-as/:email' do |email|
|
||||
admin_authorize!
|
||||
authorize_account(Account.id_from_email(email))
|
||||
redirect '/projects'
|
||||
redirect '/account'
|
||||
end
|
||||
|
||||
get '/admin/account/:email/delete' do |email|
|
||||
|
|
@ -105,7 +64,7 @@ module Stormy
|
|||
if new_email != @account.email
|
||||
begin
|
||||
@account.update_email(new_email)
|
||||
rescue Account::EmailTakenError => e
|
||||
rescue Account::DuplicateFieldError => e
|
||||
flash[:warning] = "That email address is already taken."
|
||||
redirect '/admin/account/' + email
|
||||
end
|
||||
|
|
@ -124,38 +83,6 @@ module Stormy
|
|||
end
|
||||
|
||||
|
||||
################
|
||||
### Projects ###
|
||||
################
|
||||
|
||||
get '/admin/projects' do
|
||||
admin_authorize!
|
||||
mark_last_listing
|
||||
title "Projects"
|
||||
@projects = Project.fetch_all.sort { |a,b| a.id <=> b.id }
|
||||
erb :'admin/projects', :layout => :'admin/layout'
|
||||
end
|
||||
|
||||
get '/admin/project/:id' do |id|
|
||||
admin_authorize!
|
||||
if @project = Project.fetch(id)
|
||||
title "Project ##{id}"
|
||||
title "#{title} (#{@project.name})" if @project.name
|
||||
script 'admin-project'
|
||||
erb :'admin/project', :layout => :'admin/layout'
|
||||
else
|
||||
flash[:notice] = "No such project (ID #{id})."
|
||||
redirect last_listing
|
||||
end
|
||||
end
|
||||
|
||||
get '/admin/project/:id/delete' do |id|
|
||||
admin_authorize!
|
||||
Project.delete!(id)
|
||||
redirect last_listing
|
||||
end
|
||||
|
||||
|
||||
###########
|
||||
### FAQ ###
|
||||
###########
|
||||
|
|
@ -170,7 +97,7 @@ module Stormy
|
|||
<p class="answer">Yes my son.</p>
|
||||
EOT
|
||||
end
|
||||
erb :'admin/faq', :layout => :'admin/layout'
|
||||
erb :'admin/faq'
|
||||
end
|
||||
|
||||
post '/admin/faq' do
|
||||
|
|
@ -180,38 +107,5 @@ module Stormy
|
|||
redirect '/admin/faq'
|
||||
end
|
||||
|
||||
|
||||
######################
|
||||
### Admin Accounts ###
|
||||
######################
|
||||
|
||||
get '/admin/admins' do
|
||||
admin_authorize!
|
||||
@admins = Admin.fetch_all.sort { |a,b| a.name <=> b.name }
|
||||
@fields = session.delete(:fields) || {}
|
||||
title 'Admin Accounts'
|
||||
stylesheet 'admins'
|
||||
erb :'admin/admins', :layout => :'admin/layout'
|
||||
end
|
||||
|
||||
post '/admin/admins' do
|
||||
admin_authorize!
|
||||
if params['password'] == params['password_confirmation']
|
||||
admin = Admin.create(params)
|
||||
flash[:notice] = "Added #{params['name']} (#{params['email']}) as an admin."
|
||||
else
|
||||
session[:fields] = params.slice('name', 'email')
|
||||
flash[:warning] = "Passwords do not match."
|
||||
end
|
||||
redirect '/admin/admins'
|
||||
end
|
||||
|
||||
get '/admin/admins/:id/delete' do |id|
|
||||
admin_authorize!
|
||||
Admin.delete!(id)
|
||||
flash[:notice] = "Deleted."
|
||||
redirect '/admin/admins'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
cache_control :public, :must_revalidate, :max_age => 60
|
||||
stylesheet 'index'
|
||||
stylesheet 'jquery.lightbox-0.5'
|
||||
script 'jquery.lightbox-0.5'
|
||||
script 'index'
|
||||
erb :index
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ module Stormy
|
|||
end
|
||||
|
||||
def send_verification_mail(account = current_account, subject = nil)
|
||||
account.create_email_verification_token
|
||||
body = erb(:'email/email-verification', :layout => :'email/layout', :locals => {
|
||||
:name => account.first_name,
|
||||
:email => account.email,
|
||||
|
|
|
|||
|
|
@ -10,21 +10,13 @@ module Stormy
|
|||
Account.count
|
||||
end
|
||||
|
||||
def num_admins
|
||||
Models::Admin.count
|
||||
end
|
||||
|
||||
def num_projects
|
||||
Project.count
|
||||
end
|
||||
|
||||
# Used to redirect back to the most recent list of things.
|
||||
#
|
||||
# i.e. someone goes to /admin -> /admin/account/foo -> /admin/project/007
|
||||
# if they delete that project they should go back to /admin/account/foo
|
||||
# i.e. someone goes to /admin -> /admin/account/foo -> /admin/thing/007
|
||||
# if they delete that thing they should go back to /admin/account/foo
|
||||
#
|
||||
# however if they go /admin -> /admin/projects -> /admin/project/007
|
||||
# and then delete that project they should go back to /admin/projects
|
||||
# however if they go /admin -> /admin/things -> /admin/thing/007
|
||||
# and then delete that thing they should go back to /admin/things
|
||||
def last_listing
|
||||
session.delete(:last_listing) || '/admin'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -42,53 +42,14 @@ module Stormy
|
|||
end
|
||||
end
|
||||
|
||||
def current_project(id = nil)
|
||||
if id
|
||||
@current_project = Project.fetch(id)
|
||||
else
|
||||
@current_project
|
||||
end
|
||||
end
|
||||
|
||||
def project_authorized?
|
||||
current_project && current_account && current_project.account_id == current_account.id
|
||||
end
|
||||
|
||||
def authorize_project_api!(id)
|
||||
authorize_api!
|
||||
current_project(id)
|
||||
throw(:halt, fail('no such project')) unless current_project
|
||||
unless project_authorized?
|
||||
content_type 'text/plain'
|
||||
throw(:halt, not_authorized)
|
||||
end
|
||||
end
|
||||
|
||||
def authorize_project!(id)
|
||||
authorize!
|
||||
current_project(id)
|
||||
unless current_project && project_authorized?
|
||||
flash[:warning] = 'No such project.'
|
||||
redirect '/projects'
|
||||
end
|
||||
end
|
||||
|
||||
def authorize_admin(id)
|
||||
session[:admin_id] = id
|
||||
end
|
||||
|
||||
def deauthorize_admin
|
||||
session.delete(:admin_id)
|
||||
end
|
||||
|
||||
def admin_authorized?
|
||||
session[:admin_id] && Models::Admin.exists?(session[:admin_id])
|
||||
authorized? && current_account.has_role?('admin')
|
||||
end
|
||||
|
||||
def admin_authorize!
|
||||
unless admin_authorized?
|
||||
session[:original_url] = request.url
|
||||
redirect '/admin'
|
||||
redirect '/sign-in'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -99,10 +60,6 @@ module Stormy
|
|||
end
|
||||
end
|
||||
|
||||
def current_admin
|
||||
@current_admin ||= Models::Admin.fetch(session[:admin_id])
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright 2012 Sami Samhuri <sami@samhuri.net>
|
||||
|
||||
require 'json'
|
||||
require 'stormy/config'
|
||||
|
||||
module Stormy
|
||||
|
|
|
|||
|
|
@ -64,6 +64,15 @@ module Stormy
|
|||
end
|
||||
end
|
||||
|
||||
def breadcrumbs
|
||||
@breadcrumbs ||= []
|
||||
end
|
||||
|
||||
def breadcrumb(crumb)
|
||||
crumb[:path] ||= '/' + crumb[:name].downcase
|
||||
breadcrumbs << crumb
|
||||
end
|
||||
|
||||
def format_dollars(amount, currency = 'CAD')
|
||||
'%s $%.2f' % [currency, amount / 100.0]
|
||||
end
|
||||
|
|
@ -116,6 +125,10 @@ module Stormy
|
|||
RDiscount.new(s.to_s).to_html
|
||||
end
|
||||
|
||||
def admin_page?(path = request.path_info)
|
||||
path.starts_with?('/admin')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,27 +7,26 @@ module Stormy
|
|||
module Models
|
||||
class Account < Base
|
||||
|
||||
class EmailTakenError < RuntimeError; end
|
||||
class IncorrectPasswordError < RuntimeError; end
|
||||
|
||||
name 'account'
|
||||
Roles = %w[user admin]
|
||||
|
||||
model_name 'account'
|
||||
|
||||
field :id, :required => true
|
||||
|
||||
field :email, :type => :email, :required => true
|
||||
field :email, :type => :email, :required => true, :unique => true
|
||||
field :first_name, :required => true, :updatable => true
|
||||
field :last_name, :required => true, :updatable => true
|
||||
field :phone, :type => :phone, :updatable => true
|
||||
|
||||
field :hashed_password, :required => true
|
||||
field :password
|
||||
|
||||
field :created_timestamp, :type => :integer
|
||||
field :email_verification_token, :nullify_if_blank => true
|
||||
field :email_verified?
|
||||
field :hashed_password, :required => true
|
||||
field :password
|
||||
field :password_reset_token, :nullify_if_blank => true
|
||||
|
||||
@@account_email_index_key = Stormy.key('index:account-email')
|
||||
field :role, :required => true
|
||||
|
||||
|
||||
### Class Methods
|
||||
|
|
@ -41,18 +40,8 @@ module Stormy
|
|||
end
|
||||
end
|
||||
|
||||
def self.email_taken?(email)
|
||||
!! redis.hget(@@account_email_index_key, email.to_s.strip.downcase)
|
||||
end
|
||||
|
||||
def self.fetch_by_email(email)
|
||||
if id = id_from_email(email)
|
||||
fetch(id)
|
||||
end
|
||||
end
|
||||
|
||||
def self.reset_password(email)
|
||||
if key = key_from_email(email)
|
||||
if key = key(id_from_email(email))
|
||||
token = redis.hget(key, 'password_reset_token')
|
||||
if token.blank?
|
||||
token = UUID.generate
|
||||
|
|
@ -75,12 +64,8 @@ module Stormy
|
|||
end
|
||||
end
|
||||
|
||||
def self.id_from_email(email)
|
||||
redis.hget(@@account_email_index_key, email.strip.downcase)
|
||||
end
|
||||
|
||||
def self.verify_email(email, token)
|
||||
if key = key_from_email(email)
|
||||
if key = key(id_from_email(email))
|
||||
expected_token = redis.hget(key, 'email_verification_token')
|
||||
verified = token == expected_token
|
||||
if verified
|
||||
|
|
@ -92,21 +77,12 @@ module Stormy
|
|||
end
|
||||
|
||||
def self.email_verified?(email)
|
||||
if key = key_from_email(email)
|
||||
if key = key(id_from_email(email))
|
||||
redis.hget(key, 'email_verified') == 'true'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
### Private Class Methods
|
||||
|
||||
def self.key_from_email(email)
|
||||
key(id_from_email(email))
|
||||
end
|
||||
|
||||
private_class_method :key_from_email
|
||||
|
||||
|
||||
### Instance Methods
|
||||
|
||||
def initialize(fields = {}, options = {})
|
||||
|
|
@ -120,36 +96,20 @@ module Stormy
|
|||
end
|
||||
|
||||
def create
|
||||
raise EmailTakenError if email_taken?
|
||||
|
||||
# new accounts get an id and timestamp
|
||||
self.id = UUID.generate unless id.present?
|
||||
self.created_timestamp = Time.now.to_i
|
||||
|
||||
self.role = 'user' if role.blank?
|
||||
super
|
||||
|
||||
create_email_verification_token
|
||||
|
||||
# add to index
|
||||
redis.hset(@@account_email_index_key, email.downcase, id)
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def delete!
|
||||
project_ids.each { |id| Project.delete!(id) }
|
||||
super
|
||||
redis.hdel(@@account_email_index_key, email.strip.downcase)
|
||||
def has_role?(role)
|
||||
Roles.index(self.role) >= Roles.index(role)
|
||||
end
|
||||
|
||||
def email_taken?(email = @email)
|
||||
self.class.email_taken?(email)
|
||||
end
|
||||
|
||||
def create_email_verification_token
|
||||
self.email_verification_token ||= UUID.generate
|
||||
redis.hset(key, 'email_verification_token', email_verification_token)
|
||||
email_verification_token
|
||||
def email_verification_token
|
||||
unless @email_verification_token
|
||||
@email_verification_token = UUID.generate
|
||||
redis.hset(key, 'email_verification_token', @email_verification_token)
|
||||
end
|
||||
@email_verification_token
|
||||
end
|
||||
|
||||
def password
|
||||
|
|
@ -167,41 +127,16 @@ module Stormy
|
|||
"#{first_name} #{last_name}"
|
||||
end
|
||||
|
||||
def count_projects
|
||||
redis.scard(project_ids_key)
|
||||
end
|
||||
|
||||
def project_ids
|
||||
redis.smembers(project_ids_key)
|
||||
end
|
||||
|
||||
def projects
|
||||
project_ids.map { |pid| Project.fetch(pid) }
|
||||
end
|
||||
|
||||
def sorted_projects
|
||||
@sorted_projects ||= projects.sort { |a,b| a.created_timestamp <=> b.created_timestamp }
|
||||
end
|
||||
|
||||
def add_project_id(id)
|
||||
redis.sadd(project_ids_key, id)
|
||||
end
|
||||
|
||||
def remove_project_id(id)
|
||||
redis.srem(project_ids_key, id)
|
||||
end
|
||||
|
||||
def update_email(new_email)
|
||||
new_email = new_email.strip
|
||||
if email != new_email
|
||||
raise EmailTakenError if new_email.downcase != email.downcase && email_taken?(new_email)
|
||||
raise InvalidDataError.new({ 'email' => 'invalid' }) unless field_valid?('email', new_email)
|
||||
if email.downcase != new_email.downcase
|
||||
self.email_verified = false
|
||||
redis.hdel(@@account_email_index_key, email.downcase)
|
||||
redis.hset(@@account_email_index_key, new_email.downcase, id)
|
||||
end
|
||||
changed = new_email.downcase != email.downcase
|
||||
raise DuplicateFieldError.new(:email => new_email) if changed && email_taken?(new_email)
|
||||
raise InvalidDataError.new('email' => 'invalid') unless field_valid?('email', new_email)
|
||||
self.email_verified = false if changed
|
||||
remove_from_field_index(:email) if changed
|
||||
self.email = new_email
|
||||
add_to_field_index(:email) if changed
|
||||
save!
|
||||
end
|
||||
end
|
||||
|
|
@ -214,13 +149,6 @@ module Stormy
|
|||
self.password = new_password
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def project_ids_key
|
||||
@project_ids_key ||= "#{key}:project-ids"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -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
|
||||
|
||||
class DuplicateFieldError < RuntimeError
|
||||
attr_reader :field
|
||||
def initialize(field = nil)
|
||||
@field = field
|
||||
end
|
||||
end
|
||||
|
||||
def self.clean_number(number)
|
||||
number.gsub(/[^\d]/, '').sub(/^1/, '')
|
||||
end
|
||||
|
||||
# Allows any 10 digit number in North America, or an empty field (for account creation).
|
||||
# Allows any 10 digit number in North America, or an empty field
|
||||
PhoneNumberValidator = proc do |number|
|
||||
if number.present?
|
||||
clean_number(number).length == 10
|
||||
|
|
@ -48,7 +55,7 @@ module Stormy
|
|||
|
||||
|
||||
# Define or retrieve the name of this model.
|
||||
def self.name(name = nil)
|
||||
def self.model_name(name = nil)
|
||||
if name
|
||||
@model_name = name
|
||||
end
|
||||
|
|
@ -61,20 +68,33 @@ module Stormy
|
|||
@fields ||= {}
|
||||
end
|
||||
|
||||
def self.inherit_from(parent)
|
||||
fields.merge!(parent.fields)
|
||||
parent.belongs_to_relationships.each do |key, value|
|
||||
belongs_to_relationships[key] = value.dup
|
||||
end
|
||||
parent.has_many_relationships.each do |key, value|
|
||||
has_many_relationships[key] = value.dup
|
||||
end
|
||||
end
|
||||
|
||||
# Define fields like so:
|
||||
#
|
||||
# field :id, :type => :integer, :required => true
|
||||
# field :name, :required => true, :updatable => true
|
||||
# field :name, :required => true, :updatable => true, :indexed => true
|
||||
# field :email, :required => true, :updatable => true, :unique => true
|
||||
# field :verified?
|
||||
#
|
||||
# Defaults: {
|
||||
# :type => :string,
|
||||
# :required => false,
|
||||
# :updatable => false,
|
||||
# :accessors => true,
|
||||
# :validator => nil, # with some exceptions
|
||||
# :default => {},
|
||||
# :nullify_if_blank => false
|
||||
# :nullify_if_blank => false,
|
||||
# :indexed => false,
|
||||
# :unique => false
|
||||
# }
|
||||
#
|
||||
# Types: :string, :integer, :boolean, :json, as well as
|
||||
|
|
@ -91,12 +111,26 @@ module Stormy
|
|||
# JSON fields accept a :default option used to initialize
|
||||
# a JSON field, and also when a parse fails.
|
||||
#
|
||||
# Attribute accessors are defined for each field and boolean
|
||||
# fields get a predicate method as well, e.g. verified?
|
||||
# Unless :accessors is false, attribute accessors are
|
||||
# defined for each field. Boolean fields get a predicate
|
||||
# method as well, e.g. verified?
|
||||
#
|
||||
# Changed fields are tracked and only changed fields are
|
||||
# persisted on a `save`.
|
||||
#
|
||||
# If the :indexed option is truthy an index on that field
|
||||
# will be created and maintained and there will be a class
|
||||
# method to fetch objects by that field.
|
||||
#
|
||||
# e.g. fetch_by_name(name)
|
||||
#
|
||||
# If the :unique option is truthy then values for that field
|
||||
# must be unique across all instances. This implies :indexed
|
||||
# and adds a method to see if a value is taken, to the class
|
||||
# and to instances.
|
||||
#
|
||||
# e.g. email_taken?(email)
|
||||
#
|
||||
def self.field(name, options = {})
|
||||
if name.to_s.ends_with?('?')
|
||||
options[:type] = :boolean
|
||||
|
|
@ -106,79 +140,182 @@ module Stormy
|
|||
name = name.to_sym
|
||||
options[:type] ||= :string
|
||||
|
||||
unless options.has_key?(:accessors)
|
||||
options[:accessors] = true
|
||||
end
|
||||
|
||||
if options[:unique]
|
||||
options[:indexed] = true
|
||||
end
|
||||
|
||||
case options[:type]
|
||||
when :email
|
||||
options[:validator] ||= EmailAddressValidator
|
||||
options[:validator] = EmailAddressValidator unless options.has_key?(:validator)
|
||||
options[:type] = :string
|
||||
when :phone
|
||||
options[:validator] ||= PhoneNumberValidator
|
||||
options[:validator] = PhoneNumberValidator unless options.has_key?(:validator)
|
||||
options[:type] = :string
|
||||
when :json
|
||||
options[:default] ||= {}
|
||||
options[:default] = {} unless options.has_key?(:default)
|
||||
end
|
||||
|
||||
fields[name] = options
|
||||
define_method(name) do
|
||||
instance_variable_get("@#{name}")
|
||||
end
|
||||
|
||||
case options[:type]
|
||||
when :string
|
||||
define_method("#{name}=") do |value|
|
||||
s =
|
||||
if options[:nullify_if_blank] && value.blank?
|
||||
nil
|
||||
else
|
||||
value.to_s.strip
|
||||
end
|
||||
instance_variable_set("@#{name}", s)
|
||||
changed_fields[name] = s
|
||||
end
|
||||
|
||||
when :integer
|
||||
define_method("#{name}=") do |value|
|
||||
i = value.to_i
|
||||
instance_variable_set("@#{name}", i)
|
||||
changed_fields[name] = i
|
||||
end
|
||||
|
||||
when :boolean
|
||||
define_method("#{name}=") do |value|
|
||||
b = value == 'true' || value == true
|
||||
instance_variable_set("@#{name}", b)
|
||||
changed_fields[name] = b
|
||||
end
|
||||
define_method("#{name}?") do
|
||||
if options[:accessors]
|
||||
define_method(name) do
|
||||
instance_variable_get("@#{name}")
|
||||
end
|
||||
|
||||
when :json
|
||||
define_method(name) do
|
||||
unless value = instance_variable_get("@#{name}")
|
||||
value = options[:default].dup
|
||||
send("#{name}=", value)
|
||||
end
|
||||
value
|
||||
end
|
||||
define_method("#{name}=") do |value|
|
||||
obj =
|
||||
if value.is_a?(String)
|
||||
if value.length > 0
|
||||
JSON.parse(value)
|
||||
case options[:type]
|
||||
when :string
|
||||
define_method("#{name}=") do |value|
|
||||
s =
|
||||
if options[:nullify_if_blank] && value.blank?
|
||||
nil
|
||||
else
|
||||
options[:default].dup
|
||||
value.to_s.strip
|
||||
end
|
||||
else
|
||||
value
|
||||
instance_variable_set("@#{name}", s)
|
||||
changed_fields[name] = s
|
||||
end
|
||||
|
||||
when :integer
|
||||
define_method("#{name}=") do |value|
|
||||
i = value.to_i
|
||||
instance_variable_set("@#{name}", i)
|
||||
changed_fields[name] = i
|
||||
end
|
||||
|
||||
when :boolean
|
||||
define_method("#{name}=") do |value|
|
||||
b = value == 'true' || value == true
|
||||
instance_variable_set("@#{name}", b)
|
||||
changed_fields[name] = b
|
||||
end
|
||||
define_method("#{name}?") do
|
||||
instance_variable_get("@#{name}")
|
||||
end
|
||||
|
||||
when :json
|
||||
define_method(name) do
|
||||
unless value = instance_variable_get("@#{name}")
|
||||
value = options[:default].dup
|
||||
send("#{name}=", value)
|
||||
end
|
||||
instance_variable_set("@#{name}", obj)
|
||||
changed_fields[name] = obj
|
||||
value
|
||||
end
|
||||
define_method("#{name}=") do |value|
|
||||
obj =
|
||||
if value.is_a?(String)
|
||||
if value.length > 0
|
||||
JSON.parse(value)
|
||||
else
|
||||
options[:default].dup
|
||||
end
|
||||
else
|
||||
value
|
||||
end
|
||||
instance_variable_set("@#{name}", obj)
|
||||
changed_fields[name] = obj
|
||||
end
|
||||
|
||||
else
|
||||
define_method("#{name}=") do |value|
|
||||
instance_variable_set("@#{name}", value)
|
||||
changed_fields[name] = value
|
||||
end
|
||||
end
|
||||
end # if options[:accessors]
|
||||
|
||||
if options[:indexed]
|
||||
index_key_method_name = "#{name}_index_key"
|
||||
define_class_method(index_key_method_name) do
|
||||
Stormy.key("index:#{model_name}-#{name}")
|
||||
end
|
||||
define_class_method("fetch_by_#{name}") do |value|
|
||||
if id = send("id_from_#{name}", value)
|
||||
fetch(id)
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
define_method("#{name}=") do |value|
|
||||
instance_variable_set("@#{name}", value)
|
||||
changed_fields[name] = value
|
||||
define_class_method("id_from_#{name}") do |value|
|
||||
redis.hget(send(index_key_method_name), value.to_s.strip.downcase)
|
||||
end
|
||||
end
|
||||
|
||||
if options[:unique]
|
||||
define_class_method("#{name}_taken?") do |value|
|
||||
!! send("id_from_#{name}", value)
|
||||
end
|
||||
|
||||
define_method("#{name}_taken?") do |value|
|
||||
self.class.send("#{name}_taken?", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#####################
|
||||
### Relationships ###
|
||||
#####################
|
||||
|
||||
def self.has_many_relationships
|
||||
@has_many_relationships ||= {}
|
||||
end
|
||||
|
||||
def self.has_many(things, options = {})
|
||||
thing = things.to_s.singularize
|
||||
options[:class_name] ||= thing.capitalize
|
||||
|
||||
has_many_relationships[thing] = options
|
||||
|
||||
define_method("#{thing}_ids_key") do
|
||||
ivar_name = "@#{thing}_ids_key"
|
||||
unless ids_key = instance_variable_get(ivar_name)
|
||||
ids_key = "#{key}:#{thing}-ids"
|
||||
instance_variable_set(ivar_name, ids_key)
|
||||
end
|
||||
ids_key
|
||||
end
|
||||
private "#{thing}_ids_key"
|
||||
|
||||
define_method("count_#{things}") do
|
||||
redis.scard(send("#{thing}_ids_key"))
|
||||
end
|
||||
|
||||
define_method("#{thing}_ids") do
|
||||
redis.smembers(send("#{thing}_ids_key"))
|
||||
end
|
||||
|
||||
define_method(things) do
|
||||
klass = Stormy::Models.const_get(options[:class_name])
|
||||
send("#{thing}_ids").map { |id| klass.fetch(id) }
|
||||
end
|
||||
|
||||
define_method("add_#{thing}_id") do |id|
|
||||
redis.sadd(send("#{thing}_ids_key"), id)
|
||||
end
|
||||
|
||||
define_method("remove_#{thing}_id") do |id|
|
||||
redis.srem(send("#{thing}_ids_key"), id)
|
||||
end
|
||||
end
|
||||
|
||||
def self.belongs_to_relationships
|
||||
@belongs_to_relationships ||= {}
|
||||
end
|
||||
|
||||
def self.belongs_to(thing, options = {})
|
||||
options[:class_name] ||= thing.to_s.capitalize
|
||||
|
||||
field "#{thing}_id".to_sym, :required => options[:required]
|
||||
|
||||
belongs_to_relationships[thing] = options
|
||||
|
||||
define_method(thing) do
|
||||
klass = Stormy::Models.const_get(options[:class_name])
|
||||
if thing_id = send("#{thing}_id")
|
||||
instance_variable_set("@#{thing}", klass.fetch(thing_id))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -246,14 +383,46 @@ module Stormy
|
|||
end
|
||||
|
||||
def create
|
||||
# check for unqiue fields
|
||||
self.class.fields.each do |name, options|
|
||||
if options[:unique] && send("#{name}_taken?", send(name))
|
||||
raise DuplicateFieldError.new(name => send(name))
|
||||
end
|
||||
end
|
||||
|
||||
if has_field?(:id) && field_required?(:id)
|
||||
self.id = UUID.generate unless id.present?
|
||||
end
|
||||
|
||||
if has_field?(:created_timestamp)
|
||||
self.created_timestamp = Time.now.to_i
|
||||
end
|
||||
|
||||
# raises if invalid
|
||||
save
|
||||
add_to_index
|
||||
|
||||
add_to_indexes
|
||||
|
||||
self.class.belongs_to_relationships.each do |thing, options|
|
||||
if obj = send(thing)
|
||||
obj.send("add_#{self.class.model_name}_id", id)
|
||||
end
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def delete!
|
||||
if redis.srem(self.class.model_ids_key, id)
|
||||
self.class.has_many_relationships.each do |thing, options|
|
||||
klass = Stormy::Models.const_get(options[:class_name])
|
||||
send("#{thing}_ids").each { |id| klass.delete!(id) }
|
||||
end
|
||||
if remove_from_indexes
|
||||
self.class.belongs_to_relationships.each do |thing, options|
|
||||
if obj = send(thing)
|
||||
obj.send("remove_#{self.class.model_name}_id", id)
|
||||
end
|
||||
end
|
||||
redis.del(key)
|
||||
end
|
||||
end
|
||||
|
|
@ -280,6 +449,12 @@ module Stormy
|
|||
options[:validate] = true unless options.has_key?(:validate)
|
||||
fields.each do |name, value|
|
||||
if options[:all] || field_updatable?(name)
|
||||
|
||||
# ensure uniqueness
|
||||
if options[:unique] && send("#{name}_taken?", value)
|
||||
raise DuplicateFieldError.new(name => value)
|
||||
end
|
||||
|
||||
send("#{name}=", value)
|
||||
end
|
||||
end
|
||||
|
|
@ -296,6 +471,10 @@ module Stormy
|
|||
end
|
||||
|
||||
def save!
|
||||
if has_field?(:updated_timestamp)
|
||||
self.updated_timestamp = Time.now.to_i
|
||||
end
|
||||
|
||||
# always update JSON fields because they can be updated without our knowledge
|
||||
field_names.each do |name|
|
||||
if field_type(name) == :json
|
||||
|
|
@ -317,6 +496,7 @@ module Stormy
|
|||
end
|
||||
|
||||
def validate
|
||||
# check for invalid fields
|
||||
invalid_fields = field_names.inject({}) do |fields, name|
|
||||
if field_validates?(name)
|
||||
result = validate_field(name, send(name))
|
||||
|
|
@ -329,6 +509,10 @@ module Stormy
|
|||
end
|
||||
end
|
||||
|
||||
def fields
|
||||
Hash[field_names.zip(field_names.map { |name| send(name) })]
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
|
@ -336,8 +520,39 @@ module Stormy
|
|||
@key ||= self.class.key(self.id)
|
||||
end
|
||||
|
||||
def add_to_index
|
||||
redis.sadd(self.class.model_ids_key, self.id)
|
||||
def model_name
|
||||
@model_name ||= self.class.model_name
|
||||
end
|
||||
|
||||
def add_to_indexes
|
||||
if redis.sadd(self.class.model_ids_key, id)
|
||||
self.class.fields.each do |name, options|
|
||||
add_to_field_index(name) if options[:indexed]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_to_field_index(name)
|
||||
index_key = self.class.send("#{name}_index_key")
|
||||
redis.hset(index_key, send(name).to_s.strip.downcase, id)
|
||||
end
|
||||
|
||||
def remove_from_indexes
|
||||
if redis.srem(self.class.model_ids_key, id)
|
||||
success = true
|
||||
self.class.fields.each do |name, options|
|
||||
if options[:indexed]
|
||||
success = success && remove_from_field_index(name)
|
||||
end
|
||||
break unless success
|
||||
end
|
||||
success
|
||||
end
|
||||
end
|
||||
|
||||
def remove_from_field_index(name)
|
||||
index_key = self.class.send("#{name}_index_key")
|
||||
redis.hdel(index_key, send(name).to_s.strip.downcase)
|
||||
end
|
||||
|
||||
def changed_fields
|
||||
|
|
@ -348,6 +563,10 @@ module Stormy
|
|||
self.class.clean_number(number)
|
||||
end
|
||||
|
||||
def has_field?(name)
|
||||
self.class.fields.has_key?(name.to_sym)
|
||||
end
|
||||
|
||||
def field_names
|
||||
self.class.fields.keys
|
||||
end
|
||||
|
|
@ -360,6 +579,10 @@ module Stormy
|
|||
self.class.fields[name.to_sym][:updatable]
|
||||
end
|
||||
|
||||
def field_required?(name)
|
||||
self.class.fields[name.to_sym][:required]
|
||||
end
|
||||
|
||||
def validate_field(name, value)
|
||||
valid = true
|
||||
reason = nil
|
||||
|
|
|
|||
|
|
@ -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 'erubis'
|
||||
require 'json'
|
||||
require 'pony'
|
||||
require 'redis'
|
||||
require 'redis-store'
|
||||
require 'uuid'
|
||||
|
||||
# Ruby extensions
|
||||
require 'hash-ext'
|
||||
|
||||
require 'stormy/models'
|
||||
require 'stormy/controllers'
|
||||
require 'stormy/helpers'
|
||||
|
|
@ -74,8 +70,6 @@ module Stormy
|
|||
if production?
|
||||
body = erb(:'email/error-notification', :layout => false, :locals => {
|
||||
:account => current_account,
|
||||
:project => current_project,
|
||||
:admin => current_admin,
|
||||
:error => env['sinatra.error']
|
||||
})
|
||||
Pony.mail({
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
||||
$('#change-password-link').click(function() {
|
||||
$(this).hide()
|
||||
$('#change-password').show()
|
||||
$('#password-changed').hide()
|
||||
$(this).addClass('hidden')
|
||||
$('#change-password').removeClass('hidden')
|
||||
$('#password-changed').addClass('hidden')
|
||||
$('#old-password').focus()
|
||||
return false
|
||||
})
|
||||
|
|
@ -14,8 +14,8 @@ $(function() {
|
|||
})
|
||||
|
||||
$('#send-email-verification').click(function() {
|
||||
$('#sending-email-verification').show()
|
||||
$(this).hide()
|
||||
$('#sending-email-verification').removeClass('hidden')
|
||||
$(this).addClass('hidden')
|
||||
var self = this
|
||||
$.post('/account/send-email-verification', function(data) {
|
||||
if (data.status === 'ok') {
|
||||
|
|
@ -29,8 +29,8 @@ $(function() {
|
|||
}).error(function() {
|
||||
alert('Failed to send verification email. Try again later.')
|
||||
}).complete(function() {
|
||||
$('#sending-email-verification').hide()
|
||||
$(self).show()
|
||||
$('#sending-email-verification').addClass('hidden')
|
||||
$(self).removeClass('hidden')
|
||||
})
|
||||
return false
|
||||
})
|
||||
|
|
@ -43,14 +43,14 @@ function changePassword() {
|
|||
, confirmation = $('#password-confirmation').val()
|
||||
if ($.trim(oldPassword) && $.trim(newPassword) && newPassword === confirmation) {
|
||||
$('#change-password-form input[type="password"]').removeClass('error')
|
||||
$('#change-password-form input[type="submit"]').hide()
|
||||
$('#change-password-form .spinner').show()
|
||||
$('#change-password-form input[type="submit"]').addClass('hidden')
|
||||
$('#change-password-spinner').removeClass('hidden')
|
||||
$.post('/account/password', $('#change-password-form').serialize(), function(data) {
|
||||
if (data.status === 'ok') {
|
||||
$('input[type="password"]').val('')
|
||||
$('#change-password').hide()
|
||||
$('#change-password-link').show()
|
||||
$('#password-changed').show()
|
||||
$('#change-password').addClass('hidden')
|
||||
$('#change-password-link').removeClass('hidden')
|
||||
$('#password-changed').removeClass('hidden')
|
||||
}
|
||||
// incorrect old password
|
||||
else if (data.reason === 'incorrect') {
|
||||
|
|
@ -72,8 +72,8 @@ function changePassword() {
|
|||
}).error(function(x) {
|
||||
alert('Failed to change password. Try again later.')
|
||||
}).complete(function() {
|
||||
$('#change-password-form input[type="submit"]').show()
|
||||
$('#change-password-form .spinner').hide()
|
||||
$('#change-password-form input[type="submit"]').removeClass('hidden')
|
||||
$('#change-password-spinner').addClass('hidden')
|
||||
})
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
||||
$('input[name="email"]').focus()
|
||||
|
||||
$('#forgot-password-link').click(function() {
|
||||
window.location.href = '/forgot-password/' + $('#email').val()
|
||||
return false
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.9 KiB |
|
|
@ -237,49 +237,6 @@ module Stormy
|
|||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ require 'common'
|
|||
class AccountsControllerTest < Stormy::Test::ControllerCase
|
||||
|
||||
include Stormy::Test::Helpers::Accounts
|
||||
include Stormy::Test::Helpers::Projects
|
||||
|
||||
def setup
|
||||
setup_accounts
|
||||
|
|
@ -32,7 +31,7 @@ class AccountsControllerTest < Stormy::Test::ControllerCase
|
|||
|
||||
def test_sign_up_with_valid_data
|
||||
post '/sign-up', @account_data
|
||||
assert_redirected '/projects'
|
||||
assert_redirected '/account'
|
||||
assert_equal 1, Pony.sent_mail.length
|
||||
assert mail = Pony.sent_mail.shift
|
||||
end
|
||||
|
|
@ -80,17 +79,17 @@ class AccountsControllerTest < Stormy::Test::ControllerCase
|
|||
|
||||
def test_sign_in_submit
|
||||
sign_in
|
||||
assert_redirected '/projects'
|
||||
assert_redirected '/account'
|
||||
end
|
||||
|
||||
def test_sign_in_remember
|
||||
sign_in(@existing_account_data, 'remember' => 'on')
|
||||
assert_redirected '/projects'
|
||||
assert_redirected '/account'
|
||||
|
||||
post '/sign-out'
|
||||
assert_redirected '/'
|
||||
|
||||
get '/projects'
|
||||
get '/account'
|
||||
assert_ok
|
||||
|
||||
# deletes remembered cookie
|
||||
|
|
@ -154,7 +153,7 @@ class AccountsControllerTest < Stormy::Test::ControllerCase
|
|||
|
||||
new_password = 'new password'
|
||||
post '/account/reset-password', { 'password' => new_password }
|
||||
assert_redirected '/projects'
|
||||
assert_redirected '/account'
|
||||
assert_equal @existing_account.id, Account.check_password(@existing_account.email, new_password)
|
||||
assert Account.fetch(@existing_account.id).password == new_password
|
||||
|
||||
|
|
@ -256,7 +255,7 @@ class AccountsControllerTest < Stormy::Test::ControllerCase
|
|||
def test_verify_email
|
||||
get "/account/verify/#{@existing_account.email}/#{@existing_account.email_verification_token}"
|
||||
assert_redirected '/account'
|
||||
assert_nil Account.fetch(@existing_account.id).email_verification_token
|
||||
assert redis.hget(@existing_account.id, 'email_verification_token').blank?
|
||||
end
|
||||
|
||||
def test_verify_email_with_invalid_token_signed_in
|
||||
|
|
|
|||
|
|
@ -7,89 +7,33 @@ require 'common'
|
|||
class AdminControllerTest < Stormy::Test::ControllerCase
|
||||
|
||||
include Stormy::Test::Helpers::Accounts
|
||||
include Stormy::Test::Helpers::Projects
|
||||
include Stormy::Helpers::Admin
|
||||
include Stormy::Helpers::FAQ
|
||||
include Stormy::Helpers::Utils
|
||||
|
||||
def admins
|
||||
@admins ||= fixtures('admins')
|
||||
end
|
||||
|
||||
def setup
|
||||
@existing_admin_data = admins['sami']
|
||||
@existing_admin = Admin.create(@existing_admin_data)
|
||||
|
||||
@admin_data = admins['freddy']
|
||||
setup_accounts
|
||||
end
|
||||
|
||||
def teardown
|
||||
post '/admin/sign-out'
|
||||
Admin.list_ids.each do |id|
|
||||
Admin.delete!(id)
|
||||
end
|
||||
post '/sign-out'
|
||||
teardown_accounts
|
||||
end
|
||||
|
||||
def sign_in(admin = @existing_admin_data)
|
||||
post '/admin/sign-in', admin
|
||||
def sign_in(admin = @existing_account_data)
|
||||
post '/sign-in', admin
|
||||
end
|
||||
|
||||
|
||||
#####################
|
||||
### Sign In & Out ###
|
||||
#####################
|
||||
|
||||
def test_sign_in
|
||||
get '/admin/sign-in'
|
||||
assert_ok
|
||||
end
|
||||
|
||||
def test_sign_in_submit
|
||||
sign_in
|
||||
assert_redirected '/admin'
|
||||
end
|
||||
|
||||
def test_sign_in_with_invalid_credentials
|
||||
sign_in(@admin_data)
|
||||
assert_redirected '/admin/sign-in'
|
||||
end
|
||||
|
||||
def test_sign_in_redirect
|
||||
sign_in
|
||||
assert_redirected '/admin'
|
||||
end
|
||||
|
||||
def test_sign_out
|
||||
post '/admin/sign-out'
|
||||
assert_redirected '/admin'
|
||||
end
|
||||
|
||||
|
||||
############################
|
||||
### Dashboard & Password ###
|
||||
############################
|
||||
#################
|
||||
### Dashboard ###
|
||||
#################
|
||||
|
||||
def test_dashboard
|
||||
sign_in
|
||||
get '/admin'
|
||||
assert_ok
|
||||
assert last_response.body.match(/<title>Dashboard/)
|
||||
end
|
||||
|
||||
def test_change_password
|
||||
sign_in
|
||||
get '/admin/password'
|
||||
assert_ok
|
||||
|
||||
new_password = 'new password'
|
||||
post '/admin/password', { 'password' => new_password, 'password_confirmation' => new_password }
|
||||
assert_redirected '/admin'
|
||||
@existing_admin.reload!
|
||||
assert @existing_admin.password == new_password
|
||||
|
||||
# incorrect confirmation
|
||||
post '/admin/password', { 'password' => new_password, 'password_confirmation' => 'oops' }
|
||||
assert_redirected '/admin/password'
|
||||
assert last_response.body.match(/<title>[^<]*Dashboard[^<]*<\/title>/)
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -104,7 +48,6 @@ class AdminControllerTest < Stormy::Test::ControllerCase
|
|||
end
|
||||
|
||||
def test_account
|
||||
setup_accounts
|
||||
sign_in
|
||||
|
||||
get '/admin/account/' + @existing_account.email
|
||||
|
|
@ -113,12 +56,9 @@ class AdminControllerTest < Stormy::Test::ControllerCase
|
|||
get '/admin/account/not@an.account'
|
||||
# this was the previous listing, kind of weird but meh
|
||||
assert_redirected '/admin/account/' + @existing_account.email
|
||||
|
||||
teardown_accounts
|
||||
end
|
||||
|
||||
def test_update_account
|
||||
setup_accounts
|
||||
sign_in
|
||||
|
||||
# redirected to proper page when changing email addresses
|
||||
|
|
@ -162,94 +102,34 @@ class AdminControllerTest < Stormy::Test::ControllerCase
|
|||
assert_equal 'Samson', @existing_account.first_name
|
||||
assert_equal 'Simpson', @existing_account.last_name
|
||||
assert_equal '+12501234567', @existing_account.phone
|
||||
|
||||
teardown_accounts
|
||||
end
|
||||
|
||||
def test_sign_in_as_user
|
||||
setup_accounts
|
||||
sign_in
|
||||
|
||||
get '/admin/sign-in-as/' + @existing_account.email
|
||||
assert_equal @existing_account.id, session[:id]
|
||||
assert_redirected '/projects'
|
||||
|
||||
teardown_accounts
|
||||
assert_redirected '/account'
|
||||
end
|
||||
|
||||
def test_delete_account
|
||||
setup_accounts
|
||||
setup_projects
|
||||
sign_in
|
||||
|
||||
# make sure the last listing is marked so we are redirected correctly
|
||||
get '/admin/accounts'
|
||||
assert_ok
|
||||
|
||||
get "/admin/account/#{@existing_account.email}/delete"
|
||||
@other_account = Account.create(@account_data)
|
||||
get "/admin/account/#{@other_account.email}/delete"
|
||||
assert_redirected '/admin/accounts'
|
||||
|
||||
assert_nil Account.fetch(@existing_account.id)
|
||||
assert_nil Project.fetch(@existing_project.id)
|
||||
assert_nil Account.fetch(@other_account.id)
|
||||
|
||||
# non-existent accounts are already gone, so no problem
|
||||
get "/admin/account/nobody@nowhere.net/delete"
|
||||
|
||||
# this time the last listing was not marked, so we are redirected to the dashboard
|
||||
assert_redirected '/admin'
|
||||
|
||||
teardown_projects
|
||||
teardown_accounts
|
||||
end
|
||||
|
||||
|
||||
################
|
||||
### Projects ###
|
||||
################
|
||||
|
||||
def test_projects
|
||||
setup_accounts
|
||||
setup_projects
|
||||
sign_in
|
||||
|
||||
get '/admin/projects'
|
||||
assert_ok
|
||||
|
||||
teardown_projects
|
||||
teardown_accounts
|
||||
end
|
||||
|
||||
def test_project
|
||||
setup_accounts
|
||||
setup_projects
|
||||
sign_in
|
||||
|
||||
# non-existent project
|
||||
get '/admin/project/999'
|
||||
assert_redirected '/admin'
|
||||
|
||||
# existing project
|
||||
get '/admin/project/' + @existing_project.id
|
||||
assert_ok
|
||||
|
||||
teardown_projects
|
||||
teardown_accounts
|
||||
end
|
||||
|
||||
def test_delete_project
|
||||
setup_accounts
|
||||
setup_projects
|
||||
sign_in
|
||||
|
||||
# make sure the last listing is marked so we are redirected correctly
|
||||
get '/admin/projects'
|
||||
|
||||
get "/admin/project/#{@existing_project.id}/delete"
|
||||
assert_redirected '/admin/projects'
|
||||
assert_nil Project.fetch(@existing_project.id)
|
||||
|
||||
teardown_projects
|
||||
teardown_accounts
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -276,51 +156,4 @@ class AdminControllerTest < Stormy::Test::ControllerCase
|
|||
self.faq = original_faq
|
||||
end
|
||||
|
||||
|
||||
######################
|
||||
### Admin Accounts ###
|
||||
######################
|
||||
|
||||
def test_admins
|
||||
sign_in
|
||||
get '/admin/admins'
|
||||
assert_ok
|
||||
end
|
||||
|
||||
def test_add_admin
|
||||
sign_in
|
||||
|
||||
password = 'password'
|
||||
fields = {
|
||||
'name' => 'Freddy Kruger',
|
||||
'email' => 'freddy@example.com',
|
||||
'password' => password,
|
||||
'password_confirmation' => password
|
||||
}
|
||||
post '/admin/admins', fields
|
||||
assert_redirected '/admin/admins'
|
||||
admin = Admin.fetch_by_email('freddy@example.com')
|
||||
assert admin.password == password
|
||||
assert_equal fields['name'], admin.name
|
||||
assert_equal fields['email'], admin.email
|
||||
|
||||
# passwords do not match
|
||||
fields = {
|
||||
'name' => 'Jason Vorhees',
|
||||
'email' => 'jason@example.com',
|
||||
'password' => 'my password',
|
||||
'password_confirmation' => 'not the same password'
|
||||
}
|
||||
post '/admin/admins', fields
|
||||
assert_redirected '/admin/admins'
|
||||
assert_nil Admin.fetch_by_email('jason@example.com')
|
||||
end
|
||||
|
||||
def test_delete_admin
|
||||
sign_in
|
||||
get "/admin/admins/#{@existing_admin.id}/delete"
|
||||
assert_redirected '/admin/admins'
|
||||
assert_equal 0, Admin.count
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
, "phone" : "250-216-6216"
|
||||
, "password" : "super secret"
|
||||
, "role" : "admin"
|
||||
}
|
||||
|
||||
, "freddy": {
|
||||
|
|
@ -12,5 +13,6 @@
|
|||
, "email" : "freddy@example.com"
|
||||
, "phone" : "(250) 555-9999"
|
||||
, "password" : "even more secreter"
|
||||
, "role" : "user"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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::Test::Helpers::Accounts
|
||||
include Stormy::Test::Helpers::Projects
|
||||
|
||||
def setup
|
||||
setup_accounts
|
||||
|
|
|
|||
|
|
@ -16,14 +16,6 @@ class AdminHelperTest < Stormy::Test::HelperCase
|
|||
assert_equal 0, num_accounts
|
||||
end
|
||||
|
||||
def test_num_admins
|
||||
assert_equal 0, num_admins
|
||||
end
|
||||
|
||||
def test_num_projects
|
||||
assert_equal 0, num_projects
|
||||
end
|
||||
|
||||
def test_last_listing
|
||||
assert_equal '/admin', last_listing
|
||||
mark_last_listing '/admin/accounts'
|
||||
|
|
|
|||
|
|
@ -8,21 +8,16 @@ class AuthorizationHelperTest < Stormy::Test::HelperCase
|
|||
|
||||
include Stormy::Helpers::Authorization
|
||||
include Stormy::Test::Helpers::Accounts
|
||||
include Stormy::Test::Helpers::Admins
|
||||
include Stormy::Test::Helpers::Projects
|
||||
|
||||
def setup
|
||||
setup_accounts
|
||||
setup_projects
|
||||
end
|
||||
|
||||
def teardown
|
||||
deauthorize
|
||||
teardown_request
|
||||
teardown_accounts
|
||||
teardown_projects
|
||||
@current_account = nil
|
||||
@current_project = nil
|
||||
end
|
||||
|
||||
def teardown_request
|
||||
|
|
@ -69,7 +64,7 @@ class AuthorizationHelperTest < Stormy::Test::HelperCase
|
|||
end
|
||||
|
||||
def url
|
||||
'/projects'
|
||||
'/account'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -153,116 +148,31 @@ class AuthorizationHelperTest < Stormy::Test::HelperCase
|
|||
assert_equal @existing_account.id, current_account.id
|
||||
end
|
||||
|
||||
def test_current_project
|
||||
assert_nil current_project
|
||||
|
||||
current_project(@existing_project.id)
|
||||
assert_equal @existing_project.id, current_project.id
|
||||
end
|
||||
|
||||
def test_project_authorized?
|
||||
assert !project_authorized?
|
||||
|
||||
current_project(@existing_project.id)
|
||||
assert !project_authorized?
|
||||
|
||||
authorize_account(@existing_account.id)
|
||||
assert project_authorized?
|
||||
|
||||
@current_account = nil
|
||||
other_account = Account.create(@account_data)
|
||||
authorize_account(other_account)
|
||||
assert !project_authorized?
|
||||
other_account.delete!
|
||||
end
|
||||
|
||||
def test_authorize_project_api!
|
||||
assert_equal not_authorized, catch(:halt) { authorize_project_api!(@existing_project.id) }
|
||||
assert_content_type 'text/plain'
|
||||
@content_type = nil
|
||||
|
||||
authorize_account(@existing_account.id)
|
||||
authorize_project_api!(@existing_project.id)
|
||||
|
||||
assert_equal fail('no such project'), catch(:halt) { authorize_project_api!('non-existent id') }
|
||||
|
||||
@current_account = nil
|
||||
other_account = Account.create(@account_data)
|
||||
authorize_account(other_account.id)
|
||||
assert_equal not_authorized, catch(:halt) { authorize_project_api!(@existing_project.id) }
|
||||
assert_content_type 'text/plain'
|
||||
other_account.delete!
|
||||
end
|
||||
|
||||
def test_authorize_project!
|
||||
assert_redirected('/sign-in') { authorize_project!(@existing_project.id) }
|
||||
assert !project_authorized?
|
||||
|
||||
authorize_account(@existing_account.id)
|
||||
assert_redirected('/projects') { authorize_project!('non-existent id') }
|
||||
assert_equal 'No such project.', flash[:warning]
|
||||
@redirect = nil
|
||||
|
||||
authorize_project!(@existing_project.id)
|
||||
assert_not_redirected
|
||||
|
||||
@current_account = nil
|
||||
other_account = Account.create(@account_data)
|
||||
authorize_account(other_account.id)
|
||||
assert_redirected('/projects') { authorize_project!(@existing_project.id) }
|
||||
assert_equal 'No such project.', flash[:warning]
|
||||
other_account.delete!
|
||||
end
|
||||
|
||||
def test_authorize_admin
|
||||
setup_admins
|
||||
authorize_admin(@admin.id)
|
||||
assert_equal @admin.id, session[:admin_id]
|
||||
assert admin_authorized?
|
||||
teardown_admins
|
||||
end
|
||||
|
||||
def test_deauthorize_admin
|
||||
setup_admins
|
||||
authorize_admin(@admin.id)
|
||||
|
||||
deauthorize_admin
|
||||
assert !admin_authorized?
|
||||
assert !session.has_key?(:admin_id)
|
||||
teardown_admins
|
||||
end
|
||||
|
||||
def test_admin_authorized?
|
||||
setup_admins
|
||||
assert !admin_authorized?
|
||||
authorize_admin(@admin.id)
|
||||
authorize_account(@existing_account.id)
|
||||
assert admin_authorized?
|
||||
|
||||
@current_admin = nil
|
||||
authorize_admin('does not exist')
|
||||
@current_account = nil
|
||||
authorize_account('does not exist')
|
||||
assert !admin_authorized?
|
||||
teardown_admins
|
||||
end
|
||||
|
||||
def test_admin_authorize!
|
||||
setup_admins
|
||||
authorize_admin(@admin.id)
|
||||
authorize_account(@existing_account.id)
|
||||
admin_authorize!
|
||||
assert_not_redirected
|
||||
assert !session.has_key?(:original_url)
|
||||
teardown_admins
|
||||
end
|
||||
|
||||
def test_admin_authorize_redirects_to_sign_in
|
||||
assert_redirected('/admin') { admin_authorize! }
|
||||
assert_redirected('/sign-in') { admin_authorize! }
|
||||
assert_equal request.url, session[:original_url]
|
||||
end
|
||||
|
||||
def test_admin_authorize_api!
|
||||
setup_admins
|
||||
authorize_admin(@admin.id)
|
||||
authorize_account(@existing_account.id)
|
||||
assert_nil catch(:halt) { admin_authorize_api! }
|
||||
teardown_admins
|
||||
end
|
||||
|
||||
def test_admin_authorize_api_throws_if_unauthorized
|
||||
|
|
@ -270,13 +180,4 @@ class AuthorizationHelperTest < Stormy::Test::HelperCase
|
|||
assert_content_type 'text/plain'
|
||||
end
|
||||
|
||||
def test_current_admin
|
||||
setup_admins
|
||||
assert_nil current_admin
|
||||
|
||||
authorize_admin(@admin.id)
|
||||
assert_equal @admin.id, current_admin.id
|
||||
teardown_admins
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -203,4 +203,11 @@ class ViewsHelperTest < Stormy::Test::HelperCase
|
|||
assert_equal "<p>42</p>\n", markdown(42)
|
||||
end
|
||||
|
||||
def test_admin_page?
|
||||
assert admin_page?('/admin')
|
||||
assert admin_page?('/admin/accounts')
|
||||
assert !admin_page?('/')
|
||||
assert !admin_page?('/account')
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,11 +7,9 @@ require 'common'
|
|||
class AccountTest < Stormy::Test::Case
|
||||
|
||||
include Stormy::Test::Helpers::Accounts
|
||||
include Stormy::Test::Helpers::Projects
|
||||
|
||||
def setup
|
||||
setup_accounts
|
||||
setup_projects
|
||||
|
||||
@invalid_addresses = [
|
||||
'invalid email address',
|
||||
|
|
@ -22,7 +20,6 @@ class AccountTest < Stormy::Test::Case
|
|||
end
|
||||
|
||||
def teardown
|
||||
teardown_projects
|
||||
teardown_accounts
|
||||
end
|
||||
|
||||
|
|
@ -75,7 +72,7 @@ class AccountTest < Stormy::Test::Case
|
|||
assert Account.verify_email(@existing_account.email, @existing_account.email_verification_token)
|
||||
account = Account.fetch(@existing_account.id)
|
||||
assert account.email_verified
|
||||
assert !account.email_verification_token
|
||||
assert !account.instance_variable_get('@email_verification_token')
|
||||
assert !Account.verify_email('non@existent.email', 'insignificant token')
|
||||
end
|
||||
|
||||
|
|
@ -90,15 +87,12 @@ class AccountTest < Stormy::Test::Case
|
|||
|
||||
# no new token is generated if one is present
|
||||
token = @existing_account.email_verification_token
|
||||
@existing_account.create_email_verification_token
|
||||
assert_equal @existing_account.email_verification_token, token
|
||||
|
||||
# a token is generated if necessary
|
||||
Account.verify_email(@existing_account.email, @existing_account.email_verification_token) # clears token
|
||||
account = Account.fetch(@existing_account.id)
|
||||
assert !account.email_verification_token
|
||||
account.create_email_verification_token
|
||||
assert account.email_verification_token
|
||||
assert !account.instance_variable_get('@email_verification_token')
|
||||
assert account.email_verification_token != token
|
||||
end
|
||||
|
||||
|
|
@ -132,7 +126,7 @@ class AccountTest < Stormy::Test::Case
|
|||
end
|
||||
|
||||
def test_create_with_existing_email
|
||||
assert_raises Account::EmailTakenError do
|
||||
assert_raises Account::DuplicateFieldError do
|
||||
Account.new(@existing_account_data).create
|
||||
end
|
||||
end
|
||||
|
|
@ -195,46 +189,14 @@ class AccountTest < Stormy::Test::Case
|
|||
assert Account.fetch_by_email(@existing_account.email).nil?, 'Account was fetched by email after deletion'
|
||||
|
||||
# indexes
|
||||
assert !@existing_account.email_taken?, 'Account email is taken after deletion'
|
||||
assert !@existing_account.email_taken?(@existing_account.email), 'Account email is taken after deletion'
|
||||
assert !Account.exists?(@existing_account.id), 'Account exists after deletion'
|
||||
|
||||
# projects are deleted
|
||||
assert_equal [], @existing_account.project_ids
|
||||
end
|
||||
|
||||
def test_name
|
||||
assert_equal "#{@existing_account.first_name} #{@existing_account.last_name}", @existing_account.name
|
||||
end
|
||||
|
||||
def test_count_projects
|
||||
assert_equal 1, @existing_account.count_projects
|
||||
end
|
||||
|
||||
def test_project_ids
|
||||
assert_equal [@existing_project.id], @existing_account.project_ids
|
||||
end
|
||||
|
||||
def test_projects
|
||||
assert_equal [@existing_project.id], @existing_account.projects.map { |p| p.id }
|
||||
end
|
||||
|
||||
def test_sorted_projects
|
||||
# make sure created timestamp is in the future ... this stinks
|
||||
sleep 1
|
||||
project = Project.create(@new_project_data.merge(:account_id => @existing_account.id))
|
||||
assert_equal [@existing_project.id, project.id], @existing_account.sorted_projects.map { |p| p.id }
|
||||
end
|
||||
|
||||
def test_add_project_id
|
||||
@existing_account.add_project_id('fake-project-id')
|
||||
assert_equal 2, @existing_account.count_projects
|
||||
end
|
||||
|
||||
def test_remove_project_id
|
||||
@existing_account.remove_project_id(@existing_project.id)
|
||||
assert_equal 0, @existing_account.count_projects
|
||||
end
|
||||
|
||||
def test_update
|
||||
original_data = {
|
||||
'id' => @existing_account.id,
|
||||
|
|
@ -377,7 +339,7 @@ class AccountTest < Stormy::Test::Case
|
|||
# all should be updated
|
||||
check_account_fields(@existing_account, updated_data)
|
||||
|
||||
# restore fields required for project clean up
|
||||
# restore fields required for clean up
|
||||
@existing_account.update!(original_data)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
@my_model_class = Class.new(Stormy::Models::Base)
|
||||
@my_model_class.class_eval do
|
||||
model_name 'my_model'
|
||||
field :id, :required => true
|
||||
field :name, :updatable => true, :required => true
|
||||
field :name, :updatable => true, :required => true, :indexed => true
|
||||
field :email, :required => true, :unique => true
|
||||
field :age, {
|
||||
:type => :integer,
|
||||
:required => true,
|
||||
|
|
@ -18,14 +20,8 @@ class ModelBaseTest < Stormy::Test::Case
|
|||
:validator => proc { |n| n >= 18 }
|
||||
}
|
||||
field :verified, :type => :boolean
|
||||
|
||||
def create
|
||||
self.id = UUID.generate unless id.present?
|
||||
super
|
||||
end
|
||||
|
||||
end
|
||||
@fields = { 'name' => 'Sami', 'age' => '29' }
|
||||
@fields = { 'name' => 'Sami', 'age' => '29', 'email' => 'sami@samhuri.net' }
|
||||
@my_model = @my_model_class.create(@fields)
|
||||
end
|
||||
|
||||
|
|
@ -39,15 +35,16 @@ class ModelBaseTest < Stormy::Test::Case
|
|||
### Class Methods
|
||||
|
||||
def test_name
|
||||
@my_model_class.name 'my_model'
|
||||
assert_equal 'my_model', @my_model_class.name
|
||||
@my_model_class.model_name 'my_model'
|
||||
assert_equal 'my_model', @my_model_class.model_name
|
||||
end
|
||||
|
||||
def test_id_field
|
||||
# has id by default
|
||||
# has id
|
||||
id_field = {
|
||||
:type => :string,
|
||||
:required => true
|
||||
:required => true,
|
||||
:accessors => true
|
||||
}
|
||||
assert_equal(id_field, @my_model_class.fields[:id])
|
||||
methods = %w[id id=]
|
||||
|
|
@ -60,7 +57,9 @@ class ModelBaseTest < Stormy::Test::Case
|
|||
name_field = {
|
||||
:type => :string,
|
||||
:required => true,
|
||||
:updatable => true
|
||||
:updatable => true,
|
||||
:accessors => true,
|
||||
:indexed => true
|
||||
}
|
||||
assert_equal(name_field, @my_model_class.fields[:name])
|
||||
methods = %w[name name=]
|
||||
|
|
@ -85,7 +84,7 @@ class ModelBaseTest < Stormy::Test::Case
|
|||
end
|
||||
|
||||
def test_verified_field
|
||||
verified_field = { :type => :boolean }
|
||||
verified_field = { :type => :boolean, :accessors => true }
|
||||
assert_equal(verified_field, @my_model_class.fields[:verified])
|
||||
methods = %w[verified verified= verified?]
|
||||
methods.each do |name|
|
||||
|
|
@ -125,7 +124,7 @@ class ModelBaseTest < Stormy::Test::Case
|
|||
|
||||
def test_key
|
||||
id = @my_model.id
|
||||
key = Stormy.key(@my_model_class.name, id)
|
||||
key = Stormy.key(@my_model_class.model_name, id)
|
||||
assert_equal key, @my_model_class.key(id)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
# (none yet)
|
||||
# assert_equal defaults['demo_project_id'], @config.demo_project_id
|
||||
# assert_equal defaults['foo_bar'], @config.foo_bar
|
||||
end
|
||||
|
||||
def test_method_missing_get_default
|
||||
|
|
|
|||
|
|
@ -1,65 +1,82 @@
|
|||
<h2 class="section-heading top">Account</h2>
|
||||
|
||||
<%== flash_message %>
|
||||
|
||||
<table id="account">
|
||||
<tr>
|
||||
<th>First name</th>
|
||||
<td><span id="first_name" class="editable"><%= @account.first_name %></span></td>
|
||||
<td rowspan="4" class="edit-instructions">
|
||||
← click to edit
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Last name</th>
|
||||
<td><span id="last_name" class="editable"><%= @account.last_name %></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<td>
|
||||
<span id="email" class="editable-json"><%= @account.email %></span>
|
||||
<span id="email-verified" class="verified <%= @account.email_verified? ? '' : 'hidden' %>">✓ Verified</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Phone</th>
|
||||
<td>
|
||||
<span id="phone" class="editable"><%= @account.phone %></span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
|
||||
<p class="indented"><a href="#" id="change-password-link">Change my password</a></p>
|
||||
<div class="row">
|
||||
<div class="fields span6">
|
||||
<h2>Your Account</h2>
|
||||
|
||||
<form action="/account/password" method="post" id="change-password-form">
|
||||
<table id="change-password">
|
||||
<tr>
|
||||
<th><label for="old-password">Current password</label></th>
|
||||
<td><input type="password" id="old-password" name="old-password" placeholder="Current password"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="new-password">New password</label></th>
|
||||
<td><input type="password" id="new-password" name="new-password" placeholder="Secret"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="password-confirmation">Confirm</label></th>
|
||||
<td><input type="password" id="password-confirmation" name="password-confirmation" placeholder="Again"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th></th>
|
||||
<td align="right">
|
||||
<input type="submit" id="change-password-button" value="Change My Password">
|
||||
<img class="spinner" src="/images/spinner.gif">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
<div class="clearfix">
|
||||
<p>
|
||||
<strong>First name</strong>
|
||||
<br>
|
||||
<span id="first_name" class="editable"><%= @account.first_name %></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p id="password-changed" class="indented">Password changed!</p>
|
||||
<div class="clearfix">
|
||||
<p>
|
||||
<strong>Last name</strong>
|
||||
<br>
|
||||
<span id="last_name" class="editable"><%= @account.last_name %></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="email-verification" class="indented <%= @account.email_verified? ? 'hidden' : '' %>">
|
||||
<hr>
|
||||
<p id="unverified-email" class="unverified">Your email address is unverified.</p>
|
||||
<p><a href="#" id="send-email-verification">Send verification email</a></p>
|
||||
<p id="sending-email-verification"><img src="/images/spinner.gif"> Sending...</p>
|
||||
<div class="clearfix">
|
||||
<p>
|
||||
<strong>Phone</strong>
|
||||
<br>
|
||||
<span id="phone" class="editable"><%= @account.phone %></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
<span id="email-verified" class="verified <%= @account.email_verified? ? '' : 'hidden' %>">✓ Verified</span>
|
||||
<p>
|
||||
<strong>Email</strong>
|
||||
<br>
|
||||
<span id="email" class="editable-json"><%= @account.email %></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="email-verification" class="clearfix <%= @account.email_verified? ? 'hidden' : '' %>">
|
||||
<p id="unverified-email" class="unverified">Your email address is unverified.</p>
|
||||
<p><button id="send-email-verification" class="btn">Send verification email</button></p>
|
||||
<p id="sending-email-verification" class="hidden"><img src="/images/spinner.gif"> Sending...</p>
|
||||
</div>
|
||||
|
||||
</div> <!-- fields -->
|
||||
|
||||
<div class="fields span6">
|
||||
<hr class="separator">
|
||||
|
||||
<h2>Password</h2>
|
||||
|
||||
<form action="/account/password" method="post" id="change-password-form">
|
||||
<fieldset>
|
||||
<div class="clearfix">
|
||||
<label for="old-password">Current password</label>
|
||||
<input type="password" id="old-password" name="old-password" placeholder="Current password">
|
||||
</div>
|
||||
<div class="clearfix">
|
||||
<label for="new-password">New password</label>
|
||||
<input type="password" id="new-password" name="new-password" placeholder="Secret">
|
||||
</div>
|
||||
<div class="clearfix">
|
||||
<label for="password-confirmation">Confirm</label>
|
||||
<input type="password" id="password-confirmation" name="password-confirmation" placeholder="Again">
|
||||
</div>
|
||||
<div class="clearfix">
|
||||
<input type="submit" id="change-password-button" class="btn" value="Change my password">
|
||||
<span id="change-password-spinner" class="hidden"><img class="spinner" src="/images/spinner.gif"> Changing...</span>
|
||||
</div>
|
||||
<p id="password-changed" class="notice hidden">Password changed!</p>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
</div> <!-- fields -->
|
||||
</div> <!-- row -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
<th>Projects</th>
|
||||
</tr>
|
||||
|
||||
<% for account in @accounts %>
|
||||
|
|
@ -12,7 +11,6 @@
|
|||
<td class="name"><a href="/admin/account/<%= account.email %>"><%= account.name %></a></td>
|
||||
<td><%= account.email %></td>
|
||||
<td><%= account.phone %></td>
|
||||
<td><%= account.count_projects %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
<h2 class="section-heading top">About Us</h2>
|
||||
<h3 class="section-heading top">About Us</h3>
|
||||
<p class="section">
|
||||
Made of 100% pure awesome. #winning
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="contact-us">
|
||||
<h2 class="section-heading top">Contact Us</h2>
|
||||
<h3 class="section-heading top">Contact Us</h3>
|
||||
|
||||
<%== flash_message %>
|
||||
|
||||
|
|
@ -23,28 +23,28 @@
|
|||
</div>
|
||||
|
||||
<div id="contact-email">
|
||||
<h2 class="section-heading">Email</h2>
|
||||
<h3 class="section-heading">Email</h3>
|
||||
|
||||
<p class="section">Contact us via email.</p>
|
||||
|
||||
<table class="section">
|
||||
<tr>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
<% if admin %>
|
||||
<p><strong>Admin:</strong> <%= admin.name %> <<%= admin.email %>></p>
|
||||
<pre><%= admin.field_array %></pre>
|
||||
<% end %>
|
||||
|
||||
<% if account %>
|
||||
<p><strong>Account:</strong> <%= account.name %> <<%= account.email %>></p>
|
||||
<pre><%= account.field_array %></pre>
|
||||
<% end %>
|
||||
|
||||
<% if project %>
|
||||
<p><strong>Project:</strong> <%= project.id %> (<%= project.name %>)</p>
|
||||
<pre><%= project.field_array %></pre>
|
||||
<% end %>
|
||||
|
||||
<pre>
|
||||
<%= 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">
|
||||
<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">
|
||||
<%== @faq %>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<h2 class="section-heading top">Forgot Password</h2>
|
||||
<h3 class="section-heading top">Forgot Password</h3>
|
||||
|
||||
<%== flash_message %>
|
||||
|
||||
|
|
|
|||
118
views/layout.erb
|
|
@ -3,9 +3,22 @@
|
|||
|
||||
<head>
|
||||
<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 %>
|
||||
<link rel="stylesheet" href="<%= url %>" charset="utf-8">
|
||||
|
|
@ -15,50 +28,87 @@
|
|||
|
||||
<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>
|
||||
<% end %>
|
||||
|
||||
<div id="wrapper">
|
||||
|
||||
<p id="nav" class="<%= authorized? ? 'authorized' : '' %>">
|
||||
<% if authorized? %>
|
||||
Signed in as <%= current_account.email %>
|
||||
<br>
|
||||
<a href="/projects">Projects</a>
|
||||
|
|
||||
<a href="/account">Account</a>
|
||||
|
|
||||
<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>
|
||||
<div class="container">
|
||||
<% if breadcrumbs.size > 0 %>
|
||||
<ul class="breadcrumb">
|
||||
<% breadcrumbs.each_with_index do |crumb, i| %>
|
||||
<li class="<%= crumb[:active] ? 'active' : '' %>">
|
||||
<a href="<%= crumb[:path] %>"><%= crumb[:name] %></a>
|
||||
<% if i < breadcrumbs.size - 1%>
|
||||
<span class="divider">/</span>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
</p>
|
||||
</ul>
|
||||
<% end %>
|
||||
|
||||
<div id="header">
|
||||
<a id="logo" href="/"><img src="/images/logo.png" width="600" height="72" alt="Stormy Weather"></a>
|
||||
</div>
|
||||
<%== flash_message %>
|
||||
|
||||
<div id="content">
|
||||
<%== yield %>
|
||||
</div>
|
||||
<%== yield %>
|
||||
|
||||
<div id="footer">
|
||||
<p id="copyright">© 2012 Sami Samhuri</p>
|
||||
<hr>
|
||||
|
||||
<footer>
|
||||
<p>© 2012 Sami Samhuri</p>
|
||||
<p>
|
||||
<a href="/contact">About Us</a>
|
||||
<a href="/contact">Contact Us</a>
|
||||
•
|
||||
<a href="/faq">FAQ</a>
|
||||
•
|
||||
<a href="/contact">Contact Us</a>
|
||||
•
|
||||
<a href="/terms">Terms of Service</a>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div><!--/.fluid-container-->
|
||||
|
||||
<% for url in scripts %>
|
||||
<script src="<%= url %>"></script>
|
||||
|
|
@ -68,7 +118,7 @@
|
|||
if (false) {
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-XXXXXXXX-X']);
|
||||
_gaq.push(['_setDomainName', 'example.com']);
|
||||
_gaq.push(['_setDomainName', 'example.comapult.com']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,9 @@
|
|||
|
||||
<h2 class="section-heading top">:(</h2>
|
||||
<h3 class="section-heading top">:(</h3>
|
||||
|
||||
<div class="section">
|
||||
<p align="center">Hmmm... We don't seem to have what you're looking for.</p>
|
||||
<p align="center">
|
||||
<% if authorized? %>
|
||||
<a href="/projects">
|
||||
<% else %>
|
||||
<a href="/">
|
||||
<% end %>
|
||||
Ok fine, I'm outta here!
|
||||
</a>
|
||||
<a href="/">Ok fine, I'm outta here!</a>
|
||||
</p>
|
||||
</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 %>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,43 +1,40 @@
|
|||
<%== flash_message %>
|
||||
|
||||
<form id="sign-in-form" action="/sign-in" method="post">
|
||||
<table id="sign-in">
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<div class="row">
|
||||
<div class="sign-in-form">
|
||||
<h2>Sign in</h2>
|
||||
|
||||
<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>
|
||||
<form id="sign-in-form" action="/sign-in" method="post">
|
||||
<fieldset>
|
||||
<div class="clearfix">
|
||||
<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>
|
||||
<th><label for="password">Password</label></th>
|
||||
<td id="password-cell">
|
||||
<input type="password" name="password" id="password" size="30" placeholder="Secret">
|
||||
</td>
|
||||
</tr>
|
||||
</div><!-- sign-in-form -->
|
||||
</div><!-- row -->
|
||||
</div> <!-- content -->
|
||||
|
||||
<tr>
|
||||
<th></th>
|
||||
<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>
|
||||
<div class="row other-actions">
|
||||
<p style="float:right">
|
||||
Not a member yet? <a href="/sign-up">Sign Up!</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</form>
|
||||
</p>
|
||||
<p>
|
||||
<a href="/forgot-password" id="forgot-password-link">Forgot your password?</a>
|
||||
</p>
|
||||
</div><!-- row -->
|
||||
</div><!-- container -->
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
<p id="blurb">Please provide the following information in order to sign up.</p>
|
||||
|
||||
<% if @errors %>
|
||||
<script>
|
||||
window.SI = window.SI || {}
|
||||
|
|
@ -9,49 +7,41 @@ window.SI.errors = <%== JSON.fast_generate(@errors) %>
|
|||
|
||||
<%== flash_message %>
|
||||
|
||||
<form id="sign-up-form" action="/sign-up" method="post">
|
||||
<table id="sign-up-table">
|
||||
<tr>
|
||||
<th><label for="first_name">First name</label></th>
|
||||
<td><input type="text" id="first_name" name="first_name" size="40" placeholder="Jane" value="<%= @fields['first_name'] %>"></td>
|
||||
</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 class="container">
|
||||
<div class="content">
|
||||
<div class="row">
|
||||
<div class="sign-up-form">
|
||||
<h2>Sign up</h2>
|
||||
|
||||
<div id="sign-in">
|
||||
<p>Already have an account?</p>
|
||||
<a href="/sign-in" id="sign-in-button"><button>Sign In</button></a>
|
||||
<img src="/images/spinner.gif" id="sign-in-spinner">
|
||||
</div>
|
||||
<form id="sign-up-form" action="/sign-up" method="post">
|
||||
<fieldset>
|
||||
<div class="clearfix">
|
||||
<input type="text" id="first_name" name="first_name" size="40" placeholder="First name" value="<%= @fields['first_name'] %>">
|
||||
</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="section">
|
||||
<h4>Legal Terms & Conditions</h4>
|
||||
|
||||
<p>
|
||||
Put yer legalese here.
|
||||
</p>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<h1><%= @page_title %></h1>
|
||||
<p>legalese goes here</p>
|
||||
</div>
|
||||
</div>
|
||||