diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 2b8bdfd..119ce83 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -1,9 +1,48 @@ class Application < Merb::Controller + def index + redirect '/' + end + def current_user - (@user ||= User.find(session[:user_id])) + if session[:user_id] + (@user ||= User.find(session[:user_id])) + else + (cookies[:session_id] ||= session[:session_id]) + end + end + + def logged_in? + !session[:user_id].nil? + end + + def administrator? + logged_in? and current_user and current_user.administrator? end def reset_session session[:user_id] = nil end + + def get_photo_version(width, height) + key = "photo_#{@photo.id}_#{width}_#{height}" + img = Cache.get(key) + + File.open("#{@photo.base_directory}/#{@photo.filename}", "r") do |f| + img = Magick::Image.from_blob(f.read).first.resize_to_fit(width, height) + Cache.put(key, img) + end if img.nil? + + img + end + + def fetch_allowed_user + @user = if administrator? + User.find_by_user_name params[:id] + elsif logged_in? and params[:id] != current_user.user_name + raise NotAllowed + else + current_user + end + raise NotFound if @user.nil? + end end diff --git a/app/controllers/exceptions.rb b/app/controllers/exceptions.rb index 8f462b1..5bae591 100644 --- a/app/controllers/exceptions.rb +++ b/app/controllers/exceptions.rb @@ -2,12 +2,22 @@ class Exceptions < Application # handle NotFound exceptions (404) def not_found - render :format => :html + do_your_render_thing end # handle NotAcceptable exceptions (406) def not_acceptable - render :format => :html + do_your_render_thing + end + + private + + def do_your_render_thing + if request.xhr? + render :format => :html, :layout => false + else + render :format => :html + end end end \ No newline at end of file diff --git a/app/controllers/favorites.rb b/app/controllers/favorites.rb index 5fa6366..628ac98 100644 --- a/app/controllers/favorites.rb +++ b/app/controllers/favorites.rb @@ -1,9 +1,32 @@ class Favorites < Application - - # ...and remember, everything returned from an action - # goes to the client... - def index + before :logged_in? + before :fetch_allowed_user, :only => [ :show ] + only_provides :xml + + def show + only_provides :html + @photos = @user.favorite_photos render end + def create + raise NotAllowed unless request.xhr? + @photo = Photo.find params[:id] + pf = PhotoFavorite.new :photo_id => @photo.id, :user_id => current_user.id + if pf.save + render '', :status => 200 + else + render '', :status => 403 + end + end + + def delete + raise NotAllowed unless request.xhr? + pf = PhotoFavorite.find params[:id], :include => :user + if pf.user == current_user and pf.destroy + render '', :status => 200 + else + render '', :status => 403 + end + end end diff --git a/app/controllers/home.rb b/app/controllers/home.rb index 25585ef..8eeec23 100644 --- a/app/controllers/home.rb +++ b/app/controllers/home.rb @@ -6,4 +6,8 @@ class Home < Application def acceptable_use render end + + def hall_of_fame + render + end end diff --git a/app/controllers/photos.rb b/app/controllers/photos.rb index f6997f4..2274de1 100644 --- a/app/controllers/photos.rb +++ b/app/controllers/photos.rb @@ -1,9 +1,68 @@ class Photos < Application - - # ...and remember, everything returned from an action - # goes to the client... + before :logged_in?, :only => [ :new, :create, :delete ] + before :administrator?, :only => [ :delete ] + before :make_photo, :only => [ :new, :create ] + before :fetch_photo, :only => [ :show, :delete, :thumbnail ] + def index + @page = params[:page].to_i + per_page = 24 + @page_count = (Photo.count(:id).to_f / per_page.to_f).ceil + @photos = Photo.find :all, :order => 'id DESC', :limit => per_page, :offset => (per_page * @page) + if request.xhr? + partial 'photos/photo_browser' + else + render + end + end + + def show render end + def new + render + end + + def create + if @photo.save + flash[:notice] = 'Great success' + redirect url(:photo, @photo) + else + render :new + end + end + + def delete + raise NotAllowed unless request.xhr? + if current_user and current_user.administrator? + render + else + redirect '/' + end + end + + def thumbnail + if @photo.exist? + w = params[:width].to_i + w = @photo.width if w == 0 or w > @photo.width + h = params[:height].to_i + h = @photo.height if h == 0 or h > @photo.height + w = h if h > w + send_data get_photo_version(w, w).to_blob, :filename => @photo.filename, :disposition => 'inline', :type => @photo.content_type + else + send_file Merb.root + '/public/images/image-missing.png', :disposition => 'inline' + end + end + + protected + + def make_photo + @photo = Photo.new params[:photo] + end + + def fetch_photo + @photo = Photo.find params[:id] + raise NotFound unless @photo + end end diff --git a/app/controllers/sessions.rb b/app/controllers/sessions.rb index ff8c78c..ec0fc1c 100644 --- a/app/controllers/sessions.rb +++ b/app/controllers/sessions.rb @@ -9,19 +9,31 @@ class Sessions < Application def create user = User.find_by_user_name params[:user_name] - if user.authenticated_against?(params[:password]) + if user and user.authenticated_against?(params[:password]) session[:user_id] = user.id - flash[:notice] = 'Great success!' - redirect '/' + if request.xhr? + render '', :status => 200 + else + flash[:notice] = 'Great success!' + redirect '/' + end else - flash[:error] = 'Login failed' - render :new + if request.xhr? + render '', :status => 401 + else + flash.now[:error] = 'Login failed' + render :new + end end end def delete reset_session - flash[:notice] = 'Goodbye!' - redirect '/' + if request.xhr? + render '', :status => 200 + else + flash[:notice] = 'Goodbye!' + redirect '/' + end end end diff --git a/app/controllers/stats.rb b/app/controllers/stats.rb deleted file mode 100644 index b94a7ae..0000000 --- a/app/controllers/stats.rb +++ /dev/null @@ -1,9 +0,0 @@ -class Stats < Application - - # ...and remember, everything returned from an action - # goes to the client... - def index - render - end - -end diff --git a/app/controllers/users.rb b/app/controllers/users.rb index b51a90b..447b5a9 100644 --- a/app/controllers/users.rb +++ b/app/controllers/users.rb @@ -1,4 +1,5 @@ class Users < Application + before :fetch_allowed_user, :only => [ :show, :edit, :update, :delete ] before :prepare_user, :only => [ :show, :edit, :update, :delete ] include Ambethia::ReCaptcha::Controller @@ -59,13 +60,6 @@ class Users < Application protected def prepare_user - @user = if current_user.administrator? - User.find_by_user_name params[:id] - else - current_user - end - raise NotFound if @user.nil? @user.attributes = params[:user] if params[:user] and request.post? - true end end diff --git a/app/controllers/votes.rb b/app/controllers/votes.rb index a8dce36..64bbb29 100644 --- a/app/controllers/votes.rb +++ b/app/controllers/votes.rb @@ -1,9 +1,48 @@ class Votes < Application - - # ...and remember, everything returned from an action - # goes to the client... - def index + before :fetch_allowed_user, :only => [ :show ] + + def show + @page = params[:page].to_i + per_page = 4 + @votes = @user.votes.find :all, :limit => 4, :offset => (@page * 4) + @page_count = (@user.votes.size.to_f / per_page.to_f).ceil + if request.xhr? + partial 'votes/stats_for_user' + else + render + end + end + + def new + get_mah_fohtoh render end + def create + raise NotAllowed unless request.xhr? + @photo = Photo.find params[:photo_id] rescue nil + @vote = Vote.new :photo_id => (@photo.id rescue true), :vote => (params[:one].to_s == 'true') + if logged_in? + @vote.user = current_user + else + @vote.session_id = current_user + end + if @vote.save + get_mah_fohtoh + partial 'votes/voting' + else + emsg = "The vote failed: " + @vote.errors.each_full { |e| emsg += e + ' ' } + render emsg, :status => 401 + end + end + + private + + def get_mah_fohtoh + # just a simple check to not allow you to vote on a photo twice. model + # business logic will fail this, too, but just to make sure you don't... + @photo = Photo.find params[:photo_id] if params[:photo_id] + @photo = Photo.next_available_votable_photo current_user if @photo.nil? or (logged_in? and current_user.voted_for?(@photo)) + end end diff --git a/app/helpers/global_helpers.rb b/app/helpers/global_helpers.rb index f7b1077..543f3c9 100644 --- a/app/helpers/global_helpers.rb +++ b/app/helpers/global_helpers.rb @@ -1,9 +1,5 @@ module Merb module GlobalHelpers - def logged_in? - !session[:user_id].nil? - end - def error_messages(obj) if obj.errors.empty? nil @@ -11,5 +7,41 @@ module Merb "
" end end + + def photo_url(photo, w = nil, h = nil) + w = photo.width if w.nil? or w > photo.width + h = photo.height if h.nil? or h > photo.height + url :controller => 'photos', :action => 'thumbnail', :width => w, :height => h, :id => photo.id + end + + def indicator + "" + end + + def menu_items + if @menu_items.nil? + @menu_items = [ + { :img => '/images/face-monkey.png', :name => 'Hall of fame', :title => 'B.A. Hall of famers -- The oneest of the ones!', :href => '/hall_of_fame' }, + { :img => '/images/vote.png', :name => 'Vote', :title => 'Vote on new photos', :href => url(:new_vote) }, + { :img => '/images/image-x-generic.png', :name => 'Photos', :title => 'Browse the oneness!', :href => url(:photos) } + ] + if logged_in? + @menu_items << { :img => '/images/utilities-system-monitor.png', :name => 'Stats', :title => 'Check your voting record against popular opinion', :href => url(:vote, :id => current_user.user_name) } + @menu_items << { :img => '/images/camera-photo.png', :name => 'Upload', :title => 'Upload photos', :href => url(:new_photo) } + @menu_items << { :img => '/images/emblem-favorite.png', :name => 'Favorites', :title => 'Check your favorites', :href => url(:favorite, :id => current_user.user_name) } + @menu_items << { :img => '/images/system-lock-screen.png', :name => 'Settings', :title => 'Change your password', :href => url(:edit_user, :id => current_user.user_name) } + else + @menu_items << { :img => '/images/system-users.png', :name => 'Sign up', :title => 'Sign up for an account', :href => url(:new_user) } + @menu_items << { :img => '/images/system-lock-screen.png', :name => 'Log in', :title => 'Log in with your account', :href => url(:new_session) } + end + end + @menu_items + end + + def pagination(block_name, base_url) + @pagination_block = block_name + @base_pagination_url = base_url + partial 'home/pagination_script' + end end end diff --git a/app/helpers/photos_helper.rb b/app/helpers/photos_helper.rb index 32ce477..2254d30 100644 --- a/app/helpers/photos_helper.rb +++ b/app/helpers/photos_helper.rb @@ -1,5 +1,14 @@ module Merb module PhotosHelper - + def vote_count(photo) + curl = Gchart.pie( + :size => '400x200', + :title => "Oneable Results", + :legend => [ 'Not One', 'One' ], + :data => [ photo.zero_votes, photo.one_votes ], + :theme => :pastel + ) + "Chart Results" + end end -end # Merb \ No newline at end of file +end # Merb diff --git a/app/helpers/stats_helper.rb b/app/helpers/stats_helper.rb deleted file mode 100644 index 62531c0..0000000 --- a/app/helpers/stats_helper.rb +++ /dev/null @@ -1,5 +0,0 @@ -module Merb - module StatsHelper - - end -end # Merb \ No newline at end of file diff --git a/app/helpers/votes_helper.rb b/app/helpers/votes_helper.rb index 9a60c95..f0f8b54 100644 --- a/app/helpers/votes_helper.rb +++ b/app/helpers/votes_helper.rb @@ -1,5 +1,14 @@ module Merb module VotesHelper - + def stat_chart + curl = Gchart.pie( + :size => '300x200', + :title => "Voting results for #{@user.votes.size} photos", + :legend => [ 'Not One', 'One' ], + :data => [ @user.votes.select { |v| v.zero? }.size, @user.votes.select { |v| v.one? }.size ], + :theme => :pastel + ) + "Chart Results" + end end end # Merb \ No newline at end of file diff --git a/app/models/photo.rb b/app/models/photo.rb index 5d97abd..76b9dff 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -1,8 +1,165 @@ class Photo < ActiveRecord::Base - #property :id, Integer, :serial => true - #property :filename, String - #property :email_hash, String - #property :created_at, DateTime - validates_presence_of :filename + attr_accessor :email + attr_accessor :file + attr_protected :email_hash + has_many :votes, :dependent => :destroy + + before_create :validate_image_sanity + before_create :hashify_email + + after_create :create_directories + before_destroy :destroy_directories + + ## + # Returns the biggest dimension of this model. + # + def max_dimension + if self.width > self.height + self.width + else + self.height + end + end + + ## + # Determines how 'oneable' this photo is. Should be cached in model later. + # + def oneness + os = one_votes + "%.1f%%" % (os.to_f / self.votes.size.to_f * 100.0) + end + + ## + # Abstraction to determine the number of positive votes on this photo. Should + # be replaced with a cached counter variable. + # + def one_votes + self.votes.count :id, :conditions => [ 'vote = ?', true ] + end + + ## + # Abstraction to determine the number of negative votes on this photo. Should + # be replaced with a cached counter variable. + # + def zero_votes + self.votes.size - self.one_votes + end + + ## + # Returns the path of the image relative to Merb's root. + # + def relative_directory + fkey = id.to_s[0,2] + skey = id.to_s[0,4] + "/photos/#{fkey}/#{skey}/#{id}" + end + + ## + # Determines the base directory for all files in this model. + # + def base_directory + "#{Merb.root}/public#{self.relative_directory}" + end + + ## + # Checks to see if the file is found on the filesystem. + # + def exist? + File.exist? "#{self.base_directory}/#{self.filename}" + end + + ## + # Returns the full path to the file suitable for an image source. + # + def pathname + "#{self.relative_directory}/#{self.filename}" + end + + def self.next_available_votable_photo(user) + pids = Vote.voted_photo_ids(user) + c = if pids.empty? + nil + else + "photos.id NOT IN (#{pids.join(',')})" + end + self.find :first, :conditions => c, :order => 'id ASC' + end + + protected + + ## + # Checks to make sure that the file exists and is an image. + # + def validate_image_sanity + if self.file.empty? or self.file[:tempfile].nil? + self.errors.add(:file, 'is not a file') + elsif self.file[:content_type] !~ /image\/\w+/ + self.errors.add(:file, 'is not a supported type') + elsif self.file[:size] and self.file[:size] > 3.megabytes + self.errors.add(:file, 'is too big (3MB max)') + end + self.content_type = self.file[:content_type] + + begin + @fstr = self.file[:tempfile].read + iary = Magick::Image.from_blob(@fstr) + self.filename = File.basename(self.file[:filename]).gsub(/[^\w._-]/, '') + if iary.inspect.to_s =~ /(\d+)x(\d+)/ + self.width = $1 + self.height = $2 + end + # resize to fit on the screen if it's too big... 600x600 + if self.width > 600 or self.height > 600 + iary.first.resize_to_fit!(600, 600) + if iary.inspect.to_s =~ /(\d+)x(\d+)\+\d+\+\d+/ + self.width = $1 + self.height = $2 + end + @fstr = iary.first.to_blob + end + rescue + Merb.logger.error("Caught an exception saving an image:") + Merb.logger.error("* #{$!}") + self.errors.add(:file, 'File could not be read as an image') + end if self.errors.empty? + + if self.errors.empty? + true + else + false + end + end + + ## + # Makes the directories and writes the file to disk. + # + def create_directories + File.umask(0022) + FileUtils.mkdir_p(base_directory) unless File.exist?(base_directory) + File.open("#{base_directory}/#{self.filename}", "w") do |f| + f.puts(@fstr) + end + File.chmod(0644, "#{base_directory}/#{self.filename}") + end + + ## + # Removes the directories and files associated with this model on destroy. + # + def destroy_directories + return unless File.exists?(base_directory) + Dir.foreach(base_directory) do |file| + next if file =~ /^\.\.?$/ + File.delete(base_directory + '/' + file) + end + Dir.delete(base_directory) + end + + ## + # Renders the email into a hashed string for later retrieval. + # + def hashify_email + email_hash = User.salted_string(email) unless email.to_s.empty? + true + end end diff --git a/app/models/photo_favorite.rb b/app/models/photo_favorite.rb new file mode 100644 index 0000000..759ace2 --- /dev/null +++ b/app/models/photo_favorite.rb @@ -0,0 +1,4 @@ +class PhotoFavorite < ActiveRecord::Base + belongs_to :user + belongs_to :photo +end diff --git a/app/models/user.rb b/app/models/user.rb index 3a22c93..6724b92 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,7 +9,10 @@ class User < ActiveRecord::Base validates_uniqueness_of :user_name validates_format_of :user_name, :with => /[\w_-]+/ - has_many :votes, :dependent => :destroy + has_many :votes, :dependent => :destroy, :order => 'votes.photo_id ASC' + has_many :photos, :dependent => :destroy, :through => :votes + has_many :photo_favorites, :dependent => :destroy + has_many :favorite_photos, :through => :photo_favorites, :class_name => 'Photo', :source => :photo before_validation :saltify_password @@ -26,6 +29,11 @@ class User < ActiveRecord::Base Digest::SHA1.hexdigest("#{Merb::Config[:session_secret_key]}--#{str}--") end + def voted_for?(photo) + pid = photo.respond_to?('id') ? photo.id : photo + self.votes.detect { |v| v.photo_id == pid } + end + protected def saltify_password diff --git a/app/models/vote.rb b/app/models/vote.rb index d2dab15..b682c52 100644 --- a/app/models/vote.rb +++ b/app/models/vote.rb @@ -2,7 +2,11 @@ class Vote < ActiveRecord::Base belongs_to :photo belongs_to :user - validates_presence_of :vote + validate :unique_for_user + validates_presence_of :photo_id + + attr_protected :user_id + attr_protected :session_id ## # Checks if this vote is anonymous, or not an authenticated User vote. @@ -31,4 +35,44 @@ class Vote < ActiveRecord::Base def one? self.to_i == 1 end + + ## + # Checks if a user has voted for a Photo. If you pass a User for user it will + # check for an authenticated user, else it will look for an anonymous vote. + # + def self.voted_for?(photo, user) + c = [ 'photo_id = :pid' ] + v = { :pid => (photo.respond_to?('id') ? photo.id : photo) } + if user.respond_to?('id') + c << 'user_id = :uid' + v[:uid] = user.id + else + c << 'session_id = :uid' + v[:uid] = user + end + self.find :first, :conditions => [ c.join(' AND '), v ] + end + + ## + # Does a quick find and collect on the cast votes so you can find a + def self.voted_photo_ids(user) + c = if user.respond_to?('id') + "votes.user_id = #{user.id}" + else + "votes.session_id = '#{user}'" + end + self.find(:all, :conditions => c, :select => 'photo_id').collect { |v| v.photo_id } + end + + protected + + def unique_for_user + if self.user.to_s.empty? and self.session_id.empty? + self.errors.add(:vote, 'must have an owner') + elsif self.user and Vote.voted_for?(self.photo, self.user) + self.errors.add(:vote, 'has already been collected for this photo') + elsif self.session_id and Vote.voted_for?(self.photo, self.session_id) + self.errors.add(:anonymous, 'vote has already been collected') + end + end end diff --git a/app/views/exceptions/not_found.html.erb b/app/views/exceptions/not_found.html.erb deleted file mode 100644 index 388c72c..0000000 --- a/app/views/exceptions/not_found.html.erb +++ /dev/null @@ -1,47 +0,0 @@ -
-
- - -

pocket rocket web framework

-
-
- -
-

Exception:

-

<%= params[:exception] %>

-
- -
-

Welcome to Merb!

-

Merb is a light-weight MVC framework written in Ruby. We hope you enjoy it.

- -

Where can I find help?

-

If you have any questions or if you can't figure something out, please take a - look at our project page, - feel free to come chat at irc.freenode.net, channel #merb, - or post to merb mailing list - on Google Groups.

- -

What if I've found a bug?

-

If you want to file a bug or make your own contribution to Merb, - feel free to register and create a ticket at our - project development page - on Lighthouse.

- -

How do I edit this page?

-

You're seeing this page because you need to edit the following files: -

-

-
- - -
diff --git a/app/views/exceptions/not_found.html.haml b/app/views/exceptions/not_found.html.haml new file mode 100644 index 0000000..83f1f2e --- /dev/null +++ b/app/views/exceptions/not_found.html.haml @@ -0,0 +1,5 @@ +%h1 + %img{ :src => '/images/image-missing.png' } + 404 :: Not Found + +%p Sorry, the resource you were looking for could not be found. Check your URL and try again. diff --git a/app/views/favorites/index.html.haml b/app/views/favorites/index.html.haml deleted file mode 100644 index 4596a79..0000000 --- a/app/views/favorites/index.html.haml +++ /dev/null @@ -1 +0,0 @@ -You're in index of the Favorites controller. \ No newline at end of file diff --git a/app/views/favorites/show.html.haml b/app/views/favorites/show.html.haml new file mode 100644 index 0000000..5c4045f --- /dev/null +++ b/app/views/favorites/show.html.haml @@ -0,0 +1,3 @@ +%h1 Your favorite photos + += partial 'photos/photo_browser' diff --git a/app/views/home/_pagination_navigation.html.haml b/app/views/home/_pagination_navigation.html.haml new file mode 100644 index 0000000..cc07521 --- /dev/null +++ b/app/views/home/_pagination_navigation.html.haml @@ -0,0 +1,11 @@ +#pagination_navigation + %p + %a{ :href => '#', :onclick => "scroll_box(0); return false;", :title => 'First page' } + %img{ :src => '/images/go-first.png' } + %a{ :href => '#', :onclick => "scroll_box(page - 1); return false;", :title => 'Previous page' } + %img{ :src => '/images/go-previous.png' } + == #{@page + 1} / #{@page_count} + %a{ :href => '#', :onclick => "scroll_box(page + 1); return false;", :title => 'Next page' } + %img{ :src => '/images/go-next.png' } + %a{ :href => '#', :onclick => "scroll_box(page_count - 1); return false;", :title => 'Next page' } + %img{ :src => '/images/go-last.png' } \ No newline at end of file diff --git a/app/views/home/_pagination_script.html.haml b/app/views/home/_pagination_script.html.haml new file mode 100644 index 0000000..4f2fcea --- /dev/null +++ b/app/views/home/_pagination_script.html.haml @@ -0,0 +1,20 @@ +:javascript + page = #{@page}; + page_count = #{@page_count}; + function scroll_box(newpage) + { + np = parseInt(newpage); + if(np < 0) + np = 0; + if(np >= page_count) + np = page_count - 1; + if(np == page) + return; + setTimeout("update_scroll_box(" + np + ");", 800); + new Effect.DropOut($('inner_#{@pagination_block}')); + } + function update_scroll_box(newpage) + { + page = newpage; + new Ajax.Updater('#{@pagination_block}', '#{@base_pagination_url}#{@base_pagination_url =~ /\?/ ? '&' : '?'}page=' + page, { method: 'get', onComplete: function(){ new Effect.Appear($('inner_#{@pagination_block}')); }, onSuccess: function(){ $('pagenum').innerHTML = parseInt(page) + 1; } }); + } diff --git a/app/views/home/hall_of_fame.html.haml b/app/views/home/hall_of_fame.html.haml new file mode 100644 index 0000000..8543ea4 --- /dev/null +++ b/app/views/home/hall_of_fame.html.haml @@ -0,0 +1,2 @@ +%h1 Hall of famers! + diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index d7d3daf..95bc54c 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -11,8 +11,21 @@ :border-left 1px solid #d4d4d4 em :margin-left 20px - h1 - :text-align center + ul + :width 400px + li + :padding 3px 10px 3px 10px + :border 1px solid #c17d11 + :background-color #e9b96e + :margin 10px + img + :vertical-align middle + a + :font-weight bold + :text-decoration none + :color #8f5902 + &:hover + :color #5e3a01 %span#front_page %h1 What this is all about @@ -29,33 +42,9 @@ %h1 Getting started - %ul.no_list_style - - if logged_in? + %ul.no_list_style.centered + - menu_items.each do |menu_item| %li - %a{ :href => url(:edit_user, :id => current_user.user_name) } - %img{ :src => '/images/system-lock-screen.png' } - Change your password - %li - %a{ :href => url(:favorite, :id => current_user.user_name) } - %img{ :src => '/images/emblem-favorite.png' } - Check your favorites - %li - %a{ :href => url(:new_photo) } - %img{ :src => '/images/camera-photo.png' } - Upload photos - %li - %a{ :href => url(:votes) } - %img{ :src => '/images/vote.png' } - Vote on new photos - %li - %a{ :href => url(:stat, :id => current_user.user_name) } - %img{ :src => '/images/utilities-system-monitor.png' } - Check stats on photos of yourself - - else - %li Sign up for an account - %li Log in if you have one - %li Check your favorites - %li Upload photos - %li Vote on new photos - %li Check stats on photos of yourself - + %a{ :href => menu_item[:href], :title => menu_item[:name] } + %img{ :src => menu_item[:img] } + = menu_item[:title] diff --git a/app/views/layout/application.html.haml b/app/views/layout/application.html.haml index ca68e1d..b4e6258 100644 --- a/app/views/layout/application.html.haml +++ b/app/views/layout/application.html.haml @@ -4,9 +4,7 @@ %title Binary Attraction %meta{ 'http-equiv' => "content-type", :content => "text/html; charset=utf8" } %link{ :href => "/stylesheets/ba.css", :rel => "stylesheet", :type => "text/css", :media => "screen", :charset => "utf-8" } - %script{ :src => "/javascripts/prototype.js", :type => "text/javascript" } - %script{ :src => "/javascripts/effects.js", :type => "text/javascript" } - %script{ :src => "/javascripts/dragdrop.js", :type => "text/javascript" } + %script{ :src => "/javascripts/prototype_and_effects_minimized.js", :type => "text/javascript" } %script{ :src => "/javascripts/application.js", :type => "text/javascript" } - unless flash.keys.empty? :javascript @@ -17,39 +15,20 @@ #flash_container - flash.keys.each do |key| %div{ :class => key }= flash[key] - #header - %span#header_image - %a{ :href => '/', :title => 'B.A. Home' } - %img{ :src => '/images/binaryattraction.png', :alt => 'Binary Attraction' } - #tool_bar - - if logged_in? - %a{ :href => url(:new_vote), :title => 'Vote' } - %img{ :src => '/images/vote.png' } - Vote + #header_image + %a{ :href => '/', :title => 'B.A. Home' } + %img{ :src => '/images/binaryattraction.png', :alt => 'Binary Attraction' } + #tool_bar + - menu_items.each do |menu_item| + %a{ :href => menu_item[:href], :title => menu_item[:title] } + %img{ :src => menu_item[:img] } + = menu_item[:name] + - if menu_item != menu_items.last | - %a{ :href => url(:new_photo), :title => 'Upload a photo' } - %img{ :src => '/images/camera-photo.png' } - Upload a photo - | - %a{ :href => url(:favorite, :id => session[:user_id]), :title => 'Favorites' } - %img{ :src => '/images/emblem-favorite.png' } - Favorites - | - %a{ :href => url(:stats), :title => 'Stats' } - %img{ :src => '/images/utilities-system-monitor.png' } - Check Stats - | - %a{ :href => url(:delete_session, :id => session[:user_id]), :title => 'Log out' } - %img{ :src => '/images/system-log-out.png' } - Log out - - else - %a{ :href => url(:new_user), :title => 'Sign Up' } - %img{ :src => '/images/system-users.png' } - Sign Up - | - %a{ :href => url(:new_session), :title => 'Log In' } - %img{ :src => '/images/system-lock-screen.png' } - Log In + - if logged_in? + | + %a{ :href => url(:delete_session, :id => session[:user_id]), :title => 'Log out of B.A.' } + %img{ :src => '/images/system-log-out.png' } #content = catch_content :for_layout #footer diff --git a/app/views/photos/_photo.html.haml b/app/views/photos/_photo.html.haml new file mode 100644 index 0000000..7eb4168 --- /dev/null +++ b/app/views/photos/_photo.html.haml @@ -0,0 +1,5 @@ +%div.centered{ :style => "width: #{@photo.width rescue 50}px" } + - if @photo and @photo.exist? + %img{ :src => @photo.pathname, :alt => @photo.filename, :width => @photo.width, :height => @photo.height } + - else + %img{ :src => '/images/image-missing.png', :alt => 'Missing File' } diff --git a/app/views/photos/_photo_browser.html.haml b/app/views/photos/_photo_browser.html.haml new file mode 100644 index 0000000..4bbbdbd --- /dev/null +++ b/app/views/photos/_photo_browser.html.haml @@ -0,0 +1,8 @@ +#inner_photo_browser{ :style => (request.xhr? ? 'display: none;' : '') } +- if @photos.nil? or @photos.empty? + %h3 None! +- else + - @photos.each_with_index do |photo, index| + %div.photo_browser_box + %a{ :href => url(:photo, photo) } + %img{ :src => photo_url(photo, 100, 100) } diff --git a/app/views/photos/index.html.haml b/app/views/photos/index.html.haml index 6c2a32a..561440d 100644 --- a/app/views/photos/index.html.haml +++ b/app/views/photos/index.html.haml @@ -1 +1,8 @@ -You're in index of the Photos controller. \ No newline at end of file +%h1 Photos of oneness + += pagination 'photo_browser', url(:photos) + +#browser_container.centered + = partial 'home/pagination_navigation' + #photo_browser + = partial 'photos/photo_browser' diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml new file mode 100644 index 0000000..f5af73a --- /dev/null +++ b/app/views/photos/new.html.haml @@ -0,0 +1,15 @@ += error_messages @photo + += form_for @photo do + %fieldset + %legend Upload A Photo + %p + %label{ :for => 'photo[email]' } Email + = text_field :name => 'photo[email]', :id => 'photo[email]' + %small Optional + %p + %label{ :for => 'photo[file]' } Image + = file_field :name => 'photo[file]', :id => 'photo[file]' + %small 3MB limit, JPG, PNG, GIF, etc. + = submit 'Save', :onclick => "$('indicator').show()" + = indicator diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml new file mode 100644 index 0000000..715cf3e --- /dev/null +++ b/app/views/photos/show.html.haml @@ -0,0 +1,63 @@ +#main_photo_container + = partial 'photos/photo' + + - v = Vote.voted_for? @photo, current_user + - if v + #mini_container.centered + %div.stat_box + %p + %strong You voted + %br + %tt= v.to_i + + %div.stat_box + %p + %strong Total 0 + %br + = @photo.zero_votes + + %div.stat_box + %p + %strong Total 1 + %br + %tt= @photo.one_votes + + %div.stat_box + %p + %strong Total Votes + %br + %tt= @photo.votes.size + + %div.stat_box + %p + %strong Oneness + %br + %tt= @photo.oneness + + %br{ :style => 'clear: both' } + + %div.centered{ :style => "width: 400px;"} + = vote_count @photo + + - else + %style{ :type => 'text/css' } + :sass + #outer_vote_container + :height 30px + + #outer_vote_container + %div{ :style => 'display: none', :id => 'to_be_voted' } + = partial 'votes/vote_controls' + #to_be_unvoted + %p + %a{ :href => '#', :onclick => "transition_out_controls(); return false;" } Vote on this photo now! + :javascript + function transition_out_controls() + { + new Effect.DropOut($('to_be_unvoted')); + setTimeout('transition_in_controls()', 1000); + } + function transition_in_controls() + { + new Effect.Appear($('to_be_voted')); + } diff --git a/app/views/sessions/new.html.haml b/app/views/sessions/new.html.haml index a1386d0..66d78c6 100644 --- a/app/views/sessions/new.html.haml +++ b/app/views/sessions/new.html.haml @@ -1,5 +1,23 @@ -= form :action => url(:sessions) do - %fieldset +:javascript + function login() + { + new Ajax.Request($('login_form').action, + { + parameters: Form.serialize($('login_form')), + onCreate: function(){ $('indicator').show(); }, + onComplete: function(){ $('indicator').hide(); }, + onSuccess: function(){ window.location.href = '/'; }, + onFailure: function(){ + new Effect.Shake($('fieldset')); + $('password').value = ''; + $('password').focus(); + } + }); + } + += form :action => url(:sessions), :id => 'login_form', :onsubmit => "login(); return false;" do + %fieldset#fieldset{ :style => "width: 250px;" } + %legend Papers, comrade? %p %label{ :for => 'user_name' } User Name @@ -9,4 +27,4 @@ Password = password_field :name => 'password', :id => 'password' = submit 'Login' - + = indicator diff --git a/app/views/stats/index.html.haml b/app/views/stats/index.html.haml deleted file mode 100644 index fc81d5a..0000000 --- a/app/views/stats/index.html.haml +++ /dev/null @@ -1 +0,0 @@ -You're in index of the Stats controller. \ No newline at end of file diff --git a/app/views/votes/_stats_for_user.html.haml b/app/views/votes/_stats_for_user.html.haml new file mode 100644 index 0000000..219edeb --- /dev/null +++ b/app/views/votes/_stats_for_user.html.haml @@ -0,0 +1,25 @@ +- dim = 100 +#inner_scrolling_photo_block{ :style => (request.xhr? ? 'display: none;' : '') } + %table{ :cellspacing => 0, :cellpadding => 0 } + %tr + %td{ :style => "width: 140px; height: 150px;" } + - if @votes[0] + %a{ :href => url(:photo, @votes[0].photo), :onclick => 'window.open(this.href);return false;' } + %img{ :src => photo_url(@votes[0].photo, dim, dim) } + %p== #{@votes[0].to_i} / #{@votes[0].photo.oneness} + %td{ :style => "width: 140px; height: 150px;" } + - if @votes[1] + %a{ :href => url(:photo, @votes[1].photo), :onclick => 'window.open(this.href);return false;' } + %img{ :src => photo_url(@votes[1].photo, dim, dim) } + %p== #{@votes[1].to_i} / #{@votes[1].photo.oneness} + %tr + %td{ :style => "width: 140px; height: 150px;" } + - if @votes[2] + %a{ :href => url(:photo, @votes[2].photo), :onclick => 'window.open(this.href);return false;' } + %img{ :src => photo_url(@votes[2].photo, dim, dim) } + %p== #{@votes[2].to_i} / #{@votes[2].photo.oneness} + %td{ :style => "width: 140px; height: 150px;" } + - if @votes[3] + %a{ :href => url(:photo, @votes[3].photo), :onclick => 'window.open(this.href);return false;' } + %img{ :src => photo_url(@votes[3].photo, dim, dim) } + %p== #{@votes[3].to_i} / #{@votes[3].photo.oneness} diff --git a/app/views/votes/_vote_controls.html.haml b/app/views/votes/_vote_controls.html.haml new file mode 100644 index 0000000..060289e --- /dev/null +++ b/app/views/votes/_vote_controls.html.haml @@ -0,0 +1,9 @@ +- unless @photo.nil? or !@photo.exist? + #vote_error.error.centered{ :style => "display: none" } + #vote_controls.centered + %a{ :href => '#', :onclick => "vote('#{url(:votes, :method => :post, :photo_id => @photo.id, :one => false)}'); return false;", :title => '0-able' } + %img{ :src => '/images/0.png' } + %a{ :href => '#', :onclick => "vote('#{url(:votes, :method => :post, :photo_id => @photo.id, :one => true)}'); return false;", :title => '1-able' } + %img{ :src => '/images/1.png' } +- else + %p Either you are the master of voting or there are no photos to be voted upon. \ No newline at end of file diff --git a/app/views/votes/_voting.html.haml b/app/views/votes/_voting.html.haml new file mode 100644 index 0000000..18bfc48 --- /dev/null +++ b/app/views/votes/_voting.html.haml @@ -0,0 +1,2 @@ += partial 'photos/photo' += partial 'votes/vote_controls' diff --git a/app/views/votes/new.html.haml b/app/views/votes/new.html.haml new file mode 100644 index 0000000..55bad76 --- /dev/null +++ b/app/views/votes/new.html.haml @@ -0,0 +1,2 @@ +#main_photo_container + = partial 'votes/voting' diff --git a/app/views/votes/show.html.haml b/app/views/votes/show.html.haml new file mode 100644 index 0000000..dbe4cfe --- /dev/null +++ b/app/views/votes/show.html.haml @@ -0,0 +1,22 @@ += pagination 'scrolling_photo_block', url(:vote, :id => @user.user_name) + +#scrolling_photo_block_container + = partial 'home/pagination_navigation' + #scrolling_photo_block + = partial 'votes/stats_for_user' + +- if @user == current_user + %h1 Your voting record +- else + %h1== #{@user.user_name}'s Voting record + +%div.user_stat_chart + = stat_chart + +%p== Zero: #{@user.votes.select { |v| v.zero? }.size} + +%p== One: #{@user.votes.select { |v| v.one? }.size} + +%p== Oneness: #{"%.1f%%" % (@user.votes.select { |v| v.one? }.size.to_f / @user.votes.size.to_f * 100.0)} + +%br{ :style => 'clear: both' } diff --git a/config/init.rb b/config/init.rb index 09a04e8..651cb20 100644 --- a/config/init.rb +++ b/config/init.rb @@ -1,11 +1,25 @@ Gem.clear_paths Gem.path.unshift(Merb.root / "gems") $LOAD_PATH.unshift(Merb.root / "lib") -# Merb.push_path(:lib, Merb.root / "lib") # uses **/*.rb as path glob -dependencies 'haml', 'sass', 'merb_helpers', 'merb_has_flash', 'digest/sha1', 'recaptcha' +dependencies 'haml', 'sass', 'merb_helpers', 'merb_has_flash', 'digest/sha1', 'merb-mailer', 'recaptcha' +require 'RMagick' +require 'memcache' +require 'memcache_util' +require 'gchart' Merb::BootLoader.after_app_loads do + config_path = File.join(Merb.root, 'config', 'memcache.yml') + if File.file?(config_path) and File.readable?(config_path) + memcache_connection_str = YAML.load(File.read(config_path)) + else + memcache_connection_str = 'localhost:11211' + end + CACHE = MemCache.new memcache_connection_str + + Merb::Mailer.config = { :sendmail_path => '/usr/sbin/sendmail' } + Merb::Mailer.delivery_method = :sendmail + recaptcha_path = File.join(Merb.root, 'config', 'recaptcha.yml') if File.file?(recaptcha_path) and File.readable?(recaptcha_path) rc = YAML::load_file(recaptcha_path) diff --git a/config/router.rb b/config/router.rb index 022dfca..537c4dd 100644 --- a/config/router.rb +++ b/config/router.rb @@ -1,13 +1,14 @@ Merb.logger.info("Compiling routes...") Merb::Router.prepare do |r| + r.match('/').to( :controller => 'home', :action => 'index' ) + r.match('/acceptable_use').to( :controller => 'home', :action => 'acceptable_use' ) + r.match('/hall_of_fame').to(:controller => 'home', :action => 'hall_of_fame') + r.resources :sessions r.resources :users r.resources :people r.resources :votes - r.resources :photos r.resources :favorites - r.resources :stats - # r.default_routes - r.match('/').to( :controller => 'home', :action => 'index' ) - r.match('/acceptable_use').to( :controller => 'home', :action => 'acceptable_use' ) + r.match('/photos/thumbnail/:id').to(:controller => 'photos', :action => 'thumbnail') + r.resources :photos end diff --git a/public/images/0.png b/public/images/0.png new file mode 100644 index 0000000..23c9e2c Binary files /dev/null and b/public/images/0.png differ diff --git a/public/images/1.png b/public/images/1.png new file mode 100644 index 0000000..7eb2ab3 Binary files /dev/null and b/public/images/1.png differ diff --git a/public/images/ajax-loader.gif b/public/images/ajax-loader.gif index 9d5d9ed..059f739 100644 Binary files a/public/images/ajax-loader.gif and b/public/images/ajax-loader.gif differ diff --git a/public/images/face-monkey.png b/public/images/face-monkey.png new file mode 100644 index 0000000..ced7b2e Binary files /dev/null and b/public/images/face-monkey.png differ diff --git a/public/images/go-first.png b/public/images/go-first.png new file mode 100644 index 0000000..bf0d8f2 Binary files /dev/null and b/public/images/go-first.png differ diff --git a/public/images/go-last.png b/public/images/go-last.png new file mode 100644 index 0000000..32df45d Binary files /dev/null and b/public/images/go-last.png differ diff --git a/public/images/go-next.png b/public/images/go-next.png new file mode 100644 index 0000000..6f3f65d Binary files /dev/null and b/public/images/go-next.png differ diff --git a/public/images/go-previous.png b/public/images/go-previous.png new file mode 100644 index 0000000..93be3d1 Binary files /dev/null and b/public/images/go-previous.png differ diff --git a/public/images/image-missing.png b/public/images/image-missing.png new file mode 100644 index 0000000..27fccd5 Binary files /dev/null and b/public/images/image-missing.png differ diff --git a/public/images/image-x-generic.png b/public/images/image-x-generic.png new file mode 100644 index 0000000..10f4671 Binary files /dev/null and b/public/images/image-x-generic.png differ diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 58df5d8..9bfc58a 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -26,3 +26,28 @@ function hide_flashes_timer() { new Effect.DropOut($('flash_container')); } + +function vote(url) +{ + new Ajax.Updater( + {success: 'main_photo_container', failure: 'vote_error'}, + url, + { + synchronous: true, + onCreate: function() + { + if($('vote_error').style.display != 'none') + new Effect.Fade($('vote_error')); + }, + onFailure: function() + { + new Effect.Appear($('vote_error')); + }, + onSuccess: function() + { + if($('vote_error').style.display != 'none') + new Effect.Fade($('vote_error')); + } + } + ); +} diff --git a/public/javascripts/dragdrop.js b/public/javascripts/dragdrop.js deleted file mode 100644 index bf429c2..0000000 --- a/public/javascripts/dragdrop.js +++ /dev/null @@ -1,974 +0,0 @@ -// script.aculo.us dragdrop.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008 - -// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) -// -// script.aculo.us is freely distributable under the terms of an MIT-style license. -// For details, see the script.aculo.us web site: http://script.aculo.us/ - -if(Object.isUndefined(Effect)) - throw("dragdrop.js requires including script.aculo.us' effects.js library"); - -var Droppables = { - drops: [], - - remove: function(element) { - this.drops = this.drops.reject(function(d) { return d.element==$(element) }); - }, - - add: function(element) { - element = $(element); - var options = Object.extend({ - greedy: true, - hoverclass: null, - tree: false - }, arguments[1] || { }); - - // cache containers - if(options.containment) { - options._containers = []; - var containment = options.containment; - if(Object.isArray(containment)) { - containment.each( function(c) { options._containers.push($(c)) }); - } else { - options._containers.push($(containment)); - } - } - - if(options.accept) options.accept = [options.accept].flatten(); - - Element.makePositioned(element); // fix IE - options.element = element; - - this.drops.push(options); - }, - - findDeepestChild: function(drops) { - deepest = drops[0]; - - for (i = 1; i < drops.length; ++i) - if (Element.isParent(drops[i].element, deepest.element)) - deepest = drops[i]; - - return deepest; - }, - - isContained: function(element, drop) { - var containmentNode; - if(drop.tree) { - containmentNode = element.treeNode; - } else { - containmentNode = element.parentNode; - } - return drop._containers.detect(function(c) { return containmentNode == c }); - }, - - isAffected: function(point, element, drop) { - return ( - (drop.element!=element) && - ((!drop._containers) || - this.isContained(element, drop)) && - ((!drop.accept) || - (Element.classNames(element).detect( - function(v) { return drop.accept.include(v) } ) )) && - Position.within(drop.element, point[0], point[1]) ); - }, - - deactivate: function(drop) { - if(drop.hoverclass) - Element.removeClassName(drop.element, drop.hoverclass); - this.last_active = null; - }, - - activate: function(drop) { - if(drop.hoverclass) - Element.addClassName(drop.element, drop.hoverclass); - this.last_active = drop; - }, - - show: function(point, element) { - if(!this.drops.length) return; - var drop, affected = []; - - this.drops.each( function(drop) { - if(Droppables.isAffected(point, element, drop)) - affected.push(drop); - }); - - if(affected.length>0) - drop = Droppables.findDeepestChild(affected); - - if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); - if (drop) { - Position.within(drop.element, point[0], point[1]); - if(drop.onHover) - drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); - - if (drop != this.last_active) Droppables.activate(drop); - } - }, - - fire: function(event, element) { - if(!this.last_active) return; - Position.prepare(); - - if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) - if (this.last_active.onDrop) { - this.last_active.onDrop(element, this.last_active.element, event); - return true; - } - }, - - reset: function() { - if(this.last_active) - this.deactivate(this.last_active); - } -} - -var Draggables = { - drags: [], - observers: [], - - register: function(draggable) { - if(this.drags.length == 0) { - this.eventMouseUp = this.endDrag.bindAsEventListener(this); - this.eventMouseMove = this.updateDrag.bindAsEventListener(this); - this.eventKeypress = this.keyPress.bindAsEventListener(this); - - Event.observe(document, "mouseup", this.eventMouseUp); - Event.observe(document, "mousemove", this.eventMouseMove); - Event.observe(document, "keypress", this.eventKeypress); - } - this.drags.push(draggable); - }, - - unregister: function(draggable) { - this.drags = this.drags.reject(function(d) { return d==draggable }); - if(this.drags.length == 0) { - Event.stopObserving(document, "mouseup", this.eventMouseUp); - Event.stopObserving(document, "mousemove", this.eventMouseMove); - Event.stopObserving(document, "keypress", this.eventKeypress); - } - }, - - activate: function(draggable) { - if(draggable.options.delay) { - this._timeout = setTimeout(function() { - Draggables._timeout = null; - window.focus(); - Draggables.activeDraggable = draggable; - }.bind(this), draggable.options.delay); - } else { - window.focus(); // allows keypress events if window isn't currently focused, fails for Safari - this.activeDraggable = draggable; - } - }, - - deactivate: function() { - this.activeDraggable = null; - }, - - updateDrag: function(event) { - if(!this.activeDraggable) return; - var pointer = [Event.pointerX(event), Event.pointerY(event)]; - // Mozilla-based browsers fire successive mousemove events with - // the same coordinates, prevent needless redrawing (moz bug?) - if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; - this._lastPointer = pointer; - - this.activeDraggable.updateDrag(event, pointer); - }, - - endDrag: function(event) { - if(this._timeout) { - clearTimeout(this._timeout); - this._timeout = null; - } - if(!this.activeDraggable) return; - this._lastPointer = null; - this.activeDraggable.endDrag(event); - this.activeDraggable = null; - }, - - keyPress: function(event) { - if(this.activeDraggable) - this.activeDraggable.keyPress(event); - }, - - addObserver: function(observer) { - this.observers.push(observer); - this._cacheObserverCallbacks(); - }, - - removeObserver: function(element) { // element instead of observer fixes mem leaks - this.observers = this.observers.reject( function(o) { return o.element==element }); - this._cacheObserverCallbacks(); - }, - - notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' - if(this[eventName+'Count'] > 0) - this.observers.each( function(o) { - if(o[eventName]) o[eventName](eventName, draggable, event); - }); - if(draggable.options[eventName]) draggable.options[eventName](draggable, event); - }, - - _cacheObserverCallbacks: function() { - ['onStart','onEnd','onDrag'].each( function(eventName) { - Draggables[eventName+'Count'] = Draggables.observers.select( - function(o) { return o[eventName]; } - ).length; - }); - } -} - -/*--------------------------------------------------------------------------*/ - -var Draggable = Class.create({ - initialize: function(element) { - var defaults = { - handle: false, - reverteffect: function(element, top_offset, left_offset) { - var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; - new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, - queue: {scope:'_draggable', position:'end'} - }); - }, - endeffect: function(element) { - var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; - new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, - queue: {scope:'_draggable', position:'end'}, - afterFinish: function(){ - Draggable._dragging[element] = false - } - }); - }, - zindex: 1000, - revert: false, - quiet: false, - scroll: false, - scrollSensitivity: 20, - scrollSpeed: 15, - snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } - delay: 0 - }; - - if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) - Object.extend(defaults, { - starteffect: function(element) { - element._opacity = Element.getOpacity(element); - Draggable._dragging[element] = true; - new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); - } - }); - - var options = Object.extend(defaults, arguments[1] || { }); - - this.element = $(element); - - if(options.handle && Object.isString(options.handle)) - this.handle = this.element.down('.'+options.handle, 0); - - if(!this.handle) this.handle = $(options.handle); - if(!this.handle) this.handle = this.element; - - if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { - options.scroll = $(options.scroll); - this._isScrollChild = Element.childOf(this.element, options.scroll); - } - - Element.makePositioned(this.element); // fix IE - - this.options = options; - this.dragging = false; - - this.eventMouseDown = this.initDrag.bindAsEventListener(this); - Event.observe(this.handle, "mousedown", this.eventMouseDown); - - Draggables.register(this); - }, - - destroy: function() { - Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); - Draggables.unregister(this); - }, - - currentDelta: function() { - return([ - parseInt(Element.getStyle(this.element,'left') || '0'), - parseInt(Element.getStyle(this.element,'top') || '0')]); - }, - - initDrag: function(event) { - if(!Object.isUndefined(Draggable._dragging[this.element]) && - Draggable._dragging[this.element]) return; - if(Event.isLeftClick(event)) { - // abort on form elements, fixes a Firefox issue - var src = Event.element(event); - if((tag_name = src.tagName.toUpperCase()) && ( - tag_name=='INPUT' || - tag_name=='SELECT' || - tag_name=='OPTION' || - tag_name=='BUTTON' || - tag_name=='TEXTAREA')) return; - - var pointer = [Event.pointerX(event), Event.pointerY(event)]; - var pos = Position.cumulativeOffset(this.element); - this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); - - Draggables.activate(this); - Event.stop(event); - } - }, - - startDrag: function(event) { - this.dragging = true; - if(!this.delta) - this.delta = this.currentDelta(); - - if(this.options.zindex) { - this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); - this.element.style.zIndex = this.options.zindex; - } - - if(this.options.ghosting) { - this._clone = this.element.cloneNode(true); - this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); - if (!this.element._originallyAbsolute) - Position.absolutize(this.element); - this.element.parentNode.insertBefore(this._clone, this.element); - } - - if(this.options.scroll) { - if (this.options.scroll == window) { - var where = this._getWindowScroll(this.options.scroll); - this.originalScrollLeft = where.left; - this.originalScrollTop = where.top; - } else { - this.originalScrollLeft = this.options.scroll.scrollLeft; - this.originalScrollTop = this.options.scroll.scrollTop; - } - } - - Draggables.notify('onStart', this, event); - - if(this.options.starteffect) this.options.starteffect(this.element); - }, - - updateDrag: function(event, pointer) { - if(!this.dragging) this.startDrag(event); - - if(!this.options.quiet){ - Position.prepare(); - Droppables.show(pointer, this.element); - } - - Draggables.notify('onDrag', this, event); - - this.draw(pointer); - if(this.options.change) this.options.change(this); - - if(this.options.scroll) { - this.stopScrolling(); - - var p; - if (this.options.scroll == window) { - with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } - } else { - p = Position.page(this.options.scroll); - p[0] += this.options.scroll.scrollLeft + Position.deltaX; - p[1] += this.options.scroll.scrollTop + Position.deltaY; - p.push(p[0]+this.options.scroll.offsetWidth); - p.push(p[1]+this.options.scroll.offsetHeight); - } - var speed = [0,0]; - if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); - if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); - if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); - if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); - this.startScrolling(speed); - } - - // fix AppleWebKit rendering - if(Prototype.Browser.WebKit) window.scrollBy(0,0); - - Event.stop(event); - }, - - finishDrag: function(event, success) { - this.dragging = false; - - if(this.options.quiet){ - Position.prepare(); - var pointer = [Event.pointerX(event), Event.pointerY(event)]; - Droppables.show(pointer, this.element); - } - - if(this.options.ghosting) { - if (!this.element._originallyAbsolute) - Position.relativize(this.element); - delete this.element._originallyAbsolute; - Element.remove(this._clone); - this._clone = null; - } - - var dropped = false; - if(success) { - dropped = Droppables.fire(event, this.element); - if (!dropped) dropped = false; - } - if(dropped && this.options.onDropped) this.options.onDropped(this.element); - Draggables.notify('onEnd', this, event); - - var revert = this.options.revert; - if(revert && Object.isFunction(revert)) revert = revert(this.element); - - var d = this.currentDelta(); - if(revert && this.options.reverteffect) { - if (dropped == 0 || revert != 'failure') - this.options.reverteffect(this.element, - d[1]-this.delta[1], d[0]-this.delta[0]); - } else { - this.delta = d; - } - - if(this.options.zindex) - this.element.style.zIndex = this.originalZ; - - if(this.options.endeffect) - this.options.endeffect(this.element); - - Draggables.deactivate(this); - Droppables.reset(); - }, - - keyPress: function(event) { - if(event.keyCode!=Event.KEY_ESC) return; - this.finishDrag(event, false); - Event.stop(event); - }, - - endDrag: function(event) { - if(!this.dragging) return; - this.stopScrolling(); - this.finishDrag(event, true); - Event.stop(event); - }, - - draw: function(point) { - var pos = Position.cumulativeOffset(this.element); - if(this.options.ghosting) { - var r = Position.realOffset(this.element); - pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; - } - - var d = this.currentDelta(); - pos[0] -= d[0]; pos[1] -= d[1]; - - if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { - pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; - pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; - } - - var p = [0,1].map(function(i){ - return (point[i]-pos[i]-this.offset[i]) - }.bind(this)); - - if(this.options.snap) { - if(Object.isFunction(this.options.snap)) { - p = this.options.snap(p[0],p[1],this); - } else { - if(Object.isArray(this.options.snap)) { - p = p.map( function(v, i) { - return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)) - } else { - p = p.map( function(v) { - return (v/this.options.snap).round()*this.options.snap }.bind(this)) - } - }} - - var style = this.element.style; - if((!this.options.constraint) || (this.options.constraint=='horizontal')) - style.left = p[0] + "px"; - if((!this.options.constraint) || (this.options.constraint=='vertical')) - style.top = p[1] + "px"; - - if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering - }, - - stopScrolling: function() { - if(this.scrollInterval) { - clearInterval(this.scrollInterval); - this.scrollInterval = null; - Draggables._lastScrollPointer = null; - } - }, - - startScrolling: function(speed) { - if(!(speed[0] || speed[1])) return; - this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; - this.lastScrolled = new Date(); - this.scrollInterval = setInterval(this.scroll.bind(this), 10); - }, - - scroll: function() { - var current = new Date(); - var delta = current - this.lastScrolled; - this.lastScrolled = current; - if(this.options.scroll == window) { - with (this._getWindowScroll(this.options.scroll)) { - if (this.scrollSpeed[0] || this.scrollSpeed[1]) { - var d = delta / 1000; - this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); - } - } - } else { - this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; - this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; - } - - Position.prepare(); - Droppables.show(Draggables._lastPointer, this.element); - Draggables.notify('onDrag', this); - if (this._isScrollChild) { - Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); - Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; - Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; - if (Draggables._lastScrollPointer[0] < 0) - Draggables._lastScrollPointer[0] = 0; - if (Draggables._lastScrollPointer[1] < 0) - Draggables._lastScrollPointer[1] = 0; - this.draw(Draggables._lastScrollPointer); - } - - if(this.options.change) this.options.change(this); - }, - - _getWindowScroll: function(w) { - var T, L, W, H; - with (w.document) { - if (w.document.documentElement && documentElement.scrollTop) { - T = documentElement.scrollTop; - L = documentElement.scrollLeft; - } else if (w.document.body) { - T = body.scrollTop; - L = body.scrollLeft; - } - if (w.innerWidth) { - W = w.innerWidth; - H = w.innerHeight; - } else if (w.document.documentElement && documentElement.clientWidth) { - W = documentElement.clientWidth; - H = documentElement.clientHeight; - } else { - W = body.offsetWidth; - H = body.offsetHeight - } - } - return { top: T, left: L, width: W, height: H }; - } -}); - -Draggable._dragging = { }; - -/*--------------------------------------------------------------------------*/ - -var SortableObserver = Class.create({ - initialize: function(element, observer) { - this.element = $(element); - this.observer = observer; - this.lastValue = Sortable.serialize(this.element); - }, - - onStart: function() { - this.lastValue = Sortable.serialize(this.element); - }, - - onEnd: function() { - Sortable.unmark(); - if(this.lastValue != Sortable.serialize(this.element)) - this.observer(this.element) - } -}); - -var Sortable = { - SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, - - sortables: { }, - - _findRootElement: function(element) { - while (element.tagName.toUpperCase() != "BODY") { - if(element.id && Sortable.sortables[element.id]) return element; - element = element.parentNode; - } - }, - - options: function(element) { - element = Sortable._findRootElement($(element)); - if(!element) return; - return Sortable.sortables[element.id]; - }, - - destroy: function(element){ - var s = Sortable.options(element); - - if(s) { - Draggables.removeObserver(s.element); - s.droppables.each(function(d){ Droppables.remove(d) }); - s.draggables.invoke('destroy'); - - delete Sortable.sortables[s.element.id]; - } - }, - - create: function(element) { - element = $(element); - var options = Object.extend({ - element: element, - tag: 'li', // assumes li children, override with tag: 'tagname' - dropOnEmpty: false, - tree: false, - treeTag: 'ul', - overlap: 'vertical', // one of 'vertical', 'horizontal' - constraint: 'vertical', // one of 'vertical', 'horizontal', false - containment: element, // also takes array of elements (or id's); or false - handle: false, // or a CSS class - only: false, - delay: 0, - hoverclass: null, - ghosting: false, - quiet: false, - scroll: false, - scrollSensitivity: 20, - scrollSpeed: 15, - format: this.SERIALIZE_RULE, - - // these take arrays of elements or ids and can be - // used for better initialization performance - elements: false, - handles: false, - - onChange: Prototype.emptyFunction, - onUpdate: Prototype.emptyFunction - }, arguments[1] || { }); - - // clear any old sortable with same element - this.destroy(element); - - // build options for the draggables - var options_for_draggable = { - revert: true, - quiet: options.quiet, - scroll: options.scroll, - scrollSpeed: options.scrollSpeed, - scrollSensitivity: options.scrollSensitivity, - delay: options.delay, - ghosting: options.ghosting, - constraint: options.constraint, - handle: options.handle }; - - if(options.starteffect) - options_for_draggable.starteffect = options.starteffect; - - if(options.reverteffect) - options_for_draggable.reverteffect = options.reverteffect; - else - if(options.ghosting) options_for_draggable.reverteffect = function(element) { - element.style.top = 0; - element.style.left = 0; - }; - - if(options.endeffect) - options_for_draggable.endeffect = options.endeffect; - - if(options.zindex) - options_for_draggable.zindex = options.zindex; - - // build options for the droppables - var options_for_droppable = { - overlap: options.overlap, - containment: options.containment, - tree: options.tree, - hoverclass: options.hoverclass, - onHover: Sortable.onHover - } - - var options_for_tree = { - onHover: Sortable.onEmptyHover, - overlap: options.overlap, - containment: options.containment, - hoverclass: options.hoverclass - } - - // fix for gecko engine - Element.cleanWhitespace(element); - - options.draggables = []; - options.droppables = []; - - // drop on empty handling - if(options.dropOnEmpty || options.tree) { - Droppables.add(element, options_for_tree); - options.droppables.push(element); - } - - (options.elements || this.findElements(element, options) || []).each( function(e,i) { - var handle = options.handles ? $(options.handles[i]) : - (options.handle ? $(e).select('.' + options.handle)[0] : e); - options.draggables.push( - new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); - Droppables.add(e, options_for_droppable); - if(options.tree) e.treeNode = element; - options.droppables.push(e); - }); - - if(options.tree) { - (Sortable.findTreeElements(element, options) || []).each( function(e) { - Droppables.add(e, options_for_tree); - e.treeNode = element; - options.droppables.push(e); - }); - } - - // keep reference - this.sortables[element.id] = options; - - // for onupdate - Draggables.addObserver(new SortableObserver(element, options.onUpdate)); - - }, - - // return all suitable-for-sortable elements in a guaranteed order - findElements: function(element, options) { - return Element.findChildren( - element, options.only, options.tree ? true : false, options.tag); - }, - - findTreeElements: function(element, options) { - return Element.findChildren( - element, options.only, options.tree ? true : false, options.treeTag); - }, - - onHover: function(element, dropon, overlap) { - if(Element.isParent(dropon, element)) return; - - if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { - return; - } else if(overlap>0.5) { - Sortable.mark(dropon, 'before'); - if(dropon.previousSibling != element) { - var oldParentNode = element.parentNode; - element.style.visibility = "hidden"; // fix gecko rendering - dropon.parentNode.insertBefore(element, dropon); - if(dropon.parentNode!=oldParentNode) - Sortable.options(oldParentNode).onChange(element); - Sortable.options(dropon.parentNode).onChange(element); - } - } else { - Sortable.mark(dropon, 'after'); - var nextElement = dropon.nextSibling || null; - if(nextElement != element) { - var oldParentNode = element.parentNode; - element.style.visibility = "hidden"; // fix gecko rendering - dropon.parentNode.insertBefore(element, nextElement); - if(dropon.parentNode!=oldParentNode) - Sortable.options(oldParentNode).onChange(element); - Sortable.options(dropon.parentNode).onChange(element); - } - } - }, - - onEmptyHover: function(element, dropon, overlap) { - var oldParentNode = element.parentNode; - var droponOptions = Sortable.options(dropon); - - if(!Element.isParent(dropon, element)) { - var index; - - var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); - var child = null; - - if(children) { - var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); - - for (index = 0; index < children.length; index += 1) { - if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { - offset -= Element.offsetSize (children[index], droponOptions.overlap); - } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { - child = index + 1 < children.length ? children[index + 1] : null; - break; - } else { - child = children[index]; - break; - } - } - } - - dropon.insertBefore(element, child); - - Sortable.options(oldParentNode).onChange(element); - droponOptions.onChange(element); - } - }, - - unmark: function() { - if(Sortable._marker) Sortable._marker.hide(); - }, - - mark: function(dropon, position) { - // mark on ghosting only - var sortable = Sortable.options(dropon.parentNode); - if(sortable && !sortable.ghosting) return; - - if(!Sortable._marker) { - Sortable._marker = - ($('dropmarker') || Element.extend(document.createElement('DIV'))). - hide().addClassName('dropmarker').setStyle({position:'absolute'}); - document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); - } - var offsets = Position.cumulativeOffset(dropon); - Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); - - if(position=='after') - if(sortable.overlap == 'horizontal') - Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); - else - Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); - - Sortable._marker.show(); - }, - - _tree: function(element, options, parent) { - var children = Sortable.findElements(element, options) || []; - - for (var i = 0; i < children.length; ++i) { - var match = children[i].id.match(options.format); - - if (!match) continue; - - var child = { - id: encodeURIComponent(match ? match[1] : null), - element: element, - parent: parent, - children: [], - position: parent.children.length, - container: $(children[i]).down(options.treeTag) - } - - /* Get the element containing the children and recurse over it */ - if (child.container) - this._tree(child.container, options, child) - - parent.children.push (child); - } - - return parent; - }, - - tree: function(element) { - element = $(element); - var sortableOptions = this.options(element); - var options = Object.extend({ - tag: sortableOptions.tag, - treeTag: sortableOptions.treeTag, - only: sortableOptions.only, - name: element.id, - format: sortableOptions.format - }, arguments[1] || { }); - - var root = { - id: null, - parent: null, - children: [], - container: element, - position: 0 - } - - return Sortable._tree(element, options, root); - }, - - /* Construct a [i] index for a particular node */ - _constructIndex: function(node) { - var index = ''; - do { - if (node.id) index = '[' + node.position + ']' + index; - } while ((node = node.parent) != null); - return index; - }, - - sequence: function(element) { - element = $(element); - var options = Object.extend(this.options(element), arguments[1] || { }); - - return $(this.findElements(element, options) || []).map( function(item) { - return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; - }); - }, - - setSequence: function(element, new_sequence) { - element = $(element); - var options = Object.extend(this.options(element), arguments[2] || { }); - - var nodeMap = { }; - this.findElements(element, options).each( function(n) { - if (n.id.match(options.format)) - nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; - n.parentNode.removeChild(n); - }); - - new_sequence.each(function(ident) { - var n = nodeMap[ident]; - if (n) { - n[1].appendChild(n[0]); - delete nodeMap[ident]; - } - }); - }, - - serialize: function(element) { - element = $(element); - var options = Object.extend(Sortable.options(element), arguments[1] || { }); - var name = encodeURIComponent( - (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); - - if (options.tree) { - return Sortable.tree(element, arguments[1]).children.map( function (item) { - return [name + Sortable._constructIndex(item) + "[id]=" + - encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); - }).flatten().join('&'); - } else { - return Sortable.sequence(element, arguments[1]).map( function(item) { - return name + "[]=" + encodeURIComponent(item); - }).join('&'); - } - } -} - -// Returns true if child is contained within element -Element.isParent = function(child, element) { - if (!child.parentNode || child == element) return false; - if (child.parentNode == element) return true; - return Element.isParent(child.parentNode, element); -} - -Element.findChildren = function(element, only, recursive, tagName) { - if(!element.hasChildNodes()) return null; - tagName = tagName.toUpperCase(); - if(only) only = [only].flatten(); - var elements = []; - $A(element.childNodes).each( function(e) { - if(e.tagName && e.tagName.toUpperCase()==tagName && - (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) - elements.push(e); - if(recursive) { - var grandchildren = Element.findChildren(e, only, recursive, tagName); - if(grandchildren) elements.push(grandchildren); - } - }); - - return (elements.length>0 ? elements.flatten() : []); -} - -Element.offsetSize = function (element, type) { - return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; -} diff --git a/public/javascripts/prototype_and_effects_minimized.js b/public/javascripts/prototype_and_effects_minimized.js new file mode 100644 index 0000000..5df9220 --- /dev/null +++ b/public/javascripts/prototype_and_effects_minimized.js @@ -0,0 +1,318 @@ + +var Prototype={Version:'1.6.0.2',Browser:{IE:!!(window.attachEvent&&!window.opera),Opera:!!window.opera,WebKit:navigator.userAgent.indexOf('AppleWebKit/')>-1,Gecko:navigator.userAgent.indexOf('Gecko')>-1&&navigator.userAgent.indexOf('KHTML')==-1,MobileSafari:!!navigator.userAgent.match(/Apple.*Mobile.*Safari/)},BrowserFeatures:{XPath:!!document.evaluate,ElementExtensions:!!window.HTMLElement,SpecificElementExtensions:document.createElement('div').__proto__&&document.createElement('div').__proto__!==document.createElement('form').__proto__},ScriptFragment:']*>([\\S\\s]*?)<\/script>',JSONFilter:/^\/\*-secure-([\s\S]*)\*\/\s*$/,emptyFunction:function(){},K:function(x){return x}};if(Prototype.Browser.MobileSafari) +Prototype.BrowserFeatures.SpecificElementExtensions=false;var Class={create:function(){var parent=null,properties=$A(arguments);if(Object.isFunction(properties[0])) +parent=properties.shift();function klass(){this.initialize.apply(this,arguments);} +Object.extend(klass,Class.Methods);klass.superclass=parent;klass.subclasses=[];if(parent){var subclass=function(){};subclass.prototype=parent.prototype;klass.prototype=new subclass;parent.subclasses.push(klass);} +for(var i=0;i0){if(match=source.match(pattern)){result+=source.slice(0,match.index);result+=String.interpret(replacement(match));source=source.slice(match.index+match[0].length);}else{result+=source,source='';}} +return result;},sub:function(pattern,replacement,count){replacement=this.gsub.prepareReplacement(replacement);count=Object.isUndefined(count)?1:count;return this.gsub(pattern,function(match){if(--count<0)return match[0];return replacement(match);});},scan:function(pattern,iterator){this.gsub(pattern,iterator);return String(this);},truncate:function(length,truncation){length=length||30;truncation=Object.isUndefined(truncation)?'...':truncation;return this.length>length?this.slice(0,length-truncation.length)+truncation:String(this);},strip:function(){return this.replace(/^\s+/,'').replace(/\s+$/,'');},stripTags:function(){return this.replace(/<\/?[^>]+>/gi,'');},stripScripts:function(){return this.replace(new RegExp(Prototype.ScriptFragment,'img'),'');},extractScripts:function(){var matchAll=new RegExp(Prototype.ScriptFragment,'img');var matchOne=new RegExp(Prototype.ScriptFragment,'im');return(this.match(matchAll)||[]).map(function(scriptTag){return(scriptTag.match(matchOne)||['',''])[1];});},evalScripts:function(){return this.extractScripts().map(function(script){return eval(script)});},escapeHTML:function(){var self=arguments.callee;self.text.data=this;return self.div.innerHTML;},unescapeHTML:function(){var div=new Element('div');div.innerHTML=this.stripTags();return div.childNodes[0]?(div.childNodes.length>1?$A(div.childNodes).inject('',function(memo,node){return memo+node.nodeValue}):div.childNodes[0].nodeValue):'';},toQueryParams:function(separator){var match=this.strip().match(/([^?#]*)(#.*)?$/);if(!match)return{};return match[1].split(separator||'&').inject({},function(hash,pair){if((pair=pair.split('='))[0]){var key=decodeURIComponent(pair.shift());var value=pair.length>1?pair.join('='):pair[0];if(value!=undefined)value=decodeURIComponent(value);if(key in hash){if(!Object.isArray(hash[key]))hash[key]=[hash[key]];hash[key].push(value);} +else hash[key]=value;} +return hash;});},toArray:function(){return this.split('');},succ:function(){return this.slice(0,this.length-1)+ +String.fromCharCode(this.charCodeAt(this.length-1)+1);},times:function(count){return count<1?'':new Array(count+1).join(this);},camelize:function(){var parts=this.split('-'),len=parts.length;if(len==1)return parts[0];var camelized=this.charAt(0)=='-'?parts[0].charAt(0).toUpperCase()+parts[0].substring(1):parts[0];for(var i=1;i-1;},startsWith:function(pattern){return this.indexOf(pattern)===0;},endsWith:function(pattern){var d=this.length-pattern.length;return d>=0&&this.lastIndexOf(pattern)===d;},empty:function(){return this=='';},blank:function(){return/^\s*$/.test(this);},interpolate:function(object,pattern){return new Template(this,pattern).evaluate(object);}});if(Prototype.Browser.WebKit||Prototype.Browser.IE)Object.extend(String.prototype,{escapeHTML:function(){return this.replace(/&/g,'&').replace(//g,'>');},unescapeHTML:function(){return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}});String.prototype.gsub.prepareReplacement=function(replacement){if(Object.isFunction(replacement))return replacement;var template=new Template(replacement);return function(match){return template.evaluate(match)};};String.prototype.parseQuery=String.prototype.toQueryParams;Object.extend(String.prototype.escapeHTML,{div:document.createElement('div'),text:document.createTextNode('')});with(String.prototype.escapeHTML)div.appendChild(text);var Template=Class.create({initialize:function(template,pattern){this.template=template.toString();this.pattern=pattern||Template.Pattern;},evaluate:function(object){if(Object.isFunction(object.toTemplateReplacements)) +object=object.toTemplateReplacements();return this.template.gsub(this.pattern,function(match){if(object==null)return'';var before=match[1]||'';if(before=='\\')return match[2];var ctx=object,expr=match[3];var pattern=/^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;match=pattern.exec(expr);if(match==null)return before;while(match!=null){var comp=match[1].startsWith('[')?match[2].gsub('\\\\]',']'):match[1];ctx=ctx[comp];if(null==ctx||''==match[3])break;expr=expr.substring('['==match[3]?match[1].length:match[0].length);match=pattern.exec(expr);} +return before+String.interpret(ctx);});}});Template.Pattern=/(^|.|\r|\n)(#\{(.*?)\})/;var $break={};var Enumerable={each:function(iterator,context){var index=0;iterator=iterator.bind(context);try{this._each(function(value){iterator(value,index++);});}catch(e){if(e!=$break)throw e;} +return this;},eachSlice:function(number,iterator,context){iterator=iterator?iterator.bind(context):Prototype.K;var index=-number,slices=[],array=this.toArray();while((index+=number)=result) +result=value;});return result;},min:function(iterator,context){iterator=iterator?iterator.bind(context):Prototype.K;var result;this.each(function(value,index){value=iterator(value,index);if(result==null||valueb?1:0;}).pluck('value');},toArray:function(){return this.map();},zip:function(){var iterator=Prototype.K,args=$A(arguments);if(Object.isFunction(args.last())) +iterator=args.pop();var collections=[this].concat(args).map($A);return this.map(function(value,index){return iterator(collections.pluck(index));});},size:function(){return this.toArray().length;},inspect:function(){return'#';}};Object.extend(Enumerable,{map:Enumerable.collect,find:Enumerable.detect,select:Enumerable.findAll,filter:Enumerable.findAll,member:Enumerable.include,entries:Enumerable.toArray,every:Enumerable.all,some:Enumerable.any});function $A(iterable){if(!iterable)return[];if(iterable.toArray)return iterable.toArray();var length=iterable.length||0,results=new Array(length);while(length--)results[length]=iterable[length];return results;} +if(Prototype.Browser.WebKit){$A=function(iterable){if(!iterable)return[];if(!(Object.isFunction(iterable)&&iterable=='[object NodeList]')&&iterable.toArray)return iterable.toArray();var length=iterable.length||0,results=new Array(length);while(length--)results[length]=iterable[length];return results;};} +Array.from=$A;Object.extend(Array.prototype,Enumerable);if(!Array.prototype._reverse)Array.prototype._reverse=Array.prototype.reverse;Object.extend(Array.prototype,{_each:function(iterator){for(var i=0,length=this.length;i1?this:this[0];},uniq:function(sorted){return this.inject([],function(array,value,index){if(0==index||(sorted?array.last()!=value:!array.include(value))) +array.push(value);return array;});},intersect:function(array){return this.uniq().findAll(function(item){return array.detect(function(value){return item===value});});},clone:function(){return[].concat(this);},size:function(){return this.length;},inspect:function(){return'['+this.map(Object.inspect).join(', ')+']';},toJSON:function(){var results=[];this.each(function(object){var value=Object.toJSON(object);if(!Object.isUndefined(value))results.push(value);});return'['+results.join(', ')+']';}});if(Object.isFunction(Array.prototype.forEach)) +Array.prototype._each=Array.prototype.forEach;if(!Array.prototype.indexOf)Array.prototype.indexOf=function(item,i){i||(i=0);var length=this.length;if(i<0)i=length+i;for(;i';},toJSON:function(){return Object.toJSON(this.toObject());},clone:function(){return new Hash(this);}}})());Hash.prototype.toTemplateReplacements=Hash.prototype.toObject;Hash.from=$H;var ObjectRange=Class.create(Enumerable,{initialize:function(start,end,exclusive){this.start=start;this.end=end;this.exclusive=exclusive;},_each:function(iterator){var value=this.start;while(this.include(value)){iterator(value);value=value.succ();}},include:function(value){if(value1&&!((readyState==4)&&this._complete)) +this.respondToReadyState(this.transport.readyState);},setRequestHeaders:function(){var headers={'X-Requested-With':'XMLHttpRequest','X-Prototype-Version':Prototype.Version,'Accept':'text/javascript, text/html, application/xml, text/xml, */*'};if(this.method=='post'){headers['Content-type']=this.options.contentType+ +(this.options.encoding?'; charset='+this.options.encoding:'');if(this.transport.overrideMimeType&&(navigator.userAgent.match(/Gecko\/(\d{4})/)||[0,2005])[1]<2005) +headers['Connection']='close';} +if(typeof this.options.requestHeaders=='object'){var extras=this.options.requestHeaders;if(Object.isFunction(extras.push)) +for(var i=0,length=extras.length;i=200&&status<300);},getStatus:function(){try{return this.transport.status||0;}catch(e){return 0}},respondToReadyState:function(readyState){var state=Ajax.Request.Events[readyState],response=new Ajax.Response(this);if(state=='Complete'){try{this._complete=true;(this.options['on'+response.status]||this.options['on'+(this.success()?'Success':'Failure')]||Prototype.emptyFunction)(response,response.headerJSON);}catch(e){this.dispatchException(e);} +var contentType=response.getHeader('Content-type');if(this.options.evalJS=='force'||(this.options.evalJS&&this.isSameOrigin()&&contentType&&contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) +this.evalResponse();} +try{(this.options['on'+state]||Prototype.emptyFunction)(response,response.headerJSON);Ajax.Responders.dispatch('on'+state,this,response,response.headerJSON);}catch(e){this.dispatchException(e);} +if(state=='Complete'){this.transport.onreadystatechange=Prototype.emptyFunction;}},isSameOrigin:function(){var m=this.url.match(/^\s*https?:\/\/[^\/]*/);return!m||(m[0]=='#{protocol}//#{domain}#{port}'.interpolate({protocol:location.protocol,domain:document.domain,port:location.port?':'+location.port:''}));},getHeader:function(name){try{return this.transport.getResponseHeader(name)||null;}catch(e){return null}},evalResponse:function(){try{return eval((this.transport.responseText||'').unfilterJSON());}catch(e){this.dispatchException(e);}},dispatchException:function(exception){(this.options.onException||Prototype.emptyFunction)(this,exception);Ajax.Responders.dispatch('onException',this,exception);}});Ajax.Request.Events=['Uninitialized','Loading','Loaded','Interactive','Complete'];Ajax.Response=Class.create({initialize:function(request){this.request=request;var transport=this.transport=request.transport,readyState=this.readyState=transport.readyState;if((readyState>2&&!Prototype.Browser.IE)||readyState==4){this.status=this.getStatus();this.statusText=this.getStatusText();this.responseText=String.interpret(transport.responseText);this.headerJSON=this._getHeaderJSON();} +if(readyState==4){var xml=transport.responseXML;this.responseXML=Object.isUndefined(xml)?null:xml;this.responseJSON=this._getResponseJSON();}},status:0,statusText:'',getStatus:Ajax.Request.prototype.getStatus,getStatusText:function(){try{return this.transport.statusText||'';}catch(e){return''}},getHeader:Ajax.Request.prototype.getHeader,getAllHeaders:function(){try{return this.getAllResponseHeaders();}catch(e){return null}},getResponseHeader:function(name){return this.transport.getResponseHeader(name);},getAllResponseHeaders:function(){return this.transport.getAllResponseHeaders();},_getHeaderJSON:function(){var json=this.getHeader('X-JSON');if(!json)return null;json=decodeURIComponent(escape(json));try{return json.evalJSON(this.request.options.sanitizeJSON||!this.request.isSameOrigin());}catch(e){this.request.dispatchException(e);}},_getResponseJSON:function(){var options=this.request.options;if(!options.evalJSON||(options.evalJSON!='force'&&!(this.getHeader('Content-type')||'').include('application/json'))||this.responseText.blank()) +return null;try{return this.responseText.evalJSON(options.sanitizeJSON||!this.request.isSameOrigin());}catch(e){this.request.dispatchException(e);}}});Ajax.Updater=Class.create(Ajax.Request,{initialize:function($super,container,url,options){this.container={success:(container.success||container),failure:(container.failure||(container.success?null:container))};options=Object.clone(options);var onComplete=options.onComplete;options.onComplete=(function(response,json){this.updateContent(response.responseText);if(Object.isFunction(onComplete))onComplete(response,json);}).bind(this);$super(url,options);},updateContent:function(responseText){var receiver=this.container[this.success()?'success':'failure'],options=this.options;if(!options.evalScripts)responseText=responseText.stripScripts();if(receiver=$(receiver)){if(options.insertion){if(Object.isString(options.insertion)){var insertion={};insertion[options.insertion]=responseText;receiver.insert(insertion);} +else options.insertion(receiver,responseText);} +else receiver.update(responseText);}}});Ajax.PeriodicalUpdater=Class.create(Ajax.Base,{initialize:function($super,container,url,options){$super(options);this.onComplete=this.options.onComplete;this.frequency=(this.options.frequency||2);this.decay=(this.options.decay||1);this.updater={};this.container=container;this.url=url;this.start();},start:function(){this.options.onComplete=this.updateComplete.bind(this);this.onTimerEvent();},stop:function(){this.updater.options.onComplete=undefined;clearTimeout(this.timer);(this.onComplete||Prototype.emptyFunction).apply(this,arguments);},updateComplete:function(response){if(this.options.decay){this.decay=(response.responseText==this.lastText?this.decay*this.options.decay:1);this.lastText=response.responseText;} +this.timer=this.onTimerEvent.bind(this).delay(this.decay*this.frequency);},onTimerEvent:function(){this.updater=new Ajax.Updater(this.container,this.url,this.options);}});function $(element){if(arguments.length>1){for(var i=0,elements=[],length=arguments.length;i';delete attributes.name;return Element.writeAttribute(document.createElement(tagName),attributes);} +if(!cache[tagName])cache[tagName]=Element.extend(document.createElement(tagName));return Element.writeAttribute(cache[tagName].cloneNode(false),attributes);};Object.extend(this.Element,element||{});}).call(window);Element.cache={};Element.Methods={visible:function(element){return $(element).style.display!='none';},toggle:function(element){element=$(element);Element[Element.visible(element)?'hide':'show'](element);return element;},hide:function(element){$(element).style.display='none';return element;},show:function(element){$(element).style.display='';return element;},remove:function(element){element=$(element);element.parentNode.removeChild(element);return element;},update:function(element,content){element=$(element);if(content&&content.toElement)content=content.toElement();if(Object.isElement(content))return element.update().insert(content);content=Object.toHTML(content);element.innerHTML=content.stripScripts();content.evalScripts.bind(content).defer();return element;},replace:function(element,content){element=$(element);if(content&&content.toElement)content=content.toElement();else if(!Object.isElement(content)){content=Object.toHTML(content);var range=element.ownerDocument.createRange();range.selectNode(element);content.evalScripts.bind(content).defer();content=range.createContextualFragment(content.stripScripts());} +element.parentNode.replaceChild(content,element);return element;},insert:function(element,insertions){element=$(element);if(Object.isString(insertions)||Object.isNumber(insertions)||Object.isElement(insertions)||(insertions&&(insertions.toElement||insertions.toHTML))) +insertions={bottom:insertions};var content,insert,tagName,childNodes;for(var position in insertions){content=insertions[position];position=position.toLowerCase();insert=Element._insertionTranslations[position];if(content&&content.toElement)content=content.toElement();if(Object.isElement(content)){insert(element,content);continue;} +content=Object.toHTML(content);tagName=((position=='before'||position=='after')?element.parentNode:element).tagName.toUpperCase();childNodes=Element._getContentFromAnonymousElement(tagName,content.stripScripts());if(position=='top'||position=='after')childNodes.reverse();childNodes.each(insert.curry(element));content.evalScripts.bind(content).defer();} +return element;},wrap:function(element,wrapper,attributes){element=$(element);if(Object.isElement(wrapper)) +$(wrapper).writeAttribute(attributes||{});else if(Object.isString(wrapper))wrapper=new Element(wrapper,attributes);else wrapper=new Element('div',wrapper);if(element.parentNode) +element.parentNode.replaceChild(wrapper,element);wrapper.appendChild(element);return wrapper;},inspect:function(element){element=$(element);var result='<'+element.tagName.toLowerCase();$H({'id':'id','className':'class'}).each(function(pair){var property=pair.first(),attribute=pair.last();var value=(element[property]||'').toString();if(value)result+=' '+attribute+'='+value.inspect(true);});return result+'>';},recursivelyCollect:function(element,property){element=$(element);var elements=[];while(element=element[property]) +if(element.nodeType==1) +elements.push(Element.extend(element));return elements;},ancestors:function(element){return $(element).recursivelyCollect('parentNode');},descendants:function(element){return $(element).select("*");},firstDescendant:function(element){element=$(element).firstChild;while(element&&element.nodeType!=1)element=element.nextSibling;return $(element);},immediateDescendants:function(element){if(!(element=$(element).firstChild))return[];while(element&&element.nodeType!=1)element=element.nextSibling;if(element)return[element].concat($(element).nextSiblings());return[];},previousSiblings:function(element){return $(element).recursivelyCollect('previousSibling');},nextSiblings:function(element){return $(element).recursivelyCollect('nextSibling');},siblings:function(element){element=$(element);return element.previousSiblings().reverse().concat(element.nextSiblings());},match:function(element,selector){if(Object.isString(selector)) +selector=new Selector(selector);return selector.match($(element));},up:function(element,expression,index){element=$(element);if(arguments.length==1)return $(element.parentNode);var ancestors=element.ancestors();return Object.isNumber(expression)?ancestors[expression]:Selector.findElement(ancestors,expression,index);},down:function(element,expression,index){element=$(element);if(arguments.length==1)return element.firstDescendant();return Object.isNumber(expression)?element.descendants()[expression]:element.select(expression)[index||0];},previous:function(element,expression,index){element=$(element);if(arguments.length==1)return $(Selector.handlers.previousElementSibling(element));var previousSiblings=element.previousSiblings();return Object.isNumber(expression)?previousSiblings[expression]:Selector.findElement(previousSiblings,expression,index);},next:function(element,expression,index){element=$(element);if(arguments.length==1)return $(Selector.handlers.nextElementSibling(element));var nextSiblings=element.nextSiblings();return Object.isNumber(expression)?nextSiblings[expression]:Selector.findElement(nextSiblings,expression,index);},select:function(){var args=$A(arguments),element=$(args.shift());return Selector.findChildElements(element,args);},adjacent:function(){var args=$A(arguments),element=$(args.shift());return Selector.findChildElements(element.parentNode,args).without(element);},identify:function(element){element=$(element);var id=element.readAttribute('id'),self=arguments.callee;if(id)return id;do{id='anonymous_element_'+self.counter++}while($(id));element.writeAttribute('id',id);return id;},readAttribute:function(element,name){element=$(element);if(Prototype.Browser.IE){var t=Element._attributeTranslations.read;if(t.values[name])return t.values[name](element,name);if(t.names[name])name=t.names[name];if(name.include(':')){return(!element.attributes||!element.attributes[name])?null:element.attributes[name].value;}} +return element.getAttribute(name);},writeAttribute:function(element,name,value){element=$(element);var attributes={},t=Element._attributeTranslations.write;if(typeof name=='object')attributes=name;else attributes[name]=Object.isUndefined(value)?true:value;for(var attr in attributes){name=t.names[attr]||attr;value=attributes[attr];if(t.values[attr])name=t.values[attr](element,value);if(value===false||value===null) +element.removeAttribute(name);else if(value===true) +element.setAttribute(name,name);else element.setAttribute(name,value);} +return element;},getHeight:function(element){return $(element).getDimensions().height;},getWidth:function(element){return $(element).getDimensions().width;},classNames:function(element){return new Element.ClassNames(element);},hasClassName:function(element,className){if(!(element=$(element)))return;var elementClassName=element.className;return(elementClassName.length>0&&(elementClassName==className||new RegExp("(^|\\s)"+className+"(\\s|$)").test(elementClassName)));},addClassName:function(element,className){if(!(element=$(element)))return;if(!element.hasClassName(className)) +element.className+=(element.className?' ':'')+className;return element;},removeClassName:function(element,className){if(!(element=$(element)))return;element.className=element.className.replace(new RegExp("(^|\\s+)"+className+"(\\s+|$)"),' ').strip();return element;},toggleClassName:function(element,className){if(!(element=$(element)))return;return element[element.hasClassName(className)?'removeClassName':'addClassName'](className);},cleanWhitespace:function(element){element=$(element);var node=element.firstChild;while(node){var nextNode=node.nextSibling;if(node.nodeType==3&&!/\S/.test(node.nodeValue)) +element.removeChild(node);node=nextNode;} +return element;},empty:function(element){return $(element).innerHTML.blank();},descendantOf:function(element,ancestor){element=$(element),ancestor=$(ancestor);var originalAncestor=ancestor;if(element.compareDocumentPosition) +return(element.compareDocumentPosition(ancestor)&8)===8;if(element.sourceIndex&&!Prototype.Browser.Opera){var e=element.sourceIndex,a=ancestor.sourceIndex,nextAncestor=ancestor.nextSibling;if(!nextAncestor){do{ancestor=ancestor.parentNode;} +while(!(nextAncestor=ancestor.nextSibling)&&ancestor.parentNode);} +if(nextAncestor&&nextAncestor.sourceIndex) +return(e>a&&e','',1],TBODY:['','
',2],TR:['','
',3],TD:['
','
',4],SELECT:['',1]}};(function(){Object.extend(this.tags,{THEAD:this.tags.TBODY,TFOOT:this.tags.TBODY,TH:this.tags.TD});}).call(Element._insertionTranslations);Element.Methods.Simulated={hasAttribute:function(element,attribute){attribute=Element._attributeTranslations.has[attribute]||attribute;var node=$(element).getAttributeNode(attribute);return node&&node.specified;}};Element.Methods.ByTag={};Object.extend(Element,Element.Methods);if(!Prototype.BrowserFeatures.ElementExtensions&&document.createElement('div').__proto__){window.HTMLElement={};window.HTMLElement.prototype=document.createElement('div').__proto__;Prototype.BrowserFeatures.ElementExtensions=true;} +Element.extend=(function(){if(Prototype.BrowserFeatures.SpecificElementExtensions) +return Prototype.K;var Methods={},ByTag=Element.Methods.ByTag;var extend=Object.extend(function(element){if(!element||element._extendedByPrototype||element.nodeType!=1||element==window)return element;var methods=Object.clone(Methods),tagName=element.tagName,property,value;if(ByTag[tagName])Object.extend(methods,ByTag[tagName]);for(property in methods){value=methods[property];if(Object.isFunction(value)&&!(property in element)) +element[property]=value.methodize();} +element._extendedByPrototype=Prototype.emptyFunction;return element;},{refresh:function(){if(!Prototype.BrowserFeatures.ElementExtensions){Object.extend(Methods,Element.Methods);Object.extend(Methods,Element.Methods.Simulated);}}});extend.refresh();return extend;})();Element.hasAttribute=function(element,attribute){if(element.hasAttribute)return element.hasAttribute(attribute);return Element.Methods.Simulated.hasAttribute(element,attribute);};Element.addMethods=function(methods){var F=Prototype.BrowserFeatures,T=Element.Methods.ByTag;if(!methods){Object.extend(Form,Form.Methods);Object.extend(Form.Element,Form.Element.Methods);Object.extend(Element.Methods.ByTag,{"FORM":Object.clone(Form.Methods),"INPUT":Object.clone(Form.Element.Methods),"SELECT":Object.clone(Form.Element.Methods),"TEXTAREA":Object.clone(Form.Element.Methods)});} +if(arguments.length==2){var tagName=methods;methods=arguments[1];} +if(!tagName)Object.extend(Element.Methods,methods||{});else{if(Object.isArray(tagName))tagName.each(extend);else extend(tagName);} +function extend(tagName){tagName=tagName.toUpperCase();if(!Element.Methods.ByTag[tagName]) +Element.Methods.ByTag[tagName]={};Object.extend(Element.Methods.ByTag[tagName],methods);} +function copy(methods,destination,onlyIfAbsent){onlyIfAbsent=onlyIfAbsent||false;for(var property in methods){var value=methods[property];if(!Object.isFunction(value))continue;if(!onlyIfAbsent||!(property in destination)) +destination[property]=value.methodize();}} +function findDOMClass(tagName){var klass;var trans={"OPTGROUP":"OptGroup","TEXTAREA":"TextArea","P":"Paragraph","FIELDSET":"FieldSet","UL":"UList","OL":"OList","DL":"DList","DIR":"Directory","H1":"Heading","H2":"Heading","H3":"Heading","H4":"Heading","H5":"Heading","H6":"Heading","Q":"Quote","INS":"Mod","DEL":"Mod","A":"Anchor","IMG":"Image","CAPTION":"TableCaption","COL":"TableCol","COLGROUP":"TableCol","THEAD":"TableSection","TFOOT":"TableSection","TBODY":"TableSection","TR":"TableRow","TH":"TableCell","TD":"TableCell","FRAMESET":"FrameSet","IFRAME":"IFrame"};if(trans[tagName])klass='HTML'+trans[tagName]+'Element';if(window[klass])return window[klass];klass='HTML'+tagName+'Element';if(window[klass])return window[klass];klass='HTML'+tagName.capitalize()+'Element';if(window[klass])return window[klass];window[klass]={};window[klass].prototype=document.createElement(tagName).__proto__;return window[klass];} +if(F.ElementExtensions){copy(Element.Methods,HTMLElement.prototype);copy(Element.Methods.Simulated,HTMLElement.prototype,true);} +if(F.SpecificElementExtensions){for(var tag in Element.Methods.ByTag){var klass=findDOMClass(tag);if(Object.isUndefined(klass))continue;copy(T[tag],klass.prototype);}} +Object.extend(Element,Element.Methods);delete Element.ByTag;if(Element.extend.refresh)Element.extend.refresh();Element.cache={};};document.viewport={getDimensions:function(){var dimensions={};var B=Prototype.Browser;$w('width height').each(function(d){var D=d.capitalize();dimensions[d]=(B.WebKit&&!document.evaluate)?self['inner'+D]:(B.Opera)?document.body['client'+D]:document.documentElement['client'+D];});return dimensions;},getWidth:function(){return this.getDimensions().width;},getHeight:function(){return this.getDimensions().height;},getScrollOffsets:function(){return Element._returnOffset(window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft,window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop);}};var Selector=Class.create({initialize:function(expression){this.expression=expression.strip();this.compileMatcher();},shouldUseXPath:function(){if(!Prototype.BrowserFeatures.XPath)return false;var e=this.expression;if(Prototype.Browser.WebKit&&(e.include("-of-type")||e.include(":empty"))) +return false;if((/(\[[\w-]*?:|:checked)/).test(this.expression)) +return false;return true;},compileMatcher:function(){if(this.shouldUseXPath()) +return this.compileXPathMatcher();var e=this.expression,ps=Selector.patterns,h=Selector.handlers,c=Selector.criteria,le,p,m;if(Selector._cache[e]){this.matcher=Selector._cache[e];return;} +this.matcher=["this.matcher = function(root) {","var r = root, h = Selector.handlers, c = false, n;"];while(e&&le!=e&&(/\S/).test(e)){le=e;for(var i in ps){p=ps[i];if(m=e.match(p)){this.matcher.push(Object.isFunction(c[i])?c[i](m):new Template(c[i]).evaluate(m));e=e.replace(m[0],'');break;}}} +this.matcher.push("return h.unique(n);\n}");eval(this.matcher.join('\n'));Selector._cache[this.expression]=this.matcher;},compileXPathMatcher:function(){var e=this.expression,ps=Selector.patterns,x=Selector.xpath,le,m;if(Selector._cache[e]){this.xpath=Selector._cache[e];return;} +this.matcher=['.//*'];while(e&&le!=e&&(/\S/).test(e)){le=e;for(var i in ps){if(m=e.match(ps[i])){this.matcher.push(Object.isFunction(x[i])?x[i](m):new Template(x[i]).evaluate(m));e=e.replace(m[0],'');break;}}} +this.xpath=this.matcher.join('');Selector._cache[this.expression]=this.xpath;},findElements:function(root){root=root||document;if(this.xpath)return document._getElementsByXPath(this.xpath,root);return this.matcher(root);},match:function(element){this.tokens=[];var e=this.expression,ps=Selector.patterns,as=Selector.assertions;var le,p,m;while(e&&le!==e&&(/\S/).test(e)){le=e;for(var i in ps){p=ps[i];if(m=e.match(p)){if(as[i]){this.tokens.push([i,Object.clone(m)]);e=e.replace(m[0],'');}else{return this.findElements(document).include(element);}}}} +var match=true,name,matches;for(var i=0,token;token=this.tokens[i];i++){name=token[0],matches=token[1];if(!Selector.assertions[name](element,matches)){match=false;break;}} +return match;},toString:function(){return this.expression;},inspect:function(){return"#";}});Object.extend(Selector,{_cache:{},xpath:{descendant:"//*",child:"/*",adjacent:"/following-sibling::*[1]",laterSibling:'/following-sibling::*',tagName:function(m){if(m[1]=='*')return'';return"[local-name()='"+m[1].toLowerCase()+"' or local-name()='"+m[1].toUpperCase()+"']";},className:"[contains(concat(' ', @class, ' '), ' #{1} ')]",id:"[@id='#{1}']",attrPresence:function(m){m[1]=m[1].toLowerCase();return new Template("[@#{1}]").evaluate(m);},attr:function(m){m[1]=m[1].toLowerCase();m[3]=m[5]||m[6];return new Template(Selector.xpath.operators[m[2]]).evaluate(m);},pseudo:function(m){var h=Selector.xpath.pseudos[m[1]];if(!h)return'';if(Object.isFunction(h))return h(m);return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);},operators:{'=':"[@#{1}='#{3}']",'!=':"[@#{1}!='#{3}']",'^=':"[starts-with(@#{1}, '#{3}')]",'$=':"[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",'*=':"[contains(@#{1}, '#{3}')]",'~=':"[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",'|=':"[contains(concat('-', @#{1}, '-'), '-#{3}-')]"},pseudos:{'first-child':'[not(preceding-sibling::*)]','last-child':'[not(following-sibling::*)]','only-child':'[not(preceding-sibling::* or following-sibling::*)]','empty':"[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",'checked':"[@checked]",'disabled':"[@disabled]",'enabled':"[not(@disabled)]",'not':function(m){var e=m[6],p=Selector.patterns,x=Selector.xpath,le,v;var exclusion=[];while(e&&le!=e&&(/\S/).test(e)){le=e;for(var i in p){if(m=e.match(p[i])){v=Object.isFunction(x[i])?x[i](m):new Template(x[i]).evaluate(m);exclusion.push("("+v.substring(1,v.length-1)+")");e=e.replace(m[0],'');break;}}} +return"[not("+exclusion.join(" and ")+")]";},'nth-child':function(m){return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ",m);},'nth-last-child':function(m){return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ",m);},'nth-of-type':function(m){return Selector.xpath.pseudos.nth("position() ",m);},'nth-last-of-type':function(m){return Selector.xpath.pseudos.nth("(last() + 1 - position()) ",m);},'first-of-type':function(m){m[6]="1";return Selector.xpath.pseudos['nth-of-type'](m);},'last-of-type':function(m){m[6]="1";return Selector.xpath.pseudos['nth-last-of-type'](m);},'only-of-type':function(m){var p=Selector.xpath.pseudos;return p['first-of-type'](m)+p['last-of-type'](m);},nth:function(fragment,m){var mm,formula=m[6],predicate;if(formula=='even')formula='2n+0';if(formula=='odd')formula='2n+1';if(mm=formula.match(/^(\d+)$/)) +return'['+fragment+"= "+mm[1]+']';if(mm=formula.match(/^(-?\d*)?n(([+-])(\d+))?/)){if(mm[1]=="-")mm[1]=-1;var a=mm[1]?Number(mm[1]):1;var b=mm[2]?Number(mm[2]):0;predicate="[((#{fragment} - #{b}) mod #{a} = 0) and "+"((#{fragment} - #{b}) div #{a} >= 0)]";return new Template(predicate).evaluate({fragment:fragment,a:a,b:b});}}}},criteria:{tagName:'n = h.tagName(n, r, "#{1}", c); c = false;',className:'n = h.className(n, r, "#{1}", c); c = false;',id:'n = h.id(n, r, "#{1}", c); c = false;',attrPresence:'n = h.attrPresence(n, r, "#{1}", c); c = false;',attr:function(m){m[3]=(m[5]||m[6]);return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);},pseudo:function(m){if(m[6])m[6]=m[6].replace(/"/g,'\\"');return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);},descendant:'c = "descendant";',child:'c = "child";',adjacent:'c = "adjacent";',laterSibling:'c = "laterSibling";'},patterns:{laterSibling:/^\s*~\s*/,child:/^\s*>\s*/,adjacent:/^\s*\+\s*/,descendant:/^\s/,tagName:/^\s*(\*|[\w\-]+)(\b|$)?/,id:/^#([\w\-\*]+)(\b|$)/,className:/^\.([\w\-\*]+)(\b|$)/,pseudo:/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,attrPresence:/^\[([\w]+)\]/,attr:/\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/},assertions:{tagName:function(element,matches){return matches[1].toUpperCase()==element.tagName.toUpperCase();},className:function(element,matches){return Element.hasClassName(element,matches[1]);},id:function(element,matches){return element.id===matches[1];},attrPresence:function(element,matches){return Element.hasAttribute(element,matches[1]);},attr:function(element,matches){var nodeValue=Element.readAttribute(element,matches[1]);return nodeValue&&Selector.operators[matches[2]](nodeValue,matches[5]||matches[6]);}},handlers:{concat:function(a,b){for(var i=0,node;node=b[i];i++) +a.push(node);return a;},mark:function(nodes){var _true=Prototype.emptyFunction;for(var i=0,node;node=nodes[i];i++) +node._countedByPrototype=_true;return nodes;},unmark:function(nodes){for(var i=0,node;node=nodes[i];i++) +node._countedByPrototype=undefined;return nodes;},index:function(parentNode,reverse,ofType){parentNode._countedByPrototype=Prototype.emptyFunction;if(reverse){for(var nodes=parentNode.childNodes,i=nodes.length-1,j=1;i>=0;i--){var node=nodes[i];if(node.nodeType==1&&(!ofType||node._countedByPrototype))node.nodeIndex=j++;}}else{for(var i=0,j=1,nodes=parentNode.childNodes;node=nodes[i];i++) +if(node.nodeType==1&&(!ofType||node._countedByPrototype))node.nodeIndex=j++;}},unique:function(nodes){if(nodes.length==0)return nodes;var results=[],n;for(var i=0,l=nodes.length;i0?[b]:[];return $R(1,total).inject([],function(memo,i){if(0==(i-b)%a&&(i-b)/a>=0)memo.push(i);return memo;});},nth:function(nodes,formula,root,reverse,ofType){if(nodes.length==0)return[];if(formula=='even')formula='2n+0';if(formula=='odd')formula='2n+1';var h=Selector.handlers,results=[],indexed=[],m;h.mark(nodes);for(var i=0,node;node=nodes[i];i++){if(!node.parentNode._countedByPrototype){h.index(node.parentNode,reverse,ofType);indexed.push(node.parentNode);}} +if(formula.match(/^\d+$/)){formula=Number(formula);for(var i=0,node;node=nodes[i];i++) +if(node.nodeIndex==formula)results.push(node);}else if(m=formula.match(/^(-?\d*)?n(([+-])(\d+))?/)){if(m[1]=="-")m[1]=-1;var a=m[1]?Number(m[1]):1;var b=m[2]?Number(m[2]):0;var indices=Selector.pseudos.getIndices(a,b,nodes.length);for(var i=0,node,l=indices.length;node=nodes[i];i++){for(var j=0;j+()\s-]+|\*|\[.*?\])+)\s*(,|$)/,function(m){expressions.push(m[1].strip());});return expressions;},matchElements:function(elements,expression){var matches=$$(expression),h=Selector.handlers;h.mark(matches);for(var i=0,results=[],element;element=elements[i];i++) +if(element._countedByPrototype)results.push(element);h.unmark(matches);return results;},findElement:function(elements,expression,index){if(Object.isNumber(expression)){index=expression;expression=false;} +return Selector.matchElements(elements,expression||'*')[index||0];},findChildElements:function(element,expressions){expressions=Selector.split(expressions.join(','));var results=[],h=Selector.handlers;for(var i=0,l=expressions.length,selector;i1)?h.unique(results):results;}});if(Prototype.Browser.IE){Object.extend(Selector.handlers,{concat:function(a,b){for(var i=0,node;node=b[i];i++) +if(node.tagName!=="!")a.push(node);return a;},unmark:function(nodes){for(var i=0,node;node=nodes[i];i++) +node.removeAttribute('_countedByPrototype');return nodes;}});} +function $$(){return Selector.findChildElements(document,$A(arguments));} +var Form={reset:function(form){$(form).reset();return form;},serializeElements:function(elements,options){if(typeof options!='object')options={hash:!!options};else if(Object.isUndefined(options.hash))options.hash=true;var key,value,submitted=false,submit=options.submit;var data=elements.inject({},function(result,element){if(!element.disabled&&element.name){key=element.name;value=$(element).getValue();if(value!=null&&(element.type!='submit'||(!submitted&&submit!==false&&(!submit||key==submit)&&(submitted=true)))){if(key in result){if(!Object.isArray(result[key]))result[key]=[result[key]];result[key].push(value);} +else result[key]=value;}} +return result;});return options.hash?data:Object.toQueryString(data);}};Form.Methods={serialize:function(form,options){return Form.serializeElements(Form.getElements(form),options);},getElements:function(form){return $A($(form).getElementsByTagName('*')).inject([],function(elements,child){if(Form.Element.Serializers[child.tagName.toLowerCase()]) +elements.push(Element.extend(child));return elements;});},getInputs:function(form,typeName,name){form=$(form);var inputs=form.getElementsByTagName('input');if(!typeName&&!name)return $A(inputs).map(Element.extend);for(var i=0,matchingInputs=[],length=inputs.length;i=0;}).sortBy(function(element){return element.tabIndex}).first();return firstByIndex?firstByIndex:elements.find(function(element){return['input','select','textarea'].include(element.tagName.toLowerCase());});},focusFirstElement:function(form){form=$(form);form.findFirstElement().activate();return form;},request:function(form,options){form=$(form),options=Object.clone(options||{});var params=options.parameters,action=form.readAttribute('action')||'';if(action.blank())action=window.location.href;options.parameters=form.serialize(true);if(params){if(Object.isString(params))params=params.toQueryParams();Object.extend(options.parameters,params);} +if(form.hasAttribute('method')&&!options.method) +options.method=form.method;return new Ajax.Request(action,options);}};Form.Element={focus:function(element){$(element).focus();return element;},select:function(element){$(element).select();return element;}};Form.Element.Methods={serialize:function(element){element=$(element);if(!element.disabled&&element.name){var value=element.getValue();if(value!=undefined){var pair={};pair[element.name]=value;return Object.toQueryString(pair);}} +return'';},getValue:function(element){element=$(element);var method=element.tagName.toLowerCase();return Form.Element.Serializers[method](element);},setValue:function(element,value){element=$(element);var method=element.tagName.toLowerCase();Form.Element.Serializers[method](element,value);return element;},clear:function(element){$(element).value='';return element;},present:function(element){return $(element).value!='';},activate:function(element){element=$(element);try{element.focus();if(element.select&&(element.tagName.toLowerCase()!='input'||!['button','reset','submit'].include(element.type))) +element.select();}catch(e){} +return element;},disable:function(element){element=$(element);element.blur();element.disabled=true;return element;},enable:function(element){element=$(element);element.disabled=false;return element;}};var Field=Form.Element;var $F=Form.Element.Methods.getValue;Form.Element.Serializers={input:function(element,value){switch(element.type.toLowerCase()){case'checkbox':case'radio':return Form.Element.Serializers.inputSelector(element,value);default:return Form.Element.Serializers.textarea(element,value);}},inputSelector:function(element,value){if(Object.isUndefined(value))return element.checked?element.value:null;else element.checked=!!value;},textarea:function(element,value){if(Object.isUndefined(value))return element.value;else element.value=value;},select:function(element,index){if(Object.isUndefined(index)) +return this[element.type=='select-one'?'selectOne':'selectMany'](element);else{var opt,value,single=!Object.isArray(index);for(var i=0,length=element.length;i=0?this.optionValue(element.options[index]):null;},selectMany:function(element){var values,length=element.length;if(!length)return null;for(var i=0,values=[];i<\/script>");$("__onDOMContentLoaded").onreadystatechange=function(){if(this.readyState=="complete"){this.onreadystatechange=null;fireContentLoadedEvent();}};}})();Hash.toQueryString=Object.toQueryString;var Toggle={display:Element.toggle};Element.Methods.childOf=Element.Methods.descendantOf;var Insertion={Before:function(element,content){return Element.insert(element,{before:content});},Top:function(element,content){return Element.insert(element,{top:content});},Bottom:function(element,content){return Element.insert(element,{bottom:content});},After:function(element,content){return Element.insert(element,{after:content});}};var $continue=new Error('"throw $continue" is deprecated, use "return" instead');var Position={includeScrollOffsets:false,prepare:function(){this.deltaX=window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft||0;this.deltaY=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0;},within:function(element,x,y){if(this.includeScrollOffsets) +return this.withinIncludingScrolloffsets(element,x,y);this.xcomp=x;this.ycomp=y;this.offset=Element.cumulativeOffset(element);return(y>=this.offset[1]&&y=this.offset[0]&&x=this.offset[1]&&this.ycomp=this.offset[0]&&this.xcomp0;})._each(iterator);},set:function(className){this.element.className=className;},add:function(classNameToAdd){if(this.include(classNameToAdd))return;this.set($A(this).concat(classNameToAdd).join(' '));},remove:function(classNameToRemove){if(!this.include(classNameToRemove))return;this.set($A(this).without(classNameToRemove).join(' '));},toString:function(){return $A(this).join(' ');}};Object.extend(Element.ClassNames.prototype,Enumerable);Element.addMethods(); + +String.prototype.parseColor=function(){var color='#';if(this.slice(0,4)=='rgb('){var cols=this.slice(4,this.length-1).split(',');var i=0;do{color+=parseInt(cols[i]).toColorPart()}while(++i<3);}else{if(this.slice(0,1)=='#'){if(this.length==4)for(var i=1;i<4;i++)color+=(this.charAt(i)+this.charAt(i)).toLowerCase();if(this.length==7)color=this.toLowerCase();}} +return(color.length==7?color:(arguments[0]||this));};Element.collectTextNodes=function(element){return $A($(element).childNodes).collect(function(node){return(node.nodeType==3?node.nodeValue:(node.hasChildNodes()?Element.collectTextNodes(node):''));}).flatten().join('');};Element.collectTextNodesIgnoreClass=function(element,className){return $A($(element).childNodes).collect(function(node){return(node.nodeType==3?node.nodeValue:((node.hasChildNodes()&&!Element.hasClassName(node,className))?Element.collectTextNodesIgnoreClass(node,className):''));}).flatten().join('');};Element.setContentZoom=function(element,percent){element=$(element);element.setStyle({fontSize:(percent/100)+'em'});if(Prototype.Browser.WebKit)window.scrollBy(0,0);return element;};Element.getInlineOpacity=function(element){return $(element).style.opacity||'';};Element.forceRerendering=function(element){try{element=$(element);var n=document.createTextNode(' ');element.appendChild(n);element.removeChild(n);}catch(e){}};var Effect={_elementDoesNotExistError:{name:'ElementDoesNotExistError',message:'The specified DOM element does not exist, but is required for this effect to operate'},Transitions:{linear:Prototype.K,sinoidal:function(pos){return(-Math.cos(pos*Math.PI)/2)+0.5;},reverse:function(pos){return 1-pos;},flicker:function(pos){var pos=((-Math.cos(pos*Math.PI)/4)+0.75)+Math.random()/4;return pos>1?1:pos;},wobble:function(pos){return(-Math.cos(pos*Math.PI*(9*pos))/2)+0.5;},pulse:function(pos,pulses){pulses=pulses||5;return(((pos%(1/pulses))*pulses).round()==0?((pos*pulses*2)-(pos*pulses*2).floor()):1-((pos*pulses*2)-(pos*pulses*2).floor()));},spring:function(pos){return 1-(Math.cos(pos*4.5*Math.PI)*Math.exp(-pos*6));},none:function(pos){return 0;},full:function(pos){return 1;}},DefaultOptions:{duration:1.0,fps:100,sync:false,from:0.0,to:1.0,delay:0.0,queue:'parallel'},tagifyText:function(element){var tagifyStyle='position:relative';if(Prototype.Browser.IE)tagifyStyle+=';zoom:1';element=$(element);$A(element.childNodes).each(function(child){if(child.nodeType==3){child.nodeValue.toArray().each(function(character){element.insertBefore(new Element('span',{style:tagifyStyle}).update(character==' '?String.fromCharCode(160):character),child);});Element.remove(child);}});},multiple:function(element,effect){var elements;if(((typeof element=='object')||Object.isFunction(element))&&(element.length)) +elements=element;else +elements=$(element).childNodes;var options=Object.extend({speed:0.1,delay:0.0},arguments[2]||{});var masterDelay=options.delay;$A(elements).each(function(element,index){new effect(element,Object.extend(options,{delay:index*options.speed+masterDelay}));});},PAIRS:{'slide':['SlideDown','SlideUp'],'blind':['BlindDown','BlindUp'],'appear':['Appear','Fade']},toggle:function(element,effect){element=$(element);effect=(effect||'appear').toLowerCase();var options=Object.extend({queue:{position:'end',scope:(element.id||'global'),limit:1}},arguments[2]||{});Effect[element.visible()?Effect.PAIRS[effect][1]:Effect.PAIRS[effect][0]](element,options);}};Effect.DefaultOptions.transition=Effect.Transitions.sinoidal;Effect.ScopedQueue=Class.create(Enumerable,{initialize:function(){this.effects=[];this.interval=null;},_each:function(iterator){this.effects._each(iterator);},add:function(effect){var timestamp=new Date().getTime();var position=Object.isString(effect.options.queue)?effect.options.queue:effect.options.queue.position;switch(position){case'front':this.effects.findAll(function(e){return e.state=='idle'}).each(function(e){e.startOn+=effect.finishOn;e.finishOn+=effect.finishOn;});break;case'with-last':timestamp=this.effects.pluck('startOn').max()||timestamp;break;case'end':timestamp=this.effects.pluck('finishOn').max()||timestamp;break;} +effect.startOn+=timestamp;effect.finishOn+=timestamp;if(!effect.options.queue.limit||(this.effects.length=this.startOn){if(timePos>=this.finishOn){this.render(1.0);this.cancel();this.event('beforeFinish');if(this.finish)this.finish();this.event('afterFinish');return;} +var pos=(timePos-this.startOn)/this.totalTime,frame=(pos*this.totalFrames).round();if(frame>this.currentFrame){this.render(pos);this.currentFrame=frame;}}},cancel:function(){if(!this.options.sync) +Effect.Queues.get(Object.isString(this.options.queue)?'global':this.options.queue.scope).remove(this);this.state='finished';},event:function(eventName){if(this.options[eventName+'Internal'])this.options[eventName+'Internal'](this);if(this.options[eventName])this.options[eventName](this);},inspect:function(){var data=$H();for(property in this) +if(!Object.isFunction(this[property]))data.set(property,this[property]);return'#';}});Effect.Parallel=Class.create(Effect.Base,{initialize:function(effects){this.effects=effects||[];this.start(arguments[1]);},update:function(position){this.effects.invoke('render',position);},finish:function(position){this.effects.each(function(effect){effect.render(1.0);effect.cancel();effect.event('beforeFinish');if(effect.finish)effect.finish(position);effect.event('afterFinish');});}});Effect.Tween=Class.create(Effect.Base,{initialize:function(object,from,to){object=Object.isString(object)?$(object):object;var args=$A(arguments),method=args.last(),options=args.length==5?args[3]:null;this.method=Object.isFunction(method)?method.bind(object):Object.isFunction(object[method])?object[method].bind(object):function(value){object[method]=value};this.start(Object.extend({from:from,to:to},options||{}));},update:function(position){this.method(position);}});Effect.Event=Class.create(Effect.Base,{initialize:function(){this.start(Object.extend({duration:0},arguments[0]||{}));},update:Prototype.emptyFunction});Effect.Opacity=Class.create(Effect.Base,{initialize:function(element){this.element=$(element);if(!this.element)throw(Effect._elementDoesNotExistError);if(Prototype.Browser.IE&&(!this.element.currentStyle.hasLayout)) +this.element.setStyle({zoom:1});var options=Object.extend({from:this.element.getOpacity()||0.0,to:1.0},arguments[1]||{});this.start(options);},update:function(position){this.element.setOpacity(position);}});Effect.Move=Class.create(Effect.Base,{initialize:function(element){this.element=$(element);if(!this.element)throw(Effect._elementDoesNotExistError);var options=Object.extend({x:0,y:0,mode:'relative'},arguments[1]||{});this.start(options);},setup:function(){this.element.makePositioned();this.originalLeft=parseFloat(this.element.getStyle('left')||'0');this.originalTop=parseFloat(this.element.getStyle('top')||'0');if(this.options.mode=='absolute'){this.options.x=this.options.x-this.originalLeft;this.options.y=this.options.y-this.originalTop;}},update:function(position){this.element.setStyle({left:(this.options.x*position+this.originalLeft).round()+'px',top:(this.options.y*position+this.originalTop).round()+'px'});}});Effect.MoveBy=function(element,toTop,toLeft){return new Effect.Move(element,Object.extend({x:toLeft,y:toTop},arguments[3]||{}));};Effect.Scale=Class.create(Effect.Base,{initialize:function(element,percent){this.element=$(element);if(!this.element)throw(Effect._elementDoesNotExistError);var options=Object.extend({scaleX:true,scaleY:true,scaleContent:true,scaleFromCenter:false,scaleMode:'box',scaleFrom:100.0,scaleTo:percent},arguments[2]||{});this.start(options);},setup:function(){this.restoreAfterFinish=this.options.restoreAfterFinish||false;this.elementPositioning=this.element.getStyle('position');this.originalStyle={};['top','left','width','height','fontSize'].each(function(k){this.originalStyle[k]=this.element.style[k];}.bind(this));this.originalTop=this.element.offsetTop;this.originalLeft=this.element.offsetLeft;var fontSize=this.element.getStyle('font-size')||'100%';['em','px','%','pt'].each(function(fontSizeType){if(fontSize.indexOf(fontSizeType)>0){this.fontSize=parseFloat(fontSize);this.fontSizeType=fontSizeType;}}.bind(this));this.factor=(this.options.scaleTo-this.options.scaleFrom)/100;this.dims=null;if(this.options.scaleMode=='box') +this.dims=[this.element.offsetHeight,this.element.offsetWidth];if(/^content/.test(this.options.scaleMode)) +this.dims=[this.element.scrollHeight,this.element.scrollWidth];if(!this.dims) +this.dims=[this.options.scaleMode.originalHeight,this.options.scaleMode.originalWidth];},update:function(position){var currentScale=(this.options.scaleFrom/100.0)+(this.factor*position);if(this.options.scaleContent&&this.fontSize) +this.element.setStyle({fontSize:this.fontSize*currentScale+this.fontSizeType});this.setDimensions(this.dims[0]*currentScale,this.dims[1]*currentScale);},finish:function(position){if(this.restoreAfterFinish)this.element.setStyle(this.originalStyle);},setDimensions:function(height,width){var d={};if(this.options.scaleX)d.width=width.round()+'px';if(this.options.scaleY)d.height=height.round()+'px';if(this.options.scaleFromCenter){var topd=(height-this.dims[0])/2;var leftd=(width-this.dims[1])/2;if(this.elementPositioning=='absolute'){if(this.options.scaleY)d.top=this.originalTop-topd+'px';if(this.options.scaleX)d.left=this.originalLeft-leftd+'px';}else{if(this.options.scaleY)d.top=-topd+'px';if(this.options.scaleX)d.left=-leftd+'px';}} +this.element.setStyle(d);}});Effect.Highlight=Class.create(Effect.Base,{initialize:function(element){this.element=$(element);if(!this.element)throw(Effect._elementDoesNotExistError);var options=Object.extend({startcolor:'#ffff99'},arguments[1]||{});this.start(options);},setup:function(){if(this.element.getStyle('display')=='none'){this.cancel();return;} +this.oldStyle={};if(!this.options.keepBackgroundImage){this.oldStyle.backgroundImage=this.element.getStyle('background-image');this.element.setStyle({backgroundImage:'none'});} +if(!this.options.endcolor) +this.options.endcolor=this.element.getStyle('background-color').parseColor('#ffffff');if(!this.options.restorecolor) +this.options.restorecolor=this.element.getStyle('background-color');this._base=$R(0,2).map(function(i){return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16)}.bind(this));this._delta=$R(0,2).map(function(i){return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i]}.bind(this));},update:function(position){this.element.setStyle({backgroundColor:$R(0,2).inject('#',function(m,v,i){return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart());}.bind(this))});},finish:function(){this.element.setStyle(Object.extend(this.oldStyle,{backgroundColor:this.options.restorecolor}));}});Effect.ScrollTo=function(element){var options=arguments[1]||{},scrollOffsets=document.viewport.getScrollOffsets(),elementOffsets=$(element).cumulativeOffset(),max=(window.height||document.body.scrollHeight)-document.viewport.getHeight();if(options.offset)elementOffsets[1]+=options.offset;return new Effect.Tween(null,scrollOffsets.top,elementOffsets[1]>max?max:elementOffsets[1],options,function(p){scrollTo(scrollOffsets.left,p.round())});};Effect.Fade=function(element){element=$(element);var oldOpacity=element.getInlineOpacity();var options=Object.extend({from:element.getOpacity()||1.0,to:0.0,afterFinishInternal:function(effect){if(effect.options.to!=0)return;effect.element.hide().setStyle({opacity:oldOpacity});}},arguments[1]||{});return new Effect.Opacity(element,options);};Effect.Appear=function(element){element=$(element);var options=Object.extend({from:(element.getStyle('display')=='none'?0.0:element.getOpacity()||0.0),to:1.0,afterFinishInternal:function(effect){effect.element.forceRerendering();},beforeSetup:function(effect){effect.element.setOpacity(effect.options.from).show();}},arguments[1]||{});return new Effect.Opacity(element,options);};Effect.Puff=function(element){element=$(element);var oldStyle={opacity:element.getInlineOpacity(),position:element.getStyle('position'),top:element.style.top,left:element.style.left,width:element.style.width,height:element.style.height};return new Effect.Parallel([new Effect.Scale(element,200,{sync:true,scaleFromCenter:true,scaleContent:true,restoreAfterFinish:true}),new Effect.Opacity(element,{sync:true,to:0.0})],Object.extend({duration:1.0,beforeSetupInternal:function(effect){Position.absolutize(effect.effects[0].element)},afterFinishInternal:function(effect){effect.effects[0].element.hide().setStyle(oldStyle);}},arguments[1]||{}));};Effect.BlindUp=function(element){element=$(element);element.makeClipping();return new Effect.Scale(element,0,Object.extend({scaleContent:false,scaleX:false,restoreAfterFinish:true,afterFinishInternal:function(effect){effect.element.hide().undoClipping();}},arguments[1]||{}));};Effect.BlindDown=function(element){element=$(element);var elementDimensions=element.getDimensions();return new Effect.Scale(element,100,Object.extend({scaleContent:false,scaleX:false,scaleFrom:0,scaleMode:{originalHeight:elementDimensions.height,originalWidth:elementDimensions.width},restoreAfterFinish:true,afterSetup:function(effect){effect.element.makeClipping().setStyle({height:'0px'}).show();},afterFinishInternal:function(effect){effect.element.undoClipping();}},arguments[1]||{}));};Effect.SwitchOff=function(element){element=$(element);var oldOpacity=element.getInlineOpacity();return new Effect.Appear(element,Object.extend({duration:0.4,from:0,transition:Effect.Transitions.flicker,afterFinishInternal:function(effect){new Effect.Scale(effect.element,1,{duration:0.3,scaleFromCenter:true,scaleX:false,scaleContent:false,restoreAfterFinish:true,beforeSetup:function(effect){effect.element.makePositioned().makeClipping();},afterFinishInternal:function(effect){effect.element.hide().undoClipping().undoPositioned().setStyle({opacity:oldOpacity});}})}},arguments[1]||{}));};Effect.DropOut=function(element){element=$(element);var oldStyle={top:element.getStyle('top'),left:element.getStyle('left'),opacity:element.getInlineOpacity()};return new Effect.Parallel([new Effect.Move(element,{x:0,y:100,sync:true}),new Effect.Opacity(element,{sync:true,to:0.0})],Object.extend({duration:0.5,beforeSetup:function(effect){effect.effects[0].element.makePositioned();},afterFinishInternal:function(effect){effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);}},arguments[1]||{}));};Effect.Shake=function(element){element=$(element);var options=Object.extend({distance:20,duration:0.5},arguments[1]||{});var distance=parseFloat(options.distance);var split=parseFloat(options.duration)/10.0;var oldStyle={top:element.getStyle('top'),left:element.getStyle('left')};return new Effect.Move(element,{x:distance,y:0,duration:split,afterFinishInternal:function(effect){new Effect.Move(effect.element,{x:-distance*2,y:0,duration:split*2,afterFinishInternal:function(effect){new Effect.Move(effect.element,{x:distance*2,y:0,duration:split*2,afterFinishInternal:function(effect){new Effect.Move(effect.element,{x:-distance*2,y:0,duration:split*2,afterFinishInternal:function(effect){new Effect.Move(effect.element,{x:distance*2,y:0,duration:split*2,afterFinishInternal:function(effect){new Effect.Move(effect.element,{x:-distance,y:0,duration:split,afterFinishInternal:function(effect){effect.element.undoPositioned().setStyle(oldStyle);}})}})}})}})}})}});};Effect.SlideDown=function(element){element=$(element).cleanWhitespace();var oldInnerBottom=element.down().getStyle('bottom');var elementDimensions=element.getDimensions();return new Effect.Scale(element,100,Object.extend({scaleContent:false,scaleX:false,scaleFrom:window.opera?0:1,scaleMode:{originalHeight:elementDimensions.height,originalWidth:elementDimensions.width},restoreAfterFinish:true,afterSetup:function(effect){effect.element.makePositioned();effect.element.down().makePositioned();if(window.opera)effect.element.setStyle({top:''});effect.element.makeClipping().setStyle({height:'0px'}).show();},afterUpdateInternal:function(effect){effect.element.down().setStyle({bottom:(effect.dims[0]-effect.element.clientHeight)+'px'});},afterFinishInternal:function(effect){effect.element.undoClipping().undoPositioned();effect.element.down().undoPositioned().setStyle({bottom:oldInnerBottom});}},arguments[1]||{}));};Effect.SlideUp=function(element){element=$(element).cleanWhitespace();var oldInnerBottom=element.down().getStyle('bottom');var elementDimensions=element.getDimensions();return new Effect.Scale(element,window.opera?0:1,Object.extend({scaleContent:false,scaleX:false,scaleMode:'box',scaleFrom:100,scaleMode:{originalHeight:elementDimensions.height,originalWidth:elementDimensions.width},restoreAfterFinish:true,afterSetup:function(effect){effect.element.makePositioned();effect.element.down().makePositioned();if(window.opera)effect.element.setStyle({top:''});effect.element.makeClipping().show();},afterUpdateInternal:function(effect){effect.element.down().setStyle({bottom:(effect.dims[0]-effect.element.clientHeight)+'px'});},afterFinishInternal:function(effect){effect.element.hide().undoClipping().undoPositioned();effect.element.down().undoPositioned().setStyle({bottom:oldInnerBottom});}},arguments[1]||{}));};Effect.Squish=function(element){return new Effect.Scale(element,window.opera?1:0,{restoreAfterFinish:true,beforeSetup:function(effect){effect.element.makeClipping();},afterFinishInternal:function(effect){effect.element.hide().undoClipping();}});};Effect.Grow=function(element){element=$(element);var options=Object.extend({direction:'center',moveTransition:Effect.Transitions.sinoidal,scaleTransition:Effect.Transitions.sinoidal,opacityTransition:Effect.Transitions.full},arguments[1]||{});var oldStyle={top:element.style.top,left:element.style.left,height:element.style.height,width:element.style.width,opacity:element.getInlineOpacity()};var dims=element.getDimensions();var initialMoveX,initialMoveY;var moveX,moveY;switch(options.direction){case'top-left':initialMoveX=initialMoveY=moveX=moveY=0;break;case'top-right':initialMoveX=dims.width;initialMoveY=moveY=0;moveX=-dims.width;break;case'bottom-left':initialMoveX=moveX=0;initialMoveY=dims.height;moveY=-dims.height;break;case'bottom-right':initialMoveX=dims.width;initialMoveY=dims.height;moveX=-dims.width;moveY=-dims.height;break;case'center':initialMoveX=dims.width/2;initialMoveY=dims.height/2;moveX=-dims.width/2;moveY=-dims.height/2;break;} +return new Effect.Move(element,{x:initialMoveX,y:initialMoveY,duration:0.01,beforeSetup:function(effect){effect.element.hide().makeClipping().makePositioned();},afterFinishInternal:function(effect){new Effect.Parallel([new Effect.Opacity(effect.element,{sync:true,to:1.0,from:0.0,transition:options.opacityTransition}),new Effect.Move(effect.element,{x:moveX,y:moveY,sync:true,transition:options.moveTransition}),new Effect.Scale(effect.element,100,{scaleMode:{originalHeight:dims.height,originalWidth:dims.width},sync:true,scaleFrom:window.opera?1:0,transition:options.scaleTransition,restoreAfterFinish:true})],Object.extend({beforeSetup:function(effect){effect.effects[0].element.setStyle({height:'0px'}).show();},afterFinishInternal:function(effect){effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);}},options))}});};Effect.Shrink=function(element){element=$(element);var options=Object.extend({direction:'center',moveTransition:Effect.Transitions.sinoidal,scaleTransition:Effect.Transitions.sinoidal,opacityTransition:Effect.Transitions.none},arguments[1]||{});var oldStyle={top:element.style.top,left:element.style.left,height:element.style.height,width:element.style.width,opacity:element.getInlineOpacity()};var dims=element.getDimensions();var moveX,moveY;switch(options.direction){case'top-left':moveX=moveY=0;break;case'top-right':moveX=dims.width;moveY=0;break;case'bottom-left':moveX=0;moveY=dims.height;break;case'bottom-right':moveX=dims.width;moveY=dims.height;break;case'center':moveX=dims.width/2;moveY=dims.height/2;break;} +return new Effect.Parallel([new Effect.Opacity(element,{sync:true,to:0.0,from:1.0,transition:options.opacityTransition}),new Effect.Scale(element,window.opera?1:0,{sync:true,transition:options.scaleTransition,restoreAfterFinish:true}),new Effect.Move(element,{x:moveX,y:moveY,sync:true,transition:options.moveTransition})],Object.extend({beforeStartInternal:function(effect){effect.effects[0].element.makePositioned().makeClipping();},afterFinishInternal:function(effect){effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle);}},options));};Effect.Pulsate=function(element){element=$(element);var options=arguments[1]||{};var oldOpacity=element.getInlineOpacity();var transition=options.transition||Effect.Transitions.sinoidal;var reverser=function(pos){return transition(1-Effect.Transitions.pulse(pos,options.pulses))};reverser.bind(transition);return new Effect.Opacity(element,Object.extend(Object.extend({duration:2.0,from:0,afterFinishInternal:function(effect){effect.element.setStyle({opacity:oldOpacity});}},options),{transition:reverser}));};Effect.Fold=function(element){element=$(element);var oldStyle={top:element.style.top,left:element.style.left,width:element.style.width,height:element.style.height};element.makeClipping();return new Effect.Scale(element,5,Object.extend({scaleContent:false,scaleX:false,afterFinishInternal:function(effect){new Effect.Scale(element,1,{scaleContent:false,scaleY:false,afterFinishInternal:function(effect){effect.element.hide().undoClipping().setStyle(oldStyle);}});}},arguments[1]||{}));};Effect.Morph=Class.create(Effect.Base,{initialize:function(element){this.element=$(element);if(!this.element)throw(Effect._elementDoesNotExistError);var options=Object.extend({style:{}},arguments[1]||{});if(!Object.isString(options.style))this.style=$H(options.style);else{if(options.style.include(':')) +this.style=options.style.parseStyle();else{this.element.addClassName(options.style);this.style=$H(this.element.getStyles());this.element.removeClassName(options.style);var css=this.element.getStyles();this.style=this.style.reject(function(style){return style.value==css[style.key];});options.afterFinishInternal=function(effect){effect.element.addClassName(effect.options.style);effect.transforms.each(function(transform){effect.element.style[transform.style]='';});}}} +this.start(options);},setup:function(){function parseColor(color){if(!color||['rgba(0, 0, 0, 0)','transparent'].include(color))color='#ffffff';color=color.parseColor();return $R(0,2).map(function(i){return parseInt(color.slice(i*2+1,i*2+3),16)});} +this.transforms=this.style.map(function(pair){var property=pair[0],value=pair[1],unit=null;if(value.parseColor('#zzzzzz')!='#zzzzzz'){value=value.parseColor();unit='color';}else if(property=='opacity'){value=parseFloat(value);if(Prototype.Browser.IE&&(!this.element.currentStyle.hasLayout)) +this.element.setStyle({zoom:1});}else if(Element.CSS_LENGTH.test(value)){var components=value.match(/^([\+\-]?[0-9\.]+)(.*)$/);value=parseFloat(components[1]);unit=(components.length==3)?components[2]:null;} +var originalValue=this.element.getStyle(property);return{style:property.camelize(),originalValue:unit=='color'?parseColor(originalValue):parseFloat(originalValue||0),targetValue:unit=='color'?parseColor(value):value,unit:unit};}.bind(this)).reject(function(transform){return((transform.originalValue==transform.targetValue)||(transform.unit!='color'&&(isNaN(transform.originalValue)||isNaN(transform.targetValue))))});},update:function(position){var style={},transform,i=this.transforms.length;while(i--) +style[(transform=this.transforms[i]).style]=transform.unit=='color'?'#'+ +(Math.round(transform.originalValue[0]+ +(transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart()+ +(Math.round(transform.originalValue[1]+ +(transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart()+ +(Math.round(transform.originalValue[2]+ +(transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart():(transform.originalValue+ +(transform.targetValue-transform.originalValue)*position).toFixed(3)+ +(transform.unit===null?'':transform.unit);this.element.setStyle(style,true);}});Effect.Transform=Class.create({initialize:function(tracks){this.tracks=[];this.options=arguments[1]||{};this.addTracks(tracks);},addTracks:function(tracks){tracks.each(function(track){track=$H(track);var data=track.values().first();this.tracks.push($H({ids:track.keys().first(),effect:Effect.Morph,options:{style:data}}));}.bind(this));return this;},play:function(){return new Effect.Parallel(this.tracks.map(function(track){var ids=track.get('ids'),effect=track.get('effect'),options=track.get('options');var elements=[$(ids)||$$(ids)].flatten();return elements.map(function(e){return new effect(e,Object.extend({sync:true},options))});}).flatten(),this.options);}});Element.CSS_PROPERTIES=$w('backgroundColor backgroundPosition borderBottomColor borderBottomStyle '+'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth '+'borderRightColor borderRightStyle borderRightWidth borderSpacing '+'borderTopColor borderTopStyle borderTopWidth bottom clip color '+'fontSize fontWeight height left letterSpacing lineHeight '+'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+'maxWidth minHeight minWidth opacity outlineColor outlineOffset '+'outlineWidth paddingBottom paddingLeft paddingRight paddingTop '+'right textIndent top width wordSpacing zIndex');Element.CSS_LENGTH=/^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;String.__parseStyleElement=document.createElement('div');String.prototype.parseStyle=function(){var style,styleRules=$H();if(Prototype.Browser.WebKit) +style=new Element('div',{style:this}).style;else{String.__parseStyleElement.innerHTML='
';style=String.__parseStyleElement.childNodes[0].style;} +Element.CSS_PROPERTIES.each(function(property){if(style[property])styleRules.set(property,style[property]);});if(Prototype.Browser.IE&&this.include('opacity')) +styleRules.set('opacity',this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);return styleRules;};if(document.defaultView&&document.defaultView.getComputedStyle){Element.getStyles=function(element){var css=document.defaultView.getComputedStyle($(element),null);return Element.CSS_PROPERTIES.inject({},function(styles,property){styles[property]=css[property];return styles;});};}else{Element.getStyles=function(element){element=$(element);var css=element.currentStyle,styles;styles=Element.CSS_PROPERTIES.inject({},function(results,property){results[property]=css[property];return results;});if(!styles.opacity)styles.opacity=element.getOpacity();return styles;};};Effect.Methods={morph:function(element,style){element=$(element);new Effect.Morph(element,Object.extend({style:style},arguments[2]||{}));return element;},visualEffect:function(element,effect,options){element=$(element) +var s=effect.dasherize().camelize(),klass=s.charAt(0).toUpperCase()+s.substring(1);new Effect[klass](element,options);return element;},highlight:function(element,options){element=$(element);new Effect.Highlight(element,options);return element;}};$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+'pulsate shake puff squish switchOff dropOut').each(function(effect){Effect.Methods[effect]=function(element,options){element=$(element);Effect[effect.charAt(0).toUpperCase()+effect.substring(1)](element,options);return element;}});$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(function(f){Effect.Methods[f]=Element[f];});Element.addMethods(Effect.Methods); \ No newline at end of file diff --git a/public/stylesheets/ba.css b/public/stylesheets/ba.css index 3b09b0e..7622560 100644 --- a/public/stylesheets/ba.css +++ b/public/stylesheets/ba.css @@ -22,16 +22,24 @@ a, a:visited, a:hover { fieldset { width: 500px; - margin-left: 25px; + margin: 15px auto 15px auto; } label { margin-right: 5px; } +h1 { + text-align: center; +} + +h1 img { + vertical-align: middle; +} + #container { margin: 0 auto; - width: 750px; + width: 775px; position: relative; background: #ffffff; padding: 0px; @@ -59,17 +67,15 @@ label { border-bottom: 1px solid #a40000; } -#header { - height: 120px; - padding-top: 10px; -} - #header_image { - margin-left: 30px; + width: 446px; + margin: 0px auto; + padding: 10px 0px; } #tool_bar { margin-top: 7px; + margin-bottom: 10x; padding: 3px 10px 3px 10px; border-top: 1px solid #c17d11; border-bottom: 1px solid #c17d11; @@ -119,3 +125,129 @@ label { #recaptcha_container { margin: 10px 0px 10px 0px; +} + +.centered { + margin-left: auto; + margin-right: auto; +} + +#mini_container { + width: 650px; + margin-top: 10px; + margin-bottom: 10px; + min-height: 35px; +} + +#photo_browser { + height: 460px; + margin-top: 15px; + margin-bottom: 15px; +} + +.stat_box { + width: 105px; + float: left; + margin-left: 20px; + min-height: 30px; + border: 1px solid #555753; + background-color: #babdb6; +} + +.stat_box img { + display: block; + margin: 3px auto 3px auto; +} + +.stat_box p { + text-align: center; + margin: 3px; + padding: 0px; +} + +.photo_browser_box { + width: 105px; + height: 105px; + float: left; + margin-left: 20px; + margin-bottom: 10px; +} + +.photo_browser_box img { + display: block; + margin: auto; + vertical-align: middle; +} + +#vote_error { + width: 400px; + padding: 3px; + margin-top: 5px; + margin-bottom: 5px; +} + +#main_photo_container p { + text-align: center; +} + +#vote_controls { + border: 1px solid #c17d11; + background-color: #e9b96e; + width: 66px; + height: 32px; +} + +#vote_controls img { + padding: 5px; + width: 22px; + height: 22px; + float: left; +} + +.user_stat_chart { + float: left; + width: 300px; + height: 200px; + margin: 0px 10px 5px 25px; + border: 1px solid #8f5902; +} + +#scrolling_photo_block_container { + margin-right: 5px; + margin-bottom: 5px; + float: right; + width: 282px; + height: 336px; +} + +#pagination_navigation { + width: 250px; + margin-left: auto; + margin-right: auto; +} + +#pagination_navigation p { + text-align: center; +} + +#pagination_navigation img { + display: inline; + vertical-align: middle; +} + +#scrolling_photo_block_container p { + text-align: center; + margin: 5px; +} + +#scrolling_photo_block img { + display: block; + margin: 5px auto 5px auto; + padding: 0px; +} + +#scrolling_photo_block td, #scrolling_photo_block table { + border: 1px solid #8f5902; + padding: 0px; + margin: 0px; +} diff --git a/schema/migrations/002_create_photos_migration.rb b/schema/migrations/002_create_photos_migration.rb index c5880a9..cb426f2 100644 --- a/schema/migrations/002_create_photos_migration.rb +++ b/schema/migrations/002_create_photos_migration.rb @@ -1,13 +1,12 @@ class CreatePhotosMigration < ActiveRecord::Migration def self.up create_table :photos do |t| - t.string :filename - t.integer :user_id - t.string :owner_token + t.string :filename, :content_type, :email_hash + t.integer :width, :height t.datetime :created_at + t.boolean :approved end - add_index :photos, :user_id - add_index :photos, :owner_token + add_index :photos, :email_hash end def self.down diff --git a/schema/migrations/003_create_votes_migration.rb b/schema/migrations/003_create_votes_migration.rb index 3bb000b..b41c771 100644 --- a/schema/migrations/003_create_votes_migration.rb +++ b/schema/migrations/003_create_votes_migration.rb @@ -2,10 +2,12 @@ class CreateVotesMigration < ActiveRecord::Migration def self.up create_table :votes do |t| t.integer :photo_id, :user_id + t.string :session_id t.boolean :vote end add_index :votes, :photo_id add_index :votes, :user_id + add_index :votes, :session_id end def self.down diff --git a/schema/migrations/005_photo_favorite_migration.rb b/schema/migrations/005_photo_favorite_migration.rb new file mode 100644 index 0000000..fd2d330 --- /dev/null +++ b/schema/migrations/005_photo_favorite_migration.rb @@ -0,0 +1,13 @@ +class PhotoFavoriteMigration < ActiveRecord::Migration + def self.up + create_table :photo_favorites do |t| + t.integer :photo_id, :user_id + end + add_index :photo_favorites, :photo_id + add_index :photo_favorites, :user_id + end + + def self.down + drop_table :photo_favorites + end +end diff --git a/schema/schema.rb b/schema/schema.rb index 72527ae..d72f24e 100644 --- a/schema/schema.rb +++ b/schema/schema.rb @@ -9,17 +9,27 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 4) do +ActiveRecord::Schema.define(:version => 5) do + + create_table "photo_favorites", :force => true do |t| + t.integer "photo_id" + t.integer "user_id" + end + + add_index "photo_favorites", ["user_id"], :name => "index_photo_favorites_on_user_id" + add_index "photo_favorites", ["photo_id"], :name => "index_photo_favorites_on_photo_id" create_table "photos", :force => true do |t| t.string "filename" - t.integer "user_id" - t.string "owner_token" + t.string "content_type" + t.string "email_hash" + t.integer "width" + t.integer "height" t.datetime "created_at" + t.boolean "approved" end - add_index "photos", ["owner_token"], :name => "index_photos_on_owner_token" - add_index "photos", ["user_id"], :name => "index_photos_on_user_id" + add_index "photos", ["email_hash"], :name => "index_photos_on_email_hash" create_table "sessions", :force => true do |t| t.string "session_id" @@ -41,9 +51,11 @@ ActiveRecord::Schema.define(:version => 4) do create_table "votes", :force => true do |t| t.integer "photo_id" t.integer "user_id" + t.string "session_id" t.boolean "vote" end + add_index "votes", ["session_id"], :name => "index_votes_on_session_id" add_index "votes", ["user_id"], :name => "index_votes_on_user_id" add_index "votes", ["photo_id"], :name => "index_votes_on_photo_id" diff --git a/spec/controllers/stats_spec.rb b/spec/controllers/stats_spec.rb deleted file mode 100644 index c81867f..0000000 --- a/spec/controllers/stats_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb') - -describe Stats, "index action" do - before(:each) do - dispatch_to(Stats, :index) - end -end \ No newline at end of file diff --git a/spec/helpers/stats_helper_spec.rb b/spec/helpers/stats_helper_spec.rb deleted file mode 100644 index ca89fef..0000000 --- a/spec/helpers/stats_helper_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb') - -describe Merb::StatsHelper do - -end \ No newline at end of file diff --git a/spec/models/photo_favorite_spec.rb b/spec/models/photo_favorite_spec.rb new file mode 100644 index 0000000..1a7893c --- /dev/null +++ b/spec/models/photo_favorite_spec.rb @@ -0,0 +1,7 @@ +require File.join( File.dirname(__FILE__), '..', "spec_helper" ) + +describe PhotoFavorite do + + it "should have specs" + +end \ No newline at end of file